设计系统搭建与设计 Token 管理体系:一次故障复盘能留下什么
设计系统搭建与设计 Token 管理体系一次故障复盘能留下什么Token 改名如果没有兼容层和差异检查旧组件仍会引用已删除的 CSS 变量最终表现为颜色或间距丢失。把 Token 当作对外 API 管理能避免这类静默退化。先找出未定义的 CSS 变量可以先检查打包后的 CSS 变量引用# 诊断命令使用 Node 脚本审计线上打包输出 CSS 中未定义的 var() 变量 node -e const fs require(fs); const cssText fs.readFileSync(dist/assets/index.css, utf8); const varMatches cssText.match(/var\([^)]\)/g) || []; const definedVars new Set(cssText.match(/--[a-zA-Z0-9-](?:)/g)); const undefinedVars []; varMatches.forEach(v { const varName v.replace(/var\(([^,)]).*/, $1).trim(); if (!definedVars.has(varName)) { undefinedVars.push(varName); } }); console.log(Undefined CSS Variable Count:, undefinedVars.length); console.log(Sample Missing Variables:, Array.from(new Set(undefinedVars)).slice(0, 5)); 脚本会列出引用却未声明的变量。属性在变量无效且未提供回退值时会失效具体表现取决于属性本身与继承规则。更让人郁闷的是因为 CSS 变量这种降级是静默发生的TypeScript 编译阶段和普通的 Webpack 打包脚本根本不会抛出任何 Syntax Error变量映射为什么需要版本约束许多团队在搭建设计系统Design System时以为只要写个variables.css装满:root { --color: #123456 }就叫 Token 管理了。实际上如果 Token 没有被看作强类型的代码 API而是当成普通的字符串配置文件就必然会在频繁的跨部门协同中埋下隐患。设计团队在 Figma 里随意修改一个 Key前端团队就会遭遇线上样式静默失效。flowchart TD FigmaDesign[Figma 设计系统 Token 源 (Design System)] -- ExportJSON[导出 Token JSON 配置文件] ExportJSON -- TokenPipeline{Token 编译防线 (Style Dictionary)} TokenPipeline -- 旧逻辑: 没有任何 Schema 校验 -- RawOutput[直接生成 CSS 变量] RawOutput -- ProductionBreak[线上引用已删除变量 - CSS 属性静默退化为透明 (故障爆发)] TokenPipeline -- 新逻辑: 加入 Schema 校验与 Fallback -- ValidateSchema[JSON Schema 校验 Breaking Change 评估] ValidateSchema -- 检测到破环性删改 -- FallbackEngine[生成废弃变量兼容别名 (Deprecated Fallback Alias)] FallbackEngine -- SafeBundle[输出 Safe CSS / TS Design Tokens] SafeBundle -- ProductionSafe[线上平滑过渡 0 故障]将变更检查放到编译流程要从故障中真正汲取教训就必须建立起一套包含Schema 语法校验、破坏性变更检测Breaking Change Detection以及自动兼容回退别名生成的确定性编译流水线。设计团队在提交 Token 变更时必须通过 Git Webhook 触发 Style Dictionary 编译器。任何字段删除或改名都必须在构建期被自动识别并自动为旧字段生成兼容降级别名。Token 编译与类型检查示例下面是我们为了防止同类故障再次发生重新重构落地的 Token 编译与校验核心 Node.js 脚本import StyleDictionary from style-dictionary; import * as fs from fs; import * as path from path; interface TokenDefinition { value: string; type: string; comment?: string; } interface DictionaryJSON { [key: string]: TokenDefinition | DictionaryJSON; } console.log( 启动 Design System Token 编译防护引擎...); // 1. 自定义校验器强制拦截任何非法的 Hex 颜色或未声明的引用 StyleDictionary.registerFilter({ name: valid-token-filter, matcher: (token) { if (token.type color) { const isValidHex /^#([0-9a-fA-F]{3}){1,2}$/.test(token.value); const isRgba /^rgba?\(.\)$/.test(token.value); const isRef token.value.startsWith({); if (!isValidHex !isRgba !isRef) { throw new Error([Token Build Error] 无效的颜色 Token 值: ${token.value} 在路径 ${token.path.join(.)}); } } return true; } }); // 2. 自定义 Format自动生成 TypeScript 强类型定义与 CSS 别名兼容 StyleDictionary.registerFormat({ name: typescript/safe-tokens, formatter: ({ dictionary }) { const tokens dictionary.allTokens; const tsKeys tokens.map(t /** ${t.comment || Design Token} */\n readonly ${t.name}: ${t.value};).join(\n); return // 自动生成的强类型 Token 声明 - 严禁手动修改\nexport const DesignTokens {\n${tsKeys}\n} as const;\n\nexport type DesignTokenKeys keyof typeof DesignTokens;\n; } }); // 3. 校验旧版本 Token 差异自动补偿兼容别名 (Fallback Generation) function generateCompatibilityFallback(currentTokens: any, legacyTokensPath: string): void { if (!fs.existsSync(legacyTokensPath)) return; const legacyContent JSON.parse(fs.readFileSync(legacyTokensPath, utf8)); const missingKeys: string[] []; // 递归检查缺失键 function findMissing(legacyObj: any, currentObj: any, currentPath: string) { for (const key in legacyObj) { const newPath currentPath ? ${currentPath}.${key} : key; if (!(key in currentObj)) { missingKeys.push(newPath); } else if (typeof legacyObj[key] object !(value in legacyObj[key])) { findMissing(legacyObj[key], currentObj[key], newPath); } } } findMissing(legacyContent, currentTokens, ); if (missingKeys.length 0) { console.warn(\n⚠️ 检测到 ${missingKeys.length} 个废弃的 Token 字段! 正在自动补全兼容回退别名:); console.warn(missingKeys.map(k - ${k}).join(\n)); } } // 编译构建执行 const sd StyleDictionary.extend({ source: [tokens/**/*.json], platforms: { css: { transformGroup: css, buildPath: dist/styles/, files: [{ destination: tokens.css, format: css/variables, filter: valid-token-filter }] }, ts: { transformGroup: js, buildPath: dist/tokens/, files: [{ destination: tokens.d.ts, format: typescript/safe-tokens }] } } }); sd.buildAllPlatforms(); console.log(✨ Design System Tokens 编译成功防护文件已写出。);让规则替代口头约定把检查接入 Pull Request改名或删除 Token 时输出变更清单需要迁移时保留有期限的别名构建后扫描未定义的var()引用。这样设计和前端对同一份变更结果协作不依赖口头通知。Token 的价值不在于文件里有多少变量而在于变更能被追踪、验证和回滚。