🌈从0到1,基于dumi2.0 + monorepro + pnpm 搭建一个自己的组件库

2,755 阅读10分钟

背景

当一定的技术沉淀,不再想做流水线的工作,看了很多文章和源码之后,终于打算搭建一个属于自己的组件库了,该文章从0到1搭建,本文用于记录和分享一下自己搭建和踩坑的一些过程,后面会把参考的文章和仓库分享出来,大家一起冲冲冲!

仓库地址

在线预览

喜欢的朋友可以帮忙点个start🌟,非常感谢

1. 技术选型

  • dumi 最开始想选择 storybook 来搭建,后面发现蚂蚁金服新出的 dumi 更轻量且更便捷,最后选择采取dumi来做组件库预览的搭建
  • monorepro 可以把多个工程放到一个 git 仓库中进行管理,他们共享同一套构建流程、代码规范也可以做到统一,特别是如果存在模块间的相互引用的情况,查看代码、修改bug、调试等会更加方便
  • pnpm 更节约磁盘空间并提升安装速度和创建非扁平化的 node_modules 文件夹,具体原理可看神说有光大神的解析

2. 组件库架构搭建

初始化

首先进入创建好的文件夹,进行pnpm和git的初始化:

mkdir perfect-design
cd perfect-design
npm install -g pnpm
pnpm init

新建packages文件夹:

image.png

新建pnpm-workspace.yaml文件,里面配置工作目录:

image.png

定义开发规范

采用 eslint 做代码检查规范,这里 -w 的意思是需要在根项目,也就是在最外层进行安装才行,不然会报错

pnpm i eslint -D -w
npx eslint --init

新建 .eslintrc.json ,里面加入 eslint 配置:

{
        "env": {
                "browser": true,
                "es2021": true,
                "node": true
        },
        "extends": [
                "eslint:recommended",
                "plugin:@typescript-eslint/recommended",
                "prettier",
                "plugin:prettier/recommended"
        ],
        "parser": "@typescript-eslint/parser",
        "parserOptions": {
                "ecmaVersion": "latest",
                "sourceType": "module"
        },
        "plugins": ["@typescript-eslint", "prettier", "react"],
        "rules": {
                "prettier/prettier": "error",
                "no-case-declarations": "off",
                "no-constant-condition": "off",
                "@typescript-eslint/ban-ts-comment": "off",
                "@typescript-eslint/no-var-requires": 0
        }
}

新建 .gitignore 忽略 node_modules 的 git 控制:

image.png

安装 ts 和 ts 的 eslint 插件:

pnpm i -D -w @typescript-eslint/eslint-plugin typescript

安装 prettier 进行代码格式化 Tips:需要配合 vscode 插件 prettier 配置保存格式化一起使用

pnpm i prettier -D -w

新建 .prettierrc.json 文件加入配置,这里我不喜欢分号就去掉了,不习惯的可以把 semi 设为true

{
  "printWidth": 80,
  "tabWidth": 2,
  "useTabs": true,
  "singleQuote": true,
  "semi": false,
  "trailingComma": "none",
  "bracketSpacing": true
}

prettier集成到eslint中,其中:

  • eslint-config-prettier:覆盖ESLint本身的规则配置.
  • eslint-plugin-prettier:用Prettier来接管修复代码即eslint --fix.

安装 prettier 集成 eslint 的插件:

pnpm i eslint-config-prettier eslint-plugin-prettier -D -w

lint增加对应的执行脚本,并验证效果:

"lint": "eslint --ext .js,.ts,.jsx,.tsx --fix --quiet ./packages"

安装 lint-staged 相比eslint,它只做暂存区的代码校验,在提交速度上会更快:

pnpm i lint-staged -D -w

在package.json中在添加多一个gitHooks的配置:

"gitHooks": {
        "pre-commit": "lint-staged"
}

安装 stylelint 和 prettier 插件 校验 css 代码格式:

pnpm i stylelint stylelint-config-prettier stylelint-config-standard -D -w

在 package.json 中添加script命令:

"stylelint": "stylelint {packages,.dumi/theme}/**/*.less --fix"

安装 pretty-quick 在提交commit时自动调用 pretter 进行格式化:

pnpm i pretty-quick -D -w

把以上所有安装好的校验配置合并在一个script命令上:

"pre-commit": "pretty-quick --staged && pnpm eslint && pnpm stylelint"

git 允许在各种操作之前添加一些 hook 脚本,如未正常运行则 git 操作不通过。 最出名的是以下两个

  • precommit
  • prepush

husky 可以通过配置,把 npm scripts 写入其中的方式来实现自定义脚本位置

安装 husky 并进行初始化,用于拦截commit命令:

pnpm i husky -D -w
npx husky install

把刚刚在 script 标签上集成的校验命令配置到 husy 的 pre-commit 上,它会在 git 提交的时候运行script标签定义好的命令去做代码校验:

 npx husky add .husky/pre-commit "pnpm pre-commit"

安装 commitlintgit提交信息进行检查,首先安装必要的库:

pnpm i commitlint @commitlint/cli @commitlint/config-conventional -D -w

新建配置文件.commitlintrc.js

module.exports = {  
  extends: ["@commitlint/config-conventional"]  
}

把装好的 commitlintrc 集成到husky中:

npx husky add .husky/commit-msg "npx --no-install commitlint -e $HUSKY_GIT_PARAMS"

这里使用conventional规范集意义,是规范代码提交格式,每个提交的功能必须对应备注:

  • feat: 添加新功能
  • fix: 修复 Bug
  • chore: 一些不影响功能的更改
  • docs: 专指文档的修改
  • perf: 性能方面的优化
  • refactor: 代码重构
  • test: 添加一些测试代码等等

如新增了一个功能,在提交的时候如下规范写入,冒号后面必须得加一个空格,否则会报错

git commit -m 'feat: 项目框架搭建'

新建tsconfig.json,加入如下配置:

{
        "compilerOptions": {
                "skipLibCheck": true,
                "esModuleInterop": true,
                "baseUrl": "./",
                "target": "es6",
                "module": "es2015",
                "lib": ["dom", "dom.iterable", "esnext", "es2021"],
                "allowJs": true,
                "allowSyntheticDefaultImports": true,
                "strict": false,
                "forceConsistentCasingInFileNames": true,
                "moduleResolution": "node", //node环境
                "resolveJsonModule": true,
                "isolatedModules": true,
                "noEmit": true,
                "jsx": "react",
                "experimentalDecorators": true
        },
        "exclude": ["node_modules", "lib"]
}

自此整个项目的框架就搭建的差不多了,基本上大部分的项目初始结构都可以运用上面这一套组合拳进行搭建,后面开始搭建 dumi 集成的组件库

3. 文件库搭建

安装 dumi 和需要的 react 和删除打包文件的 rimraf 库 :

pnpm i dumi server -D -w
pnpm i react react-dom rimraf -D -w

配置 script 启动和打包命令:

"start": "dumi dev",
"build:docs": "rimraf docs-dist && dumi build",

在 packages 中新建 perfect-design 文件夹,cd到里面进行 pnpm init,然后再创建一个 src 的文件夹,在 src 内在新建 components 文件夹,我们后续开发的组件都将放在这个目录下

image.png

⚠️注意:dumi 默认路径是在最外层的 src 为主要路径,我们这边采用多包管理的方式,需要在 .dumirc.ts 配置resolve 下的 atomDirs 数组修改匹配路径

新建 .dumirc.ts 配置文件:

import { defineConfig } from 'dumi'
import path from 'path'

let base: string | undefined
let publicPath: string | undefined

if (process.env.PREVIEW !== '1') {
        base = '/perfect-design/' // 后续部署到gh-pages如果不配置路径会找不到资源
        publicPath = '/perfect-design/'
}

export default defineConfig({
        base,
        publicPath,
        title: 'Perfect Design', // 站点名称
        outputPath: 'docs-dist', // 输出文件夹
        resolve: {
                docDirs: ['docs'],
                atomDirs: [
                        // 在这里修改components的匹配路径
                        { type: 'component', dir: '/packages/perfect-design/src/components' }
                ],
                codeBlockMode: 'passive'
        },
        alias: {
                perfectD: path.join(__dirname, 'packages/perfect-design/src') // 配置引入别名
        },
        themeConfig: {
                name: 'Perfect D',
                carrier: 'dumi', // 设备状态栏左侧的文本内容
                hd: true,
                rtl: true,
                nav: [ // dumi的菜单路由
                        {
                                title: '指南',
                                link: '/guide'
                        },
                        {
                                title: '组件',
                                link: '/components/alert'
                        }
                ]
        }
})

(!!注意!!: dumi有多种写文档的方式,一种是在 docs 上面编写 md,然后匹配文件进行菜单渲染,我们这采用的是上面👆那种模式,使用 nav 配置菜单路由,这样可以更加方便。除此之外还有自定义路由、约定式路由等,更多编写方式可以去官网dumi翻看阅读哈)

在根目录新建 docs 文件夹,新建 index.md 文件,在这里就可以编写你的主页文档啦!

---
title: Perfect Design
hero:
  title: Perfect Design
  desc: 文档站点基于 dumi 生成
  actions:
    - text: 快速上手
      link: /guide
features:
  - emoji: 🚀
    title: 更好的编译性能
    description: 无敌的雅痞
  - emoji: 🔍
    title: 内置全文搜索
    description: 好看又实用啊
  - emoji: 🎨
    title: 全新主题系统
    description: 不是吧不是吧
footer: Open-source MIT Licensed | Copyright © 2020<br />Powered by [dumi](https://d.umijs.org)
---

然后再新建一个 guide.md 文件,写入以下内容,dumi会根据文件名称和你在 .dumirc配置的link路由进行匹配:

---
nav:
  title: 快速上手
  order: 1
---

# 快速上手

## 安装

**使用 npm / yarn / pnpm 安装**

```shell
npm install @k-maikou/perfect-design
```

```shell
yarn add @k-maikou/perfect-design
```

```shell
pnpm install @k-maikou/perfect-design
```

## 示例

```js
import Alert from '@k-maikou/perfect-design/es/alert' // 手动按需加载 js
import '@k-maikou/perfect-design/es/alert/style' // 手动按需加载 less

ReactDOM.render(<Alert kind="warning">这是一条警告提示</Alert>, mountNode)
```

### 自动按需加载

使用 [babel-plugin-import ](https://www.npmjs.com/package/babel-plugin-import) 优化引入方式,如下:

```js
import { Alert } from '@k-maikou/perfect-design' // 与上述示例等价

ReactDOM.render(<Alert kind="warning">这是一条警告提示</Alert>, mountNode)
```

安装 `babel-plugin-import`

```
yarn add babel-plugin-import --dev
```

配置`.babelrc`  `babel-loader`

```json
{
        "plugins": [
                [
                        "import",
                        {
                                "libraryName": "@k-maikou/perfect-design",
                                "libraryDirectory": "esm", // default: lib
                                "style": true // or 'css'
                        }
                ]
        ]
}
```

这个时候我们启动 pnpm start 就能看到我们的页面啦!!

image.png

image.png

4. 组件实现

这里以实现最简单的 Alert 组件为例,我们在 packages/src/components 目录下新建 Alert 文件夹,新建 index.tsx,里面实现我们的 Alert 组件,这里的实现用到了 CSSTransition 和 styled-components,代码量较多,具体实现可以去我的仓库地址查看哈。

然后在 src/index.ts 内把写好的组件通过 export { default as Alert } from './components/Alert' 统一导出

image.png

新建 style 文件夹,为了方便后面的按需加载,采用导出 index.tsx 的方式来写样式文件:

image.png

新建 demo 文件夹,把我们写好的功能放在 index 里面,后续文档会根据写的内容进行展示:

(这里的直接引入 perfectD 是在.dumirc.ts文件内配置 alias,然后在packages目录下的项目新建 typings.d 文件,通过声明 declare module 'perfectD',就可以项目内使用了)

image.png

然后新建 index.md, 把我们实现的功能 api 以文档的形式写上去,然后用 code 标签导入我们写在 demo 文件夹里的index文件,我们在 nav 上配置了菜单路由,dumi默认会根据 componsnts 下组件的 md 文件进行展示:

---
title: Alert 警告提示
nav:
  title: 组件
  path: /common
group:
  title: 反馈
mobile: false
toc: content
---

# Alert 警告提示

向用户显示警告的信息时,通过警告提示,展现需要关注的信息。

## 基本使用

警告提示,展现需要关注的信息,适用于简短的警告提示。

<code src="./demo/index.tsx"></code>

## API

| Name         | Description      | Type                               | Default |
| ------------ | ---------------- | ---------------------------------- | ------- |
| style        | 自定义样式       | `CSSProperties`                    | `--`    |
| className    | 类名             | `string`                           | `--`    |
| type         | 类型             | `info / success / warning / error` | `info`  |
| title        | 显示标题         | `string`                           | `''`    |
| content      | 警告文字         | `string`                           | `''`    |
| showClear    | 显示清除按钮     | `boolean`                          | `false` |
| showIcon     | 显示警告提示图标 | `boolean`                          | `true`  |
| closeElement | 自定义关闭图标   | `React.ReactNode / string`         | `--`    |
| onClose      | 关闭回调函数     | `Function`                         | `--`    |

然后就能看到我们的组件展示在页面上啦!!

image.png

5. 部署项目到 git hub 静态服务器

我们登陆 github 的账号,在 setting 处新建我们的密钥 token, 记得及时复制下来,下次进来就会看不到了

image.png

然后来到我们的仓库,点击setting

image.png

点击我们的Environments,把我们的密钥添加进去

image.png

github 部署有很多种库可以使用,我们这使用 gh-pages 来帮我们一键部署:

   pnpm i gh-pages -D -w
   

添加部署命令,这里再打包之前会先删除之前的缓存,然后重新打包成 docs-dist 文件,然后使用 gh-pages 命令:

"deploy": "rimraf node_modules/.cache/gh-pages && pnpm build:docs && gh-pages -d docs-dist"

注意:这里如果网络不好会出现下面这种错误👇

HTTP/2 stream 1 was not closed cleanly before end of the underlying stream

这里我们把 github 默认的 HTTP/2 协议修改为 HTTP1.1 就行

git config --global http.version HTTP/1.1
git config --global http.postBuffer 524288000

启用 pnpm deploy,会在新建并推送到远程分支 gh-pages 上

image.png

然后点击我们的 github action 就可以看到我们的页面出来啦!

image.png

最后

看似简单的组件库,也花了我好几天的时间,一步一步摸索,希望能帮助各位大佬少走点坑

以下为参考文献和仓库,也是看着他们一步一步来滴:

一个掘金博主写的组件库搭建,后面跑去github更新了

开源的concis组件库,写的非常的不错,里面有个埋点监控的组件挺好用的

卡颂老师的从0实现React18,最开始的项目搭建就是参考的这里的