设计系统搭建:从 Token 体系到组件库自动化管理的工程实践
设计系统搭建从 Token 体系到组件库自动化管理的工程实践一、设计一致性困境当团队规模撞上样式碎片项目初期两三个开发者用 CSS 变量加一份共享样式文件就能维持界面一致性。但当团队扩展到十人以上、同时维护多个产品线时问题开始爆发不同项目各自定义了名为primary的颜色但色值不同间距命名从sm/md/lg到4/8/12各有一套组件 API 设计风格迥异导致跨项目复用几乎不可能。设计系统Design System的搭建正是为了解决这类规模化一致性问题。它不仅仅是一份 UI 规范文档而是一套从设计 Token 到组件代码、从版本发布到跨项目消费的完整工程体系。本文聚焦设计系统的工程化落地探讨 Token 体系设计、组件库自动化管理以及跨项目同步的实践方案。二、Design Token 体系与组件库自动化架构Design Token 是设计系统的原子层它将设计决策从具体的 CSS 属性中抽离出来形成平台无关的中间表示。组件库则是在 Token 之上构建的分子层消费 Token 并提供可复用的 UI 组件。flowchart LR subgraph 设计层 A[Figma 设计源] -- B[Token 定义br/JSON/YAML] end subgraph 转换层 B -- C[Style Dictionarybr/编译管线] C -- D1[CSS 变量] C -- D2[SCSS 变量] C -- D3[JS Token 对象] C -- D4[iOS/Android 原生] end subgraph 消费层 D1 -- E1[Web 组件库] D2 -- E1 D3 -- E2[JS 运行时主题切换] D4 -- E3[移动端组件库] end subgraph 自动化 F[CI Pipeline] --|Token 变更触发| C G[Changesets] --|版本管理| H[npm 发包] E1 -- H endToken 体系的设计需要分层全局 TokenGlobal Token定义最基础的值别名 TokenAlias Token赋予语义组件 TokenComponent Token绑定具体组件。这种三层结构确保了修改的局部性——调整品牌色只需修改全局 Token组件 Token 会通过别名链自动更新。组件库的自动化管理涵盖三个维度代码质量Lint 测试、版本发布Changesets 自动化 Changelog、跨项目同步npm 包 语义化版本。每一个维度的缺失都会导致组件库逐渐腐化最终被项目团队弃用。三、Token 编译管线与组件库自动化发布实现3.1 基于 Style Dictionary 的 Token 编译// token.config.ts —— Style Dictionary 编译配置 import StyleDictionary from style-dictionary; import { fileURLToPath } from url; import path from path; // 自定义 Transform将 Token 名称转为 CSS 变量命名规范 StyleDictionary.registerTransform({ name: name/kebab, type: name, transformer: (token) { // color.brand.primary → --color-brand-primary return token.path.join(-); }, }); // 自定义 Transform GroupWeb 平台完整转换组 StyleDictionary.registerTransformGroup({ name: custom/web, transforms: [ attribute/cti, name/kebab, time/seconds, content/icon, size/rem, color/css, ], }); // 自定义 Format生成带注释的 CSS 变量文件 StyleDictionary.registerFormat({ name: css/variables-with-comment, formatter: ({ dictionary }) { const lines dictionary.allTokens.map((token) { const comment token.comment ? /* ${token.comment} */ : ; return --${token.name}: ${token.value};${comment}; }); return /* Design Token - Auto Generated */\n:root {\n${lines.join(\n)}\n}; }, }); // 多平台编译配置 const config: StyleDictionary.Config { source: [tokens/**/*.json], platforms: { css: { transformGroup: custom/web, buildPath: dist/css/, files: [{ destination: tokens.css, format: css/variables-with-comment, options: { outputReferences: true }, }], }, js: { transformGroup: custom/web, buildPath: dist/js/, files: [{ destination: tokens.js, format: javascript/es6, }], }, scss: { transformGroup: custom/web, buildPath: dist/scss/, files: [{ destination: _tokens.scss, format: scss/variables, options: { outputReferences: true }, }], }, }, }; export default config;3.2 组件库自动化发布管线// scripts/release.ts —— 基于 Changesets 的自动化发布脚本 import { execSync } from child_process; import fs from fs; import path from path; interface PackageInfo { name: string; version: string; private: boolean; } class ComponentLibRelease { private packagesDir: string; private dryRun: boolean; constructor(packagesDir: string, dryRun false) { this.packagesDir packagesDir; this.dryRun dryRun; } /** 执行完整发布流程 */ async run(): Promisevoid { // Step 1: 代码质量检查 this.exec(npm run lint); this.exec(npm run typecheck); this.exec(npm run test -- --coverage); // Step 2: 构建 Token 和组件 this.exec(npm run build:tokens); this.exec(npm run build:components); // Step 3: 检查是否有待发布的 Changeset const hasChangeset this.hasPendingChangesets(); if (!hasChangeset) { console.log(没有待发布的 Changeset跳过发布); return; } // Step 4: 版本升级 this.exec(npx changeset version); // Step 5: 校验版本号一致性组件库对 peer 依赖版本敏感 this.validatePeerDependencies(); // Step 6: 发布 if (this.dryRun) { console.log([Dry Run] 将执行 npm publish); this.exec(npx changeset publish --dry-run); } else { this.exec(npx changeset publish); // 发布后创建 Git Tag this.exec(git push --follow-tags); } } private hasPendingChangesets(): boolean { const dir .changeset; if (!fs.existsSync(dir)) return false; const files fs.readdirSync(dir) .filter(f f ! config.json f.endsWith(.md)); return files.length 0; } /** 校验组件包之间的 peer 依赖版本是否对齐 */ private validatePeerDependencies(): void { const packages this.getPackages(); const versionMap new Mapstring, string(); // 收集所有包的当前版本 for (const pkg of packages) { versionMap.set(pkg.name, pkg.version); } // 校验 peer 依赖引用的版本是否与实际发布版本一致 for (const pkg of packages) { const pkgJson this.readPackageJson(pkg.name); const peers pkgJson.peerDependencies ?? {}; for (const [dep, versionRange] of Object.entries(peers)) { const actualVersion versionMap.get(dep); if (!actualVersion) continue; // 简化校验检查主版本号是否匹配 const majorFromRange (versionRange as string).replace(/[^0-9]/g, ).charAt(0); const majorActual actualVersion.split(.)[0]; if (majorFromRange majorFromRange ! majorActual) { throw new Error( ${pkg.name} 的 peer 依赖 ${dep}${versionRange} 与实际版本 ${actualVersion} 主版本号不匹配 ); } } } } private getPackages(): PackageInfo[] { const dirs fs.readdirSync(this.packagesDir); return dirs .map(dir { const pkgPath path.join(this.packagesDir, dir, package.json); if (!fs.existsSync(pkgPath)) return null; const pkg JSON.parse(fs.readFileSync(pkgPath, utf-8)); return { name: pkg.name, version: pkg.version, private: pkg.private ?? false, }; }) .filter((p): p is PackageInfo p ! null !p.private); } private readPackageJson(name: string): any { const dirs fs.readdirSync(this.packagesDir); for (const dir of dirs) { const pkgPath path.join(this.packagesDir, dir, package.json); if (!fs.existsSync(pkgPath)) continue; const pkg JSON.parse(fs.readFileSync(pkgPath, utf-8)); if (pkg.name name) return pkg; } throw new Error(包 ${name} 未找到); } private exec(command: string): void { console.log( ${command}); execSync(command, { stdio: inherit }); } } // 执行入口 const release new ComponentLibRelease( path.resolve(process.cwd(), packages), process.argv.includes(--dry-run) ); release.run().catch((err) { console.error(发布失败:, err.message); process.exit(1); });四、设计系统落地的现实阻力与取舍设计系统的搭建只是第一步真正的挑战在于持续运营和跨团队推广。Token 与代码的同步鸿沟设计在 Figma 中更新了 Token但代码中的 CSS 变量没有同步更新导致设计与实现再次偏离。解决方案是将 Token 定义从 Figma 导出为 JSON通过 CI 管线自动编译为各平台产物消除人工搬运环节。但这要求设计团队接受Token 是代码的一部分这一理念对设计工作流是一次重构。组件库的版本碎片化多个项目消费同一组件库的不同大版本Bug 修复需要在多个分支上重复提交。语义化版本约束了兼容性但无法消除碎片化本身。实践中需要在 Breaking Change 时提供 codemod 自动迁移工具降低项目升级成本。过度设计的风险设计系统容易陷入大而全的陷阱——试图覆盖所有场景的 Token 和组件结果维护成本远超收益。建议从核心场景颜色、间距、排版、基础组件起步按需扩展。一个被 80% 项目使用的 20 个组件的库远比一个被 10% 项目使用的 200 个组件的库更有价值。五、总结设计系统的工程化落地需要三个支点分层的 Token 体系确保设计决策的可追溯性Style Dictionary 编译管线消除设计与代码的同步鸿沟Changesets 自动化发布保障组件库的持续交付。落地时务必克制过度设计的冲动从核心场景起步用数据驱动扩展决策——组件库的价值不在于数量而在于被消费的频率。

相关新闻