Monorepo 模板 —— 使用 PNPM 从零搭建 Monorepo,测试 web components 并发布

1 目标

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

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

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

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

2 环境要求

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

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

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

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 以及 typescript,用于存放对应通用的配置
  • 在 web-components 创建 text 文件夹,实现一个简单的文本组件。 text 文件夹下创建 src 文件夹。

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

形成的目录结构如下:

ease-life
|-- apps
|   |-- storybook
|-- packages
    |-- config
    |   |-- 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、config/eslint、config/stylelint 以及 config/typescript 下都执行 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'
import { defineConfig } from 'vite';

export default defineConfig({
  build: {
    lib: {
      entry: 'index.ts',
      name: 'index', // UMD模块中库的全局名称,按需修改
      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.0.0",
    "pnpm": ">= 9.0.0"
  }
}

3.3.3 自定义 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`<p>Hello, ${this.name}!</p>`;
    }
}
  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.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 六个文件。

3.3.5 添加 Lint、TS 配置(可选)

通过在 config 文件夹下添加一系列通用配置来满足 monorepo 仓库的需要,同时可以将其进行发布用在其他项目中。如需使用本文已经配置好的内容,则请移步:

  1. ESLint
    添加 index.js:
/* eslint-disable no-underscore-dangle */
import { fixupConfigRules } from '@eslint/compat';
import { FlatCompat } from '@eslint/eslintrc';
import eslint from '@eslint/js';
// import eslintImport from 'eslint-plugin-import';
import globals from 'globals';
import path from 'path';
import tseslint from 'typescript-eslint';
import { fileURLToPath } from 'url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const flatCompat = new FlatCompat({
  baseDirectory: __dirname,
});

export default tseslint.config(
  eslint.configs.recommended,
  ...tseslint.configs.recommendedTypeChecked,
  ...fixupConfigRules(flatCompat.extends('airbnb-base')),
  ...flatCompat.extends('airbnb-typescript/base'),
  {
    languageOptions: {
      parserOptions: {
        project: true,
        tsconfigRootDir: import.meta.dirname,
      },
    },
  },
  {
    files: ['**/*.{js,jsx,cjs,mjs}'],
    ...tseslint.configs.disableTypeChecked,
  },
  {
    // 配置需要被忽略的文件,替代之前的 .eslintignore 文件
    ignores: [
      '.idea',
      '.vscode',
      '**/dist/',
    ],
    files: ['**/*.{js,jsx,mjs,cjs,ts,ts,vue}'],
  },
  {
    // plugins: {
    //   import: eslintImport, // 添加了 airbnb-base 就不用再加这个了
    // },
    settings: {
      'import/resolver': {
        typescript: {},
      },
    },
    languageOptions: {
      globals: {
        ...globals.browser, // 'window' is not defined.
        ...globals.node, // 'process' is not defined.
      },
    },
  },
  {
    files: ['eslint.config.js', 'vite.config.js', 'stylelint.config.js'],
    rules: {
      'import/no-extraneous-dependencies': 'off',
    },
  },
  {
    rules: {
      'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
      'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
      'max-len': ['error', 200],
    },
  },
);

修改 package.json:

{
  "name": "@ease-life/eslint-config",
  "version": "1.1.0",
  "description": "Custom airbnb eslint config with ESLint v9",
  "main": "index.js",
  "keywords": [
    "eslint"
  ],
  "author": "zqc",
  "license": "MIT",
  "type": "module",
  "publishConfig": {
    "access": "public"
  },
  "dependencies": {
    "@eslint/compat": "1.1.0",
    "@eslint/eslintrc": "3.1.0",
    "@eslint/js": "9.4.0",
    "eslint": "9.4.0",
    "eslint-config-airbnb-base": "15.0.0",
    "eslint-config-airbnb-typescript": "18.0.0",
    "eslint-import-resolver-typescript": "3.6.1",
    "eslint-plugin-import": "2.29.1",
    "globals": "15.4.0",
    "typescript-eslint": "7.13.0"
  }
}
  1. Stylelint
    添加 index.js:
export default {
  // 继承已有配置 如果规则与自己想要的冲突 可以在下面rules中修改规则
  extends: [
    'stylelint-config-standard',
    'stylelint-config-standard-scss',
    'stylelint-config-sass-guidelines',
  ],
  // 插件是由社区创建的规则或规则集 按照规则对CSS属性进行排序
  plugins: [
    // 执行各种各样的 SCSS语法特性检测规则(插件包)
    'stylelint-scss',
    // 指定排序,比如声明的块内(插件包)属性的顺序
    'stylelint-order',
  ],
  customSyntax: 'postcss-html',
  rules: {
    // 允许的最大嵌套深度为 3
    'max-nesting-depth': 3,
    'color-named': null,
    // 屏蔽一些scss等语法检查
    'at-rule-no-unknown': [
      true,
      {
        ignoreAtRules: [
          'extend',
          'at-root',
          'debug',
          'warn',
          'error',
          'if',
          'else',
          'for',
          'each',
          'while',
          'mixin',
          'include',
          'content',
          'return',
          'function',
        ],
      },
    ],
    // 屏蔽没有申明通用字体
    'font-family-no-missing-generic-family-keyword': null,
    // ID选择器 最多使用一个
    'selector-max-id': 1,
    // 不允许使用的选择器的类型
    'selector-no-qualifying-type': null,
    // 屏蔽类选择器的检查,以确保使用字符 __
    'selector-class-pattern': null,
    // 允许的最大复合选择器为 5
    'selector-max-compound-selectors': 5,
    // 属性排序规则
    'order/properties-order': [
      [
        'content',
        'position',
        'top',
        'right',
        'bottom',
        'left',
        'z-index',
        'display',
        'vertical-align',
        'flex',
        'flex-grow',
        'flex-shrink',
        'flex-basis',
        'flex-direction',
        'flex-flow',
        'flex-wrap',
        'grid',
        'grid-area',
        'grid-template',
        'grid-template-areas',
        'grid-template-rows',
        'grid-template-columns',
        'grid-row',
        'grid-row-start',
        'grid-row-end',
        'grid-column',
        'grid-column-start',
        'grid-column-end',
        'grid-auto-rows',
        'grid-auto-columns',
        'grid-auto-flow',
        'grid-gap',
        'grid-row-gap',
        'grid-column-gap',
        'gap',
        'row-gap',
        'column-gap',
        'align-content',
        'align-items',
        'align-self',
        'justify-content',
        'justify-items',
        'justify-self',
        'order',
        'float',
        'clear',
        'object-fit',
        'overflow',
        'overflow-x',
        'overflow-y',
        'overflow-scrolling',
        'clip',
        //
        'box-sizing',
        'width',
        'min-width',
        'max-width',
        'height',
        'min-height',
        'max-height',
        'margin',
        'margin-top',
        'margin-right',
        'margin-bottom',
        'margin-left',
        'padding',
        'padding-top',
        'padding-right',
        'padding-bottom',
        'padding-left',
        'border',
        'border-spacing',
        'border-collapse',
        'border-width',
        'border-style',
        'border-color',
        'border-top',
        'border-top-width',
        'border-top-style',
        'border-top-color',
        'border-right',
        'border-right-width',
        'border-right-style',
        'border-right-color',
        'border-bottom',
        'border-bottom-width',
        'border-bottom-style',
        'border-bottom-color',
        'border-left',
        'border-left-width',
        'border-left-style',
        'border-left-color',
        'border-radius',
        'border-top-left-radius',
        'border-top-right-radius',
        'border-bottom-right-radius',
        'border-bottom-left-radius',
        'border-image',
        'border-image-source',
        'border-image-slice',
        'border-image-width',
        'border-image-outset',
        'border-image-repeat',
        'border-top-image',
        'border-right-image',
        'border-bottom-image',
        'border-left-image',
        'border-corner-image',
        'border-top-left-image',
        'border-top-right-image',
        'border-bottom-right-image',
        'border-bottom-left-image',
        //
        'background',
        'background-color',
        'background-image',
        'background-attachment',
        'background-position',
        'background-position-x',
        'background-position-y',
        'background-clip',
        'background-origin',
        'background-size',
        'background-repeat',
        'color',
        'box-decoration-break',
        'box-shadow',
        'outline',
        'outline-width',
        'outline-style',
        'outline-color',
        'outline-offset',
        'table-layout',
        'caption-side',
        'empty-cells',
        'list-style',
        'list-style-position',
        'list-style-type',
        'list-style-image',
        //
        'font',
        'font-weight',
        'font-style',
        'font-variant',
        'font-size-adjust',
        'font-stretch',
        'font-size',
        'font-family',
        'src',
        'line-height',
        'letter-spacing',
        'quotes',
        'counter-increment',
        'counter-reset',
        '-ms-writing-mode',
        'text-align',
        'text-align-last',
        'text-decoration',
        'text-emphasis',
        'text-emphasis-position',
        'text-emphasis-style',
        'text-emphasis-color',
        'text-indent',
        'text-justify',
        'text-outline',
        'text-transform',
        'text-wrap',
        'text-overflow',
        'text-overflow-ellipsis',
        'text-overflow-mode',
        'text-shadow',
        'white-space',
        'word-spacing',
        'word-wrap',
        'word-break',
        'overflow-wrap',
        'tab-size',
        'hyphens',
        'interpolation-mode',
        //
        'opacity',
        'visibility',
        'filter',
        'resize',
        'cursor',
        'pointer-events',
        'user-select',
        //
        'unicode-bidi',
        'direction',
        'columns',
        'column-span',
        'column-width',
        'column-count',
        'column-fill',
        'column-gap',
        'column-rule',
        'column-rule-width',
        'column-rule-style',
        'column-rule-color',
        'break-before',
        'break-inside',
        'break-after',
        'page-break-before',
        'page-break-inside',
        'page-break-after',
        'orphans',
        'widows',
        'zoom',
        'max-zoom',
        'min-zoom',
        'user-zoom',
        'orientation',
        'fill',
        'stroke',
        //
        'transition',
        'transition-delay',
        'transition-timing-function',
        'transition-duration',
        'transition-property',
        'transform',
        'transform-origin',
        'animation',
        'animation-name',
        'animation-duration',
        'animation-play-state',
        'animation-timing-function',
        'animation-delay',
        'animation-iteration-count',
        'animation-direction',
        'animation-fill-mode',
      ],
      {
        unspecified: 'bottom',
        severity: 'warning',
      },
    ],
    // 屏蔽属性按字母顺序检查
    'order/properties-alphabetical-order': null,
  },
};

修改 package.json:

{
  "name": "@ease-life/stylelint-config",
  "version": "1.1.0",
  "description": "Custom stylelint",
  "main": "index.js",
  "keywords": [
    "stylelint"
  ],
  "author": "zqc",
  "license": "MIT",
  "type": "module",
  "publishConfig": {
    "access": "public"
  },
  "peerDependencies": {
    "postcss-html": "1.7.0",
    "stylelint": "16.6.1",
    "stylelint-config-sass-guidelines": "11.1.0",
    "stylelint-config-standard": "36.0.0",
    "stylelint-config-standard-scss": "13.1.0",
    "stylelint-order": "6.0.4",
    "stylelint-scss": "6.3.1"
  }
}
  1. TypeScript
    添加 base.json:
{
  "compilerOptions": {
    "allowJs": true,
    "esModuleInterop": true,
    "experimentalDecorators": true,
    "isolatedModules": true,
    "lib": [
      "ESNext",
      "DOM"
    ],
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "noImplicitAny": true,
    "skipLibCheck": true,
    "strict": true,
    "target": "ESNext"
  },
  "exclude": [
    "**/node_modules",
    "**/dist"
  ]
}

修改 package.json:

{
  "name": "@ease-life/typescript-config",
  "version": "1.1.1",
  "description": "Custom tsconfig",
  "keywords": [
    "tsconfig"
  ],
  "author": "zqc",
  "license": "MIT",
  "publishConfig": {
    "access": "public"
  }
}

3.3.6 添加 changeset(可选)

主要用于流程化发布和在项目中生成 CHANGELOG.md。

pnpm add @changesets/cli -Dw

3.3.7 添加其他

  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?
  1. 在项目根目录下创建 .editorconfig:
# 告诉编辑器这是最顶层的(不要再向上找了)
root = true

# All Files
[*]
charset = utf-8                  # 设置编码为 utf-8
indent_style = space             # 2 个空格的缩进
indent_size = 2
end_of_line = lf                 # Unix 风格的换行
insert_final_newline = true      # 始终在文件末尾插入一个新行
trim_trailing_whitespace = true  # 删除行尾空格
max_line_length = 200            # 单行最大宽度

# Markdown 文件
[*.md]
insert_final_newline = false
trim_trailing_whitespace = false
  1. commitlint&husky&lintstaged
pnpm add -Dw @commitlint/cli @commitlint/config-conventional husky lint-staged
echo "export default { extends: ['@commitlint/config-conventional'] };" > commitlint.config.js
pnpm exec husky init

打开 .husky/pre-commit 文件,删除其中的 “pnpm test” ,替换成:

pnpm exec lint-staged

继续执行:

echo "pnpm exec commitlint --edit \$1" > .husky/commit-msg

在根目录下的 package.json 中添加:

"lint-staged": {
    "*.{js,vue,ts}": [
      "pnpm exec eslint --fix"
    ],
    "*.{css,scss}": [
      "pnpm exec stylelint --fix"
    ]
  }

3.3.8 安装config中的内容并配置

pnpm add -Dw @ease-life/eslint-config @ease-life/stylelint-config @ease-life/typescript-config
pnpm add -Dw eslint lint-staged postcss-html stylelint stylelint-config-sass-guidelines stylelint-config-standard stylelint-config-standard-scss stylelint-order stylelint-scss

在项目根目录下创建 commitlint.config.js:

export default { extends: ['@commitlint/config-conventional'] };

在项目根目录下创建 eslint.config.js:

import baseConfig from '@ease-life/eslint-config';

export default [
  ...baseConfig,
  {
    // 屏蔽 web components 组件库的 eslint 规则
    files: ['packages/web-components/**/*.ts', 'apps/storybook/src/my-element.ts'],
    rules: {
      'import/extensions': [
        'error',
        { js: 'always' },
      ],
      'import/prefer-default-export': 'off',
      'no-restricted-exports': 'off',
      '@typescript-eslint/no-unsafe-assignment': 'off',
      '@typescript-eslint/no-unsafe-call': 'off',
      '@typescript-eslint/no-unsafe-return': 'off',
      '@typescript-eslint/indent': 'off',
    },
  },
];

在项目根目录下创建 stylelint.config.js:

import baseConfig from '@ease-life/stylelint-config';

export default {
  ...baseConfig,
};

在项目根目录下创建 tsconfig.json:

{
  "extends": "@ease-life/typescript-config/base.json",
  "include": [
    "packages/**/*.ts",
    "apps/**/*.ts"
  ],
}

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

ease-life
|-- github
    |-- .DS_Store
    |-- .editorconfig
    |-- .gitignore
    |-- .npmrc
    |-- README.md
    |-- commitlint.config.js
    |-- eslint.config.js
    |-- package.json
    |-- pnpm-lock.yaml
    |-- pnpm-workspace.yaml
    |-- stylelint.config.js
    |-- tsconfig.json
    |-- vite.config.js
    |-- .changeset
    |   |-- README.md
    |   |-- config.json
    |-- .husky
    |   |-- commit-msg
    |   |-- pre-commit
    |   |-- _
    |       |-- .gitignore
    |       |-- applypatch-msg
    |       |-- commit-msg
    |       |-- h
    |       |-- husky.sh
    |       |-- post-applypatch
    |       |-- post-checkout
    |       |-- post-commit
    |       |-- post-merge
    |       |-- post-rewrite
    |       |-- pre-applypatch
    |       |-- pre-auto-gc
    |       |-- pre-commit
    |       |-- pre-push
    |       |-- pre-rebase
    |       |-- prepare-commit-msg
    |-- apps
    |   |-- .DS_Store
    |   |-- storybook
    |       |-- .DS_Store
    |       |-- .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
        |   |-- eslint
        |   |   |-- index.js
        |   |   |-- package.json
        |   |-- stylelint
        |   |   |-- index.js
        |   |   |-- package.json
        |   |-- typescript
        |       |-- base.json
        |       |-- package.json
        |-- web-components
            |-- text
                |-- index.ts
                |-- package.json
                |-- dist
                |   |-- index.js
                |   |-- index.umd.cjs
                |-- src
                    |-- main.css
                    |-- text.ts

4 组件测试

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

-F 是 --filter 的简写

会在 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 组件发布

这里以发布至 NPM 为例。

5.3.1 用户登录

如之前没有发布过,在项目根目录下运行:

pnpm login

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

5.3.2 组件发布

  1. 直接使用 PNPM,在项目根目录下运行:
pnpm publish -r

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

  1. 结合 Changeset 做发布
pnpm changeset

通过键盘上下键切换到要发布的库,按空格后回车
选择版本,版本数字增加的是第一个、第二个还是第三个,通过回车跳过,按空格选中。
最后填写版本修改说明。再依次执行:

pnpm changeset version

确定版本

pnpm changeset publish

发布版本

  • 18
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天下布武8

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值