一套优秀的组件库不仅需要良好的 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 构建。这些经验对于构建任何规模的组件库都有参考价值。