组件库设计指南:从 Token 系统到 Tree Shaking
一套优秀的组件库不仅需要良好的 API 设计,还需要完善的主题系统与极致的构建优化。本文总结了我在重构团队内部 UI 组件库 v3 时的设计决策与实践经验。
为什么要重构?
组件库 v2 存在几个突出问题:
- 样式方案混乱:部分组件用 SCSS,部分用 CSS-in-JS,维护成本高
- 主题定制困难:颜色、间距等设计值散落在各处,修改需要逐个查找替换
- Bundle 体积臃肿:即使只用了一个按钮,也会打包整个组件库
基于这些痛点,我们决定从零重构 v3 版本。
Design Token 系统
Token 系统是组件库的「DNA」,它将设计决策(颜色、字体、间距、圆角等)抽象为可配置的变量,贯穿所有组件。
Token 分层
我们采用三层 Token 结构:
- Global Tokens:原始设计值,如
--color-blue-500: #3b82f6 - Alias Tokens:语义化别名,如
--color-primary: var(--color-blue-500) - Component Tokens:组件级别的 Token,如
--button-bg: var(--color-primary)
这种分层设计使得主题切换变得非常简单——只需覆盖 Alias Token 层,所有组件自动跟随变化。暗色模式的实现也是如此。
暗色模式支持
通过 CSS 变量 + data-theme="dark" 属性选择器,实现了零 JS 运行时开销的暗色模式:
[data-theme="dark"] {
--color-bg: #0f172a;
--color-text: #f1f5f9;
--color-primary: #60a5fa;
/* ... */
}
Tree Shaking 优化
为了让组件库支持 Tree Shaking,我们从模块设计和构建配置两方面入手。
模块化设计
每个组件是独立的模块,拥有自己的目录结构:
components/
Button/
index.tsx # 组件实现
styles.ts # 样式
types.ts # TypeScript 类型
__tests__/ # 单元测试
Input/
index.tsx
styles.ts
types.ts
组件通过各自的 index.tsx 导出,顶层入口文件只做聚合导出:
// components/index.ts
export { Button } from './Button';
export { Input } from './Input';
export { Select } from './Select';
构建配置
使用 Rollup 进行构建,配置 treeshake: true,并确保:
- package.json 中设置
"sideEffects": false - 使用 ES Module 格式输出
- 避免在模块顶层执行副作用代码
最终效果:使用 Button 组件时,打包体积从 180KB 降低到 8KB。
文档与开发体验
好的组件库需要好的文档。我们使用 Storybook 搭建了组件文档站:
- 每个组件都有交互式的 Playground,可以实时调整属性
- API 文档从 TypeScript 类型自动生成,保持与代码同步
- 支持暗色模式预览,方便调试主题适配
迁移策略
从 v2 迁移到 v3 是一个渐进的过程。我们采取了以下策略:
- 提供 Codemod 工具自动转换 import 路径
- v2 组件通过适配层兼容,不需要一次性全部替换
- 编写详细的迁移指南,列出每个组件的 Breaking Changes
总结
组件库 v3 的重构带来了显著的收益:Bundle 体积减少 35%,主题定制能力大幅提升,开发体验明显改善。关键的设计决策包括:分层 Token 系统、模块化组件结构、以及基于 Rollup 的 Tree Shaking 构建。这些经验对于构建任何规模的组件库都有参考价值。