• 使用 PNPM 从零搭建 Monorepo,测试组件并发布


    1 目标

    通过 PNPM 创建一个 monorepo(多个项目在一个代码仓库)项目,形成一个通用的仓库模板。

    这里以在该 monorepo 项目中搭建 web components 类型的组件库为例,介绍从仓库搭建、组件测试到组件发布的整个流程。

    这个仓库既可以用于公司存放和管理所有的项目,也可以用于将个人班余的所有积累整合其中。

    如不想一步一步搭建,可以直接下载 项目模板

    2 环境要求

    核心是 PNPM 和 Node.js,没有特殊的版本要求,只要他俩能对应上即可。
    在这里插入图片描述

    当前项目使用的 PNPM 版本为 9.3.0,Node.js 为 18.20.3。

    除了以上两个,项目中也使用到了以下工具或插件,可以按需添加,如不使用则不用考虑其环境要求。
    vite(v5.2.0):主要用于项目运行和构建,要求 Node.js v18+ 或者 v20+。
    Storybook(v8.1.7):用于组件的测试和展示,要求 Vite v4.0 +。

    3 仓库搭建

    3.1 新建项目

    新建一个文件夹作为项目容器。

    这里起名叫 ease-life,意为轻松生活。所有的学习、工作都是为了更好地、更轻松的生活。

    3.2 创建目录

    3.2.1 apps

    在项目根目录下创建 apps 文件夹。
    在 apps 下创建 storybook 文件夹。用于测试和展示自定义的 web components。

    apps 文件夹主要存放应用程序,如:Storybook、VitePress,还可以加上 vue-test、react-test 来对 web components 做测试。

    3.2.2 packages

    在项目根目录下创建 apps 文件夹。
    在 packages 下分别创建 config(配置信息)、web-components(实现组件与框架无关) 文件夹。

    • 在 config 文件下创建 eslint、stylelint、commitlint 以及 typescript,用于存放对应的配置
    • 在 web-components 创建 text 文件夹,实现一个简单的文本组件。 text 文件夹下创建 src 文件夹。

    packages 底下主要包含插件、组件、命令行、类库等,除了以上的内容还可以按需加上 vue-components、react-components、cli、map-library 等等。

    形成的目录结构如下:

    ease-life
    |-- apps
    |   |-- storybook
    |-- packages
        |-- config
        |   |-- commitlint
        |   |-- eslint
        |   |-- stylelint
        |   |-- typescript
        |-- web-components
            |-- text
                |-- src
    

    3.3 添加文件

    3.3.1 PNPM 相关

    1. 在项目根目录下添加文件:pnpm-workspace.yaml,定义 PNPM 的工作空间:
    packages:
      # 匹配 packages 目录下(任意文件夹下)的所有模块
      - 'packages/**'
      # 匹配 apps 直接子文件夹下的所有模块
      - 'apps/*'
    

    这里的模块,说的是:包含 package.json,可以被发布到 NPM 远程仓库的项目。

    1. 在项目根目录下添加文件:.npmrc,定义 PNPM 的配置项:
    # 允许链接工作空间中的包
    link-workspace-packages = true
    
    # 在引用工作空间中的包时,设置前缀为 *,即:使用最新版本的包
    save-prefix = ''
    

    3.3.2 Vite 相关

    1. 在根目录下运行以下内容:
    pnpm init
    

    从而生成 package.json,如下:

    {
      "name": "ease-life",
      "version": "1.0.0",
      "description": "",
      "main": "index.js",
      "scripts": {
        "test": "echo \"Error: no test specified1\" && exit 1"
      },
      "keywords": [],
      "author": "",
      "license": "ISC",
    }
    
    1. 在 web-components 以及 web-components/text 下都执行 pnpm init,或直接将根目录下的 package.json 拷贝过去。

    本文的目的是要每个组件都能够被单独被发布至 NPM 仓库,如:@ease-life/text。如只需要做整个组件库的发布,则无需在 web-components/text 下执行 pnpm init。

    1. 在项目最外层空间下添加 vite:
    pnpm add vite -Dw
    

    packages 里的所有模块如无特殊情况,统一使用 vite 来运行、打包,因此只需要在项目最外层安装一次即可。

    1. 在项目根目录下,添加文件 vite.config.js:
    import { defineConfig } from 'vite'
    
    export default defineConfig({
        build: {
            lib: {
                entry: 'index.ts',
                fileName: 'index'
            },
        }
    })
    
    1. 修改之前生成的 package.json:
    {
      "name": "ease-life",
      "version": "1.0.0",
      "description": "哥的幸福生活全靠你啦",
      "scripts": {
        "dev": "vite --open",
        "build": "vite build",
        "preview": "vite preview --open"
      },
      "keywords": [
        "monorepo",
        "web components",
        "pnpm",
        "storybook",
        "changeset"
      ],
      "author": "zqc",
      "repository": {
        "type": "git",
        "url": ""
      },
      "license": "MIT",
      "type": "module",
      "devDependencies": {
        "vite": "^5.2.0"
      },
      "engines": {
        "node": ">= 18.20.3",
        "pnpm": ">= 9.3.0"
      }
    }
    

    3.3.3 添加 config

    待完善

    3.3.4 添加其他

    1. 在项目跟目录下添加 .gitignore:
    # Logs
    logs
    *.log
    npm-debug.log*
    yarn-debug.log*
    yarn-error.log*
    pnpm-debug.log*
    lerna-debug.log*
    
    node_modules
    dist
    dist-ssr
    *.local
    
    # Editor directories and files
    .vscode/*
    !.vscode/extensions.json
    .idea
    .DS_Store
    *.suo
    *.ntvs*
    *.njsproj
    *.sln
    *.sw?
    

    3.3.5 自定义 Web Components

    1. 在 packages/web-components/text/src 下创建 text.ts:
    import { html, css, LitElement } from 'lit';
    import { customElement, property } from 'lit/decorators.js';
    
    @customElement('el-text')
    export class ELText extends LitElement {
        static styles = css`p { color: blue }`;
    
    
        @property()
        name = 'Somebody';
    
        render() {
            return html`

    Hello, ${this.name}!

    `
    ; } }
    1. 在 packages/web-components/text 下创建 index.ts(导出当前组件):
    export { ELText as default } from './src/text.ts';
    
    1. 在 packages/web-components/text 下添加 tsconfig.json:
    {
      "compilerOptions": {
        "target": "ESNext",
        "experimentalDecorators": true,
        "useDefineForClassFields": false,
        "module": "ESNext",
        "lib": [
          "ES2020",
          "DOM",
          "DOM.Iterable"
        ],
        "skipLibCheck": true,
        /* Bundler mode */
        "moduleResolution": "bundler",
        "allowImportingTsExtensions": true,
        "resolveJsonModule": true,
        "isolatedModules": true,
        "noEmit": true,
        /* Linting */
        "strict": true,
        "noUnusedLocals": true,
        "noUnusedParameters": true,
        "noFallthroughCasesInSwitch": true
      },
      "include": [
        "src"
      ]
    }
    

    以上内容将会被移至 packages/config/typescript 中,待修改

    1. 修改 在 packages/web-components/text 下的 package.json:
    {
      "name": "@ease-life/text",
      "version": "1.0.0",
      "description": "",
      "type": "module",
      "files": [
        "dist"
      ],
      "main": "./dist/index.umd.cjs",
      "module": "./dist/index.js",
      "exports": {
        ".": {
          "import": "./dist/index.js",
          "require": "./dist/index.umd.cjs"
        }
      },
      "scripts": {
        "build": "vite build -c ../../../vite.config.js"
      },
      "keywords": [
        "ELText"
      ],
      "author": "",
      "license": "ISC",
      "dependencies": {
        "lit": "^3.1.2"
      }
    }
    

    3.4 生成 storybook

    1. 在 apps/storybook 文件夹的路径下运行以下内容:
    pnpm dlx storybook@latest init
    

    选择最后一个选项,回车。
    在这里插入图片描述
    此时就会在 apps/storybook 下有对应的 storybook 的内容。

    1. 删除 apps/storybook/src/stories 下自带的 button.css、Button.stories.ts、Button.ts、header.css、Header.stories.ts、Header.ts、page.css、Page.stories.ts、Page.ts 六个文件。

    最终项目文件目录结构如下:

    ease-life
        |-- .gitignore
        |-- .npmrc
        |-- package.json
        |-- pnpm-lock.yaml
        |-- pnpm-workspace.yaml
        |-- vite.config.js
        |-- apps
        |   |-- package.json
        |   |-- storybook
        |       |-- .gitignore
        |       |-- index.html
        |       |-- package.json
        |       |-- tsconfig.json
        |       |-- .storybook
        |       |   |-- main.ts
        |       |   |-- preview.ts
        |       |-- public
        |       |   |-- vite.svg
        |       |-- src
        |           |-- index.css
        |           |-- my-element.ts
        |           |-- vite-env.d.ts
        |           |-- assets
        |           |   |-- lit.svg
        |           |-- stories
        |               |-- Configure.mdx
        |               |-- Text.stories.ts
        |               |-- assets
        |                   |-- accessibility.png
        |                   |-- accessibility.svg
        |                   |-- addon-library.png
        |                   |-- assets.png
        |                   |-- avif-test-image.avif
        |                   |-- context.png
        |                   |-- discord.svg
        |                   |-- docs.png
        |                   |-- figma-plugin.png
        |                   |-- github.svg
        |                   |-- share.png
        |                   |-- styling.png
        |                   |-- testing.png
        |                   |-- theming.png
        |                   |-- tutorials.svg
        |                   |-- youtube.svg
        |-- packages
            |-- config
            |   |-- commitlint
            |   |-- eslint
            |   |-- stylelint
            |   |-- typescript
            |-- map-library
            |-- web-components
                |-- text
                    |-- index.ts
                    |-- package.json
                    |-- tsconfig.json
                    |-- src
                        |-- text.ts
    

    4 组件测试

    1. 在项目根目录下运行以下内容,来对 text 进行构建:
    pnpm -F @ease-life/text build
    

    会在 packages/web-components/text 下生成 dist 文件夹,里边有 index.js(ESM) 以及 index.umd.cjs(CommonJS)。

    1. 在 apps/storybook/src/stories 下添加一个 Text.stories.ts:
    import type { Meta, StoryObj } from '@storybook/web-components';
    import '@ease-life/text';
    
    
    const meta: Meta = {
        component: 'el-text'
    };
    
    export default meta;
    type Story = StoryObj;
    
    export const Default: Story = {
        args: {
            name: 'world',
        },
    };
    
    1. 修改 apps/storybook 下的 package.json,将其中的 name 改为:
      "name": "@ease-life/storybook",
    
    1. 在项目根目录下运行以下内容来安装刚才定义的 web components:
    pnpm -F @ease-life/storybook add @ease-life/text
    
    1. 在项目根目录下运行以下内容,来启动 storybook:
    pnpm -F @ease-life/storybook storybook
    

    在浏览器中显示以下内容,则证明组件没有问题了。
    在这里插入图片描述

    5 组件发布

    5.1 在 NPM 官网注册

    如果没有注册过,则打开 NPM,点击右上角的 Sign Up,按提示填入信息。

    5.2 登录账户

    注册完后直接登录。

    5.3 创建组织

    打开创建组织的页面,在其中添加组织名称,组织名称就是 scope 的名称,也就是这里 @ 后面的内容。

    @ease-life/tex,我这里创建了 ease-life 的组织。

    5.3 组件发布

    1. 用户登录,在项目根目录下运行:
    pnpm login
    

    看到提示后,再次回车,在浏览器弹出的页面中进行登录,成功后显示以下内容:
    在这里插入图片描述

    1. 组件发布,在项目根目录下运行:
    pnpm publish -r
    

    会自动发布仓库中版本发生改变的组件。
    在这里插入图片描述
    出现以上类似内容,就证明发布成功了。

  • 相关阅读:
    OpenGL - Framebuffers
    【Spring Boot】Spring Boot集成RabbitMQ
    丝滑的在RT-Smart用户态运行LVGL
    项目管理中,如何建立里程碑式管理?
    Vue电影网站构建实战教程
    jxTMS设计思想之业务框架
    现代图片性能优化及体验优化指南 - 懒加载及异步图像解码方案
    C++控制不同进制输出(二进制,八进制,十进制,十六进制)各种进制之间的转换
    基于JavaSwing开发任务管理器 课程设计 大作业源码
    1024 漏洞综合项目演示(第十七课)
  • 原文地址:https://blog.csdn.net/zapzqc/article/details/139619972