从零到一搭建 React UI 组件库
目标:基于 pnpm Monorepo + Gulp 构建体系,搭建一套可发布、可扩展、可主题化、支持多种模块规范 (ESM / CJS / UMD)、支持按需加载与多语言 / 多主题的企业级 React 移动端组件库工程体系。
1. 总览:组件库工程的核心诉求
| 诉求 | 关键点 |
|---|---|
| 多包协同 | 共享基础配置、类型、工具、主题变量 |
| 多模块产物 | esm / cjs / 按需加载 / umd 独立文件 |
| 样式体系 | Less 变量主题化 + 构建时变量替换 + 平行输出 less 与 css |
| 资源处理 | 图片 / 字体 / 字体图标 / 代码示例资源集中策略 |
| Tree Shaking | ESM 纯函数式输出 + sideEffects 精准声明 |
| 类型支持 | 完整 d.ts、跳转友好、二次封装友好 |
| 主题切换 | modifyVars + 动态注入(多主题包 / 运行时切换) |
| 按需加载 | babel-plugin-import / 自研插件 + 目录规范 |
| 文档 & Demo | MDX + 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:定义compilerOptions与references。 - 各子包
tsconfig.json:composite: 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 任务设计拆解
典型分层:
- clean:清理 dist。
- build:esm:Babel 输出 ESM(保留
import)。 - build:cjs:Babel 输出 CJS(转成
require)。 - build:styles:Less -> css + 拷贝 less 源文件。
- build:assets:图片 / 字体复制或内联。
- build:types:调用 tsc 仅生成 d.ts。
- build:umd:Webpack 打包入口为聚合 index。
- build:copy-meta:复制 README、LICENSE、package.json(精简字段)。
- watch:监听组件源码增量重建(开发体验)。
- 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.ts4.3 UMD 构建(Webpack)说明
- 入口:
src/index.ts(聚合导出)。 - externals:
react,react-dom、dayjs等,防止打包进产物。 - output:
library: { name: 'BrickMobile', type: 'umd' }。 - mode: production。
- devtool:
source-map(可单独关)。 - banner:版本号 + 构建时间。
- terser:保持类名(可选)+ 去除 console(配置可开关)。
4.4 样式平行输出
任务:
- 源 less 文件原样复制 -> 允许二次定制。
- Less 编译 -> css。再经 PostCSS(autoprefixer、cssnano)。
- 生成
dist/esm/{component}/style/index.less|css& 同步 cjs。 - 入口聚合:在组件包根生成
style/index.js便于import 'xxx/es/button/style'。
4.5 资源文件(图片 / 字体)处理策略
- 小于阈值(例如 8 KB) -> base64 内联 (自定义 gulp 插件或使用
gulp-css-base64)。 - 大文件:复制到
dist/assets/,保留相对路径引用。 - SVG:
- 作为 React 组件(svgr) ->
Icon系统。 - 作为背景图:仍复制。
- 作为 React 组件(svgr) ->
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 构建时变量替换
两种策略:
- 多主题多份 css:循环主题列表,分别调用 less 编译注入
modifyVars输出:dist/themes/default.cssdist/themes/dark.css
- 运行时动态主题:输出未编译变量的 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:
- Install (pnpm fetch + install)
- Lint
- Test
- Build
- 发布(需手动触发或检测标签)
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:
- 询问组件英文名 / 中文名 / 分类。
- 生成目录:源码 + style + demo + test + README 片段。
- 自动写入聚合
index.ts。 - 更新文档侧边栏 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.ts | ✅ | tsc emitDeclarationOnly |
| 国际化 | ☐ | 视业务注入 locales |
| Lint & Test | ✅ | eslint + jest |
| 文档站 | ✅ | MDX + Playground |
| 发布自动化 | ✅ | changesets / conventional commits |
21. 结语
通过上述体系,你可以:
- 保障产物形态完善(兼容不同消费环境)。
- 主题、按需、类型、国际化等关键能力开箱即用。
- 构建脚本可控,可针对性能 / 体积继续精细优化。
- 具备演进与团队协作的长周期可维护性。
建议先最小可行版本(核心构建 + 一个组件 + 按需样式),再增量引入主题 / 国际化 / codemod / CI,以降低一次性复杂度。