公司动态
组件库管理 7 月实践总结:从混乱到自动化治理的四个环节
组件库管理 7 月实践总结从混乱到自动化治理的四个环节一、组件库失控的信号重复、断裂、没有文档组件库在一个月中经历了三次预警同一个按钮组件出现了四个变体分别放在common/Button.tsx、ui/PrimaryButton.tsx、shared/ActionButton.tsx和某个页面目录下的components/SubmitButton.tsx。它们做的事几乎一样但样式实现、Prop 接口、无障碍标注各不相同。第二个信号是设计稿和代码之间的断裂。设计系统里定义了color-primary-500和 16px 行高的文字组件而代码里的颜色是硬编码的#3B82F6行高多数是 1.5 而非 1.6。设计系统更新了一版色值开发要全局搜索替换。漏掉的几处就在线上呈现出微妙的色差。第三个信号是文档缺失。180 个组件中只有 23 个有 Storybook 示例。新组件开发时开发者找不到已有组件的使用方式只能再写一个。这又加剧了重复。二、治理第一步组件登记与去重的自动化治理的第一步是搞清楚到底有多少组件。写了扫描脚本遍历所有.tsx文件提取导出的组件名、文件路径、Props 接口定义生成一份组件清单。import { parse } from typescript-eslint/parser; import { readFileSync } from fs; import { glob } from glob; interface ComponentEntry { name: string; filePath: string; props: string[]; exported: boolean; category: string; } async function scanComponents(rootDir: string): PromiseComponentEntry[] { const files await glob(${rootDir}/**/*.{tsx,jsx}, { ignore: [**/node_modules/**, **/*.test.*, **/*.stories.*], }); const entries: ComponentEntry[] []; for (const file of files) { const code readFileSync(file, utf-8); const ast parse(code, { range: true, comment: true, }); // 遍历 AST提取 export 的 React 组件 for (const node of ast.body) { if (isComponentExport(node)) { entries.push({ name: extractComponentName(node), filePath: file, props: extractProps(node, code), exported: true, category: inferCategory(file), }); } } } return entries; }去重规则基于语义相似度而非字符串匹配。两个组件如果 Props 接口的重叠度超过 80% 且渲染类型一致都是按钮或都是输入框标记为潜在重复等待人工确认合并。组件登记的结果令人惊讶180 个组件中有 42 个被标记为潜在重复。其中按钮组件最多有 12 个变体分布在 8 个不同目录中。合并过程不是简单的选一个删其他的——每个变体都有特定的使用场景。最终只有 18 个被真正合并24 个因为功能差异被保留但规范了命名和目录位置。组件分类也是登记的重要输出。按功能维度划分为基础组件Button、Input、Select、布局组件Card、Grid、SplitPane、业务组件UserAvatar、PaymentForm、DocEditor三层。分类规范写入贡献指南新组件提交时 CI 自动校验目录位置是否正确。三、治理第二步文档的强制生成与 CI 关卡文档不能靠自觉。在 CI 中加入检查如果组件文件存在但没有对应的.stories.tsxCI 直接失败。# .github/workflows/storybook-check.yml name: Storybook Coverage Check on: pull_request: paths: - src/components/**/*.tsx jobs: check-coverage: runs-on: ubuntu-latest steps: - uses: actions/checkoutv4 - name: Check story coverage run: | npx check-story-coverage \ --components-dir src/components \ --stories-dir src/components \ --threshold 100对于已有组件但没有文档的情况利用 LLM 根据组件代码和 Props 类型自动生成 Storybook 模板。生成后的文档需要人工审阅一次才能合并但 LLM 已经把 80% 的框架代码写好了// AI 生成的 Story 模板人工审核后使用 import type { Meta, StoryObj } from storybook/react; import { Button } from ./Button; const meta: Metatypeof Button { title: Components/Button, component: Button, argTypes: { variant: { control: select, options: [primary, secondary, outline, ghost], }, size: { control: select, options: [sm, md, lg] }, disabled: { control: boolean }, }, }; export default meta; type Story StoryObjtypeof Button; export const Default: Story { args: { children: 按钮, variant: primary } }; export const Disabled: Story { args: { children: 禁用, disabled: true } }; export const Loading: Story { args: { children: 加载中, loading: true } };四、治理第三步设计 Token 同步的防断裂机制Token 同步是组件库治理的深水区。设计系统定义 Token前端代码引用 Token两端需要双向同步。建立了从 Figma 变量到 CSS 变量的自动同步管道。Figma 插件导出 Token JSON → CI 脚本校验 Token 变更 → 自动生成 TypeScript 类型文件 → 提交 PR。这个管道保证设计稿的任何色值、间距、圆角变更都能及时反映到代码中。// tokens/colors.ts — 自动生成勿手动编辑 // Generated from Figma: 2026-07-26T08:00:00Z export const colors { primary: { 50: #EFF6FF, 100: #DBEAFE, 500: #3B82F6, 900: #1E3A5F, }, neutral: { 50: #F9FAFB, 500: #6B7280, 900: #111827, }, } as const; export type ColorScale typeof colors;CI 脚本的核心校验逻辑对比新旧 Token 文件如果有 Token 被删除但仍在代码中被引用CI 拒绝合并并指出需要迁移的位置。Token 治理还需要处理暗色模式。明暗两个色值矩阵的同步逻辑复杂容易漏。最终的方案是设计师在 Figma 中分别定义明暗两套 Token管道自动生成为 CSS 变量暗色模式通过prefers-color-scheme媒体查询或.darkclass 切换无需前端代码做任何条件判断。在实践中还发现一个隐性成本设计师改了 Token 名称如primary-500改名为brand-500代码中直接引用的变量名不会同步更新导致构建虽然通过但视觉样式丢失。解决方式是在 CSS 变量生成时保留旧名称的别名映射给开发者一个过渡期来完成代码迁移同时 CI 脚本在过渡期结束后自动清理过期别名。/* 自动生成的 CSS 变量文件 */ :root { --color-primary-500: #3B82F6; --color-brand-500: var(--color-primary-500); /* 别名过渡期 */ }过渡期设置为 30 天。30 天后如果别名仍存在CI 会发出警告但不会阻塞构建。等到所有引用都已迁移手工删除别名。这个缓冲机制避免了设计改一个色值前端挂一个构建的尴尬状态。五、总结组件库治理的四个核心环节组件登记与去重自动化、文档强制生成与 CI 关卡、设计 Token 的双向同步管道、暗色模式的 Token 矩阵管理。治理不是一次性的动作而是嵌入日常流程的自动化管道。扫描、校验、生成、报告每个环节都有对应的脚本或 CI 检查。组件库的健康状态应该像 CI 状态一样一眼可见。当治理成本低于混乱成本时自动化投入就完成了它的使命。