Skip to content

从零到一搭建 React UI 组件库

目标:基于 pnpm Monorepo + Gulp 构建体系,搭建一套可发布、可扩展、可主题化、支持多种模块规范 (ESM / CJS / UMD)、支持按需加载与多语言 / 多主题的企业级 React 移动端组件库工程体系。

1. 总览:组件库工程的核心诉求

诉求关键点
多包协同共享基础配置、类型、工具、主题变量
多模块产物esm / cjs / 按需加载 / umd 独立文件
样式体系Less 变量主题化 + 构建时变量替换 + 平行输出 less 与 css
资源处理图片 / 字体 / 字体图标 / 代码示例资源集中策略
Tree ShakingESM 纯函数式输出 + sideEffects 精准声明
类型支持完整 d.ts、跳转友好、二次封装友好
主题切换modifyVars + 动态注入(多主题包 / 运行时切换)
按需加载babel-plugin-import / 自研插件 + 目录规范
文档 & DemoMDX + Playground + 在线交互 + 快速本地预览
质量体系Lint / Commit 校验 / 单测 / 视觉回归(可选)
发布语义化版本 & CHANGELOG 自动化

2. Monorepo:使用 pnpm 构建多包架构

2.1 目录结构范例

text
/ (repo root)
  pnpm-workspace.yaml
  package.json             # 根依赖(dev 工具、脚本)
  tsconfig.json            # 根 TS 基础配置(references/shared)
  packages/
    brick-mobile/          # 主组件库包(对外发布 @scope/brick-mobile)
      package.json
      src/ (或 packages/ 子目录拆功能域)
      styles/
      themes/
      utils/
      locales/
      demos/
    themes/                # 可选:额外主题扩展包
    codemod/               # 可选:升级脚本(AST 转换)
    eslint-plugin/         # 可选:自定义 lint 规则
  docs/                    # 文档站点(React + MDX)
  scripts/                 # 辅助自动化脚本(生成模板、批量重写等)

2.2 pnpm-workspace.yaml

yaml
packages:
  - 'packages/*'
  - 'packages/**'
  - 'scripts'
  - 'docs'

2.3 多包之间的协作

  • 使用 workspace:* 版本指向本地包。
  • 通过 root devDependencies 统一锁工具版本(ts, jest, eslint 等),包内只写 peerDependencies + runtime deps。
  • 推荐在根添加 changeset / release-it 进行版本管理。

2.4 TypeScript Project References(可选)

  • tsconfig.json:定义 compilerOptionsreferences
  • 各子包 tsconfig.jsoncomposite: true,利于增量编译。

3. 构建体系:为什么选 Gulp + Babel + Webpack (UMD)

模块作用
Gulp任务编排(流式遍历源码)+ 并行构建 + 细粒度控制
Babel转译 TS/JSX -> JS(无需 tsc 发包 JS,tsc 仅生成类型)
Webpack仅用于打 UMD 单文件,提供外部依赖 external、压缩、banner
Less + PostCSS主题变量 + autoprefixer + 最终产物 css/less 双输出
资源处理小体积 base64 / 大体积复制

也可以选择 tsup / vite build / rollup 等一体化方案;此处展示“手工组合”利于理解细节与完全掌控产物形态。

4. Gulp 任务设计拆解

典型分层:

  1. clean:清理 dist。
  2. build:esm:Babel 输出 ESM(保留 import)。
  3. build:cjs:Babel 输出 CJS(转成 require)。
  4. build:styles:Less -> css + 拷贝 less 源文件。
  5. build:assets:图片 / 字体复制或内联。
  6. build:types:调用 tsc 仅生成 d.ts。
  7. build:umd:Webpack 打包入口为聚合 index。
  8. build:copy-meta:复制 README、LICENSE、package.json(精简字段)。
  9. watch:监听组件源码增量重建(开发体验)。
  10. default / parallel:组合并行构建(esm 与 cjs 可并行)。

4.1 Babel 配置重点

.babelrc.js 示例要点:

  • presets:@babel/preset-env (modules: false for esm) + @babel/preset-react + @babel/preset-typescript
  • plugins:
    • @babel/plugin-transform-runtime(减少重复 helper)。
    • babel-plugin-import(为使用者提供按需加载支持,也可分发二次编译)。
    • 按需剥离 dev 代码(自定义 plugin 处理 __DEV__ 条件)。

分别产出:

  • ESM:env.modules = false,保留 ES Module,利于 tree-shaking。
  • CJS:覆盖配置 env.modules = 'commonjs'

4.2 ESM / CJS 输出目录规划

text
dist/
  esm/            # *.js + *.less + *.css + assets
  cjs/
  umd/
  types/          # d.ts

4.3 UMD 构建(Webpack)说明

  • 入口:src/index.ts(聚合导出)。
  • externals:react, react-domdayjs 等,防止打包进产物。
  • output:library: { name: 'BrickMobile', type: 'umd' }
  • mode: production。
  • devtool: source-map(可单独关)。
  • banner:版本号 + 构建时间。
  • terser:保持类名(可选)+ 去除 console(配置可开关)。

4.4 样式平行输出

任务:

  1. 源 less 文件原样复制 -> 允许二次定制。
  2. Less 编译 -> css。再经 PostCSS(autoprefixer、cssnano)。
  3. 生成 dist/esm/{component}/style/index.less|css & 同步 cjs。
  4. 入口聚合:在组件包根生成 style/index.js 便于 import 'xxx/es/button/style'

4.5 资源文件(图片 / 字体)处理策略

  • 小于阈值(例如 8 KB) -> base64 内联 (自定义 gulp 插件或使用 gulp-css-base64)。
  • 大文件:复制到 dist/assets/,保留相对路径引用。
  • SVG:
    • 作为 React 组件(svgr) -> Icon 系统。
    • 作为背景图:仍复制。

4.6 构建性能优化

  • gulp-cache / gulp-changed 增量处理。
  • watch 时:只对变更文件执行对应流水线。
  • 限制并发(小心文件句柄上限),或利用 Promise.all 控制批次。

5. 主题体系:Less + modifyVars

5.1 基本思想

  • 定义一份基础主题变量:themes/default.less
  • 不同主题(dark / compact 等)只改差异变量:themes/dark.less
  • 组件 less 中只引用变量,不写硬编码色值。

5.2 构建时变量替换

两种策略:

  1. 多主题多份 css:循环主题列表,分别调用 less 编译注入 modifyVars 输出:
    • dist/themes/default.css
    • dist/themes/dark.css
  2. 运行时动态主题:输出未编译变量的 less,业务端二次调用 less.js(或 CSS-in-JS)动态替换;移动端一般倾向策略 1 保证性能。

5.3 动态切换(可选增强)

  • 通过 <link id="brick-theme" href="...dark.css" /> 动态替换。
  • 或 CSS Variables:构建阶段将 Less 变量映射到 :root { --color-primary: #... },JS 切换时替换 root class。

5.4 变量溢出控制

  • 变量命名规范:@color-primary, @font-size-sm,统一前缀以避免冲突。
  • 输出一份 dist/theme.d.ts(利用模板生成),为 TS 中使用提供类型约束(例如在运行时注入时校验)。

6. 按需加载(Import on Demand)

6.1 目录组织

text
packages/brick-mobile/
  packages/
    button/
      index.ts        # 导出核心组件
      style/index.less
      style/index.ts  # 引入样式副作用 import './index.less'

6.2 使用者侧调用模式

ts
import { Button } from '@scope/brick-mobile';          // 聚合(带 tree-shaking)
import Button from '@scope/brick-mobile/es/button';    // 直接指向单组件(更低打包体积)
import '@scope/brick-mobile/es/button/style';          // 单组件样式

6.3 Babel 插件方案

  • 使用 babel-plugin-import
    • libraryName: '@scope/brick-mobile'
    • libraryDirectory: 'es'
    • style: true(或传函数 => 自动附加 /style)。
  • 或自研插件:解析命名导入 -> 替换为对应路径 + 自动补样式。

6.4 sideEffects 配置

通过 package.json

json
"sideEffects": [
  "dist/esm/**/style/*",
  "dist/cjs/**/style/*"
]

确保样式副作用文件不被 tree-shaking 掉。

7. TypeScript 与声明文件

7.1 仅用 tsc 发 d.ts

  • tsc -p tsconfig.build.json --emitDeclarationOnly
  • 保持源码类型安全,运行时代码由 Babel 负责。

7.2 类型重导出聚合

src/index.ts 汇总:

ts
export type { ButtonProps } from './button';

确保使用者智能提示完整。

7.3 公共类型包(可选)

提取通用类型到独立 @scope/types,避免循环依赖。

8. 国际化 (i18n) 与本地化扩展

  • 结构:locales/en-US.ts, locales/zh-CN.ts 输出平面字典。
  • 暴露 ConfigProvider 注入上下文;组件消费 useLocale()
  • 构建保持原样输出,避免打包期替换,方便扩展语言。

9. 质量保障体系

9.1 Lint & Style

  • ESLint:React + TS + Hooks + Import 规则。
  • StyleLint:Less 书写规范(可选)。
  • Prettier:统一格式。
  • Husky + lint-staged:提交前自动修复。

9.2 Commit 规范

  • @commitlint/config-conventional:feat / fix / docs / perf / refactor...
  • 保证 CHANGELOG 自动生成(conventional-changelog / changesets)。

9.3 测试

  • 单测:Jest + React Testing Library。
  • 快照:组件结构。对交互用 fireEvent
  • 覆盖率阈值:--coverage + CI 失败门槛。
  • 可选:Storybook + Loki 做视觉回归。

9.4 CI/CD

  • GitHub Actions / GitLab CI:
    1. Install (pnpm fetch + install)
    2. Lint
    3. Test
    4. Build
    5. 发布(需手动触发或检测标签)

10. 发布与版本管理

10.1 语义化版本

  • 根据 commit 自动推断:feat -> minor, fix -> patch, BREAKING -> major。

10.2 产物检查清单

  • dist 目录大小
  • tree-shaking 验证(打一个使用示例 bundle 分析)
  • 类型完整性:tsc --noEmit 通过

10.3 多包发布脚本

  • 使用 changesets:集中管理版本 bump + 生成 CHANGELOG。
  • 或自定义脚本:读取变更 diff -> 计算版本。

11. 文档与示例体系

11.1 技术选型

  • Docusaurus / VitePress / Next.js + MDX。
  • Playground:嵌入 Sandpack / react-live 实时预览。

11.2 Demo 与测试复用

  • demos/ 下的示例源码既用于文档展示,也可 JSDOM/截图测试(复用一份)。
  • 构建脚本自动扫描 demo 生成导航侧边栏。

11.3 API 自动生成(可选)

  • 基于 TS AST (ts-morph) 解析组件 props -> 生成 API 表格 Markdown 注入文档。

12. 性能与包体优化策略

方向技术点
Tree Shaking仅使用 ESM 顶层导出,无运行时聚合对象
死代码消除process.env.NODE_ENV 条件 + Babel 插件擦除 dev 代码
按需加载结构规约 + babel-plugin-import
样式裁剪拆分组件样式,不做全量聚合(或同时提供一个 all.css)
依赖外部化UMD external,避免重复内嵌 React 等
体积分析webpack-bundle-analyzer / esbuild-analyze

13. 常见踩坑与规避

问题说明与解决
CJS 与 ESM 混用路径错误确保 exports 字段显式声明子路径映射
样式副作用被摇掉sideEffects 声明包含 style 目录
d.ts 路径不匹配设置 declarationDir 与构建目录结构一致
modifyVars 未生效确保 less 编译任务正确传递变量(javascriptEnabled: true
循环依赖拆解通用常量 / hooks / types 到独立层
按需样式重复引入建议由插件控制一次性聚合或让消费者选择模式
UMD 全量体积大external 最大依赖;避免内联所有 locale/主题(延迟加载)

14. package.json 关键字段示例(主包)

jsonc
{
  "name": "@scope/brick-mobile",
  "version": "1.0.0",
  "module": "dist/esm/index.js",
  "main": "dist/cjs/index.js",
  "types": "dist/types/index.d.ts",
  "unpkg": "dist/umd/brick-mobile.min.js",
  "jsdelivr": "dist/umd/brick-mobile.min.js",
  "sideEffects": [
    "dist/esm/**/style/*",
    "dist/cjs/**/style/*",
    "dist/esm/styles/*",
    "dist/cjs/styles/*"
  ],
  "exports": {
    "./package.json": "./package.json",
    ".": {
      "import": "./dist/esm/index.js",
      "require": "./dist/cjs/index.js",
      "types": "./dist/types/index.d.ts"
    },
    "./es/*": "./dist/esm/*",
    "./cjs/*": "./dist/cjs/*"
  },
  "peerDependencies": {
    "react": ">=17",
    "react-dom": ">=17"
  }
}

15. 增强:可插拔主题扩展包

  • 新建 @scope/brick-theme-x:仅包含变量覆盖 + 可选的额外样式。
  • 主包提供 ThemeRegistry:业务注入 registerTheme('x', url | vars)

16. Codemod 支持(演进升级)

  • 当组件 API 变更,通过 jscodeshift / ts-morph 编写迁移脚本,发布到 codemod 包。
  • 文档指引 npx @scope/codemod rename-prop Button oldProp newProp

17. 新组件脚手架自动化

脚本 scripts/generate/init-component.ts

  1. 询问组件英文名 / 中文名 / 分类。
  2. 生成目录:源码 + style + demo + test + README 片段。
  3. 自动写入聚合 index.ts
  4. 更新文档侧边栏 JSON / nav。

18. 安全与质量补充

  • 依赖审计:pnpm audit / oss-review-toolkit
  • License 合规:扫描第三方许可证,生成 NOTICE。
  • Bundle 运行时守卫:对外暴露 API 提供 TS 类型 + runtime propTypes(开发环境)。

19. 渐进式迁移其它构建工具(可选)

场景迁移建议
更快 TS + JS 构建可换 esbuild(编译)+ tsc d.ts
更少手工脚本father-build / dumi 等上层方案
需要 SSR 硬性支持在示例站点使用 Next.js,而库继续保持 ESM/CJS

20. 最终核对清单(Checklist)

项目是否完成说明
Monorepo 基础workspace + root scripts
构建脚本gulp + 并行任务
ESM / CJS / UMD三种产物齐全
Less 主题modifyVars + 多主题产物
按需加载babel-plugin-import 配置
d.tstsc emitDeclarationOnly
国际化视业务注入 locales
Lint & Testeslint + jest
文档站MDX + Playground
发布自动化changesets / conventional commits

21. 结语

通过上述体系,你可以:

  1. 保障产物形态完善(兼容不同消费环境)。
  2. 主题、按需、类型、国际化等关键能力开箱即用。
  3. 构建脚本可控,可针对性能 / 体积继续精细优化。
  4. 具备演进与团队协作的长周期可维护性。

建议先最小可行版本(核心构建 + 一个组件 + 按需样式),再增量引入主题 / 国际化 / codemod / CI,以降低一次性复杂度。