ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Design Token 大规模无损迁移:基于 jscodeshift Codemod 实践

Design Token 大规模无损迁移:基于 jscodeshift Codemod 实践 Design Token 大规模无损迁移基于 jscodeshift Codemod 实践在企业级设计系统的长期生命周期中“大版本重大破坏性重构Major Breaking Upgrades”是无法回避的必然阶段例如从v2.x升级到v3.0时设计团队废弃了 200 个冗余不合理的历史 Token如将--color-primary-light-2统一规范重构为--color-brand-accent-subtle将原本零散的间距属性从纯数字16规范为带单位的强类型枚举全公司数十个业务团队面临着手工修改数十万个源码文件的巨大痛苦导致新版设计系统发布后一年多依然根本推不动业务方升级传统的字符串正则全局查找替换Regex Find Replace存在致命隐患极其容易误伤变量名相似的业务逻辑例如误把const isPrimary true替换成了isBrandAccent破坏代码原始的换行、缩进与行内中文注释。利用Facebook 开源的 AST 自动化重构引擎jscodeshift结合 Recast 的无损语法树打印技术Lossless AST Printing我们能够实现编写一段仅需 50 行的 Codemod 脚本在 10 秒内自动化、100% 精准无损地完成全公司百万行代码的 Token 自动升迁基于 jscodeshift 的 Codemod 迁移架构拓扑[业务方待升级的陈旧 TSX / JSX 源码 (数万个文件)] │ ▼ (步骤 1: Recast 解析为高保真保留格式的 AST) [保留所有原始空格、代码缩进与注释节点 (Leading/Trailing Comments)] │ ▼ (步骤 2: jscodeshift Collection 链式 AST 匹配与替换) ┌───────────────────────┴─────────────────────────────────────────┐ ├── 转换 A: 查找所有 CallExpression: token(color.primary.light) ➔ 替换为新 Token ├── 转换 B: 查找所有 JSX 属性: classNametext-primary-light ➔ 替换为新 Tailwind 类 └── 转换 C: 查找所有 CSS/SCSS var(--color-primary-light) ➔ 替换为新变量 │ ▼ (步骤 3: Recast 无损回写磁盘 (Lossless Print)) [100% 保持代码原有风格零人工失误一键提交 Git Commit]编写生产级 Design Token 自动升迁 Codemod 脚本// transforms/token-v2-to-v3-codemod.js /** * param {import(jscodeshift).FileInfo} fileInfo * param {import(jscodeshift).API} api */ module.exports function transformer(fileInfo, api) { const j api.jscodeshift.withParser(tsx); const root j(fileInfo.source); // 1. 定义老旧 Token 向全新规范 Token 的语义重命名映射字典 const TOKEN_RENAME_MAP { color.primary.light: color.brand.accent.subtle, color.primary.dark: color.brand.accent.bold, spacing.card.padding.old: spacing.layout.card, font.size.h1.large: type.headline.hero, }; // 2. 转换一拦截所有 JS/TS 函数调用: token(color.primary.light) root .find(j.CallExpression, { callee: { name: token }, }) .forEach((path) { const firstArg path.node.arguments[0]; if (firstArg firstArg.type StringLiteral) { const oldToken firstArg.value; const newToken TOKEN_RENAME_MAP[oldToken]; if (newToken) { // 核心直接就地更新字符串字面量 firstArg.value newToken; } } }); // 3. 转换二拦截内联样式中的 CSS 变量字符串: var(--color-primary-light) root.find(j.StringLiteral).forEach((path) { let strVal path.node.value; for (const [oldKey, newKey] of Object.entries(TOKEN_RENAME_MAP)) { const oldCssVar --${oldKey.replace(/\./g, -)}; const newCssVar --${newKey.replace(/\./g, -)}; if (strVal.includes(oldCssVar)) { // 全局精准替换 CSS 变量名 strVal strVal.replaceAll(oldCssVar, newCssVar); path.node.value strVal; } } }); // 4. 转换三自动更新 import 导入语句 root .find(j.ImportDeclaration, { source: { value: company/design-tokens-v2 }, }) .forEach((path) { path.node.source.value company/design-tokens; // 升迁为统一标准包 }); // 5. 无损格式化输出 return root.toSource({ quote: single, trailingComma: true, }); };业务代码迁移前后真实对比迁移前包含旧包导入、旧 Token 与复杂行内注释的代码// src/components/UserProfile.tsx import React from react; import { token } from company/design-tokens-v2; // 这是一个关键的用户卡片组件 (保留此注释) export const UserProfile () { return ( div style{{ /* 核心旧间距 */ padding: token(spacing.card.padding.old), backgroundColor: var(--color-primary-light), }} h2 style{{ fontSize: token(font.size.h1.large) }}用户档案/h2 /div ); };运行 Codemod 后的最终产物无损保留注释与格式全部新 Token 完美就位// src/components/UserProfile.tsx import React from react; import { token } from company/design-tokens; // 这是一个关键的用户卡片组件 (保留此注释) export const UserProfile () { return ( div style{{ /* 核心旧间距 */ padding: token(spacing.layout.card), backgroundColor: var(--color-brand-accent-subtle), }} h2 style{{ fontSize: token(type.headline.hero) }}用户档案/h2 /div ); };一键执行全工程扫描迁移命令在 Monorepo 根目录下直接调用jscodeshift启动多进程并行处理# 在 10 秒内批量迁移全工程 5000 个源码文件 npx jscodeshift -t transforms/token-v2-to-v3-codemod.js src/ --extensionstsx,ts,jsx,js总结推动大型技术改造的终极法则是把复杂的迁移工作从“要求业务人肉修改的道德呼吁”彻底降维为“运行一条脚本一键全自动搞定的工程确定性”。通过编写基于 AST 语义的 jscodeshift Codemod 自动化迁移工具我们消灭了一切人工升级的摩擦与顾虑让企业级设计系统的演进与大版本重构真正如臂使指、畅通无阻。
返回列表