设计系统重构这件事,我见过太多团队把它做成了一场搬家:把旧的 SCSS 变量改成 CSS 变量,把几百个 React/Vue 组件换了一层皮,上线的时候皆大欢喜,过了三个月再回头看,新系统里又长出了三套颜色、两套间距、五六种按钮写法,和重构前一模一样。这不是执行力的问题,而是重构的时候,根本没有建起让系统"不再变乱"的机制。
我是在团队最混乱的时期接触到 Google Stitch 的。品牌升级、多主题适配、老组件库 400 多个组件堆了 2000 多个 token,Figma 设计稿和线上代码已经对不上号。用 Stitch 把整个设计系统重构一遍之后,我才真正想明白一个道理:重构设计系统的核心不是换掉旧代码,而是把设计系统当成一个软件产品去治理,让每一次设计决策都能被记录、被追踪、被自动化地落到代码里。这篇文章会把整个过程的思路、操作和踩过的坑完整记录下来,给正在做或者准备做设计系统重构的团队一个参考。
1. 为什么设计系统越重构越乱:先看清四个腐化信号
大部分团队决定重构的时候,系统其实已经病得不轻了。但很少有人停下来仔细诊断"病根"在哪,多数人只看到一个表象:代码乱、样式乱、设计稿乱。于是重构方案顺理成章变成了"推倒重来",而推倒重来的结果,往往是旧病复发。
1.1 设计系统腐化的四个典型信号
我在动手之前,先给团队现有的设计系统做了一次"症状体检",总结下来有四个非常典型的信号。
第一,同一语义存在多种实现。按钮在组件库里有三种款式,间距体系里有四五套"差不多但又不完全一样"的变量,颜色 token 里光是"红色"就有十几种近义值。这不是设计团队有意为之,而是每次新需求来的时候,大家不想动老组件,于是拷贝一份改个色值就交差了。
第二,设计决策不可追踪。你问任何一个人"这个主色为什么从 3.0 版本开始改成了现在的色值",没人能回答上来。设计稿、代码、文档里记录的信息互相矛盾,甚至连 Figma 文件本身都有好几个版本。设计系统的演进完全靠人的记忆,这是最危险的。
第三,硬编码与 token 混用。有的人用 primary 变量,有的人直接写 #1976D2,还有的人写 var(--blue, #00f) 这种带 fallback 的内联值。你永远不知道线上页面到底用了多少种灰色,因为代码扫描出来的灰色值可能有几十个。
第四,规范的执行完全靠人肉。设计走查靠 UI 设计师逐个页面看,代码评审靠前端工程师逐个 PR 看。今天同事心情好管得严一点,规范就执行得好一点;明天需求紧急,一切从快,规范就成了废纸。
1.2 重构失败的三个共性根因
看到这些信号,团队通常的反应是"我们要做一次彻底的重构"。但失败的案例看多了,我发现背后的根因其实只有三个。
根因一是缺乏单一事实源。设计稿、代码、文档各有一套,重构的时候以哪个为准?很多人拍脑袋说"以 Figma 为准",可 Figma 里的样式库本身也是乱的,色板里有大量重复、过期甚至根本没人用的样式。你拿一个乱糟糟的源去治理另一个乱糟糟的源,结果只是把乱换了个形式。
根因二是用"搬迁"代替"治理"。把 SCSS 变量改成 CSS 变量,把 Stylus 换成 Sass,把旧组件改个 className——“现代化”的外壳套上了,但原有的问题原封不动搬进了新系统。重构完成的那一刻就是新系统开始腐烂的那一刻。
根因三是范围失控。想一次性把所有组件、所有业务页面全部重构完,结果战线拉长到半年,产品需求一来,重构优先级一降再降,最后烂尾。团队里出现两套并存的设计系统,比一开始就一套烂系统更痛苦。
所以我的结论很明确:重构之前,必须先找到一套能把"设计决策"和"代码实现"统一管理起来的基础设施,而不是急着写新代码。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Google Stitch 在重构中扮演什么角色
在确定重构路径之前,我花了不少时间调研市面上的工具,最后把目光锁在了 Google Stitch 上。这里先说明一点:Stitch 本身并不是那种满地都是教程的开源软件,它更像是 Google 内部孵化出来的设计系统基础设施,对外的资料比较少。我接触到的版本,核心能力已经非常完整,而且它的设计思路本身就值得所有做设计系统的人学习。
2.1 它不是一个"设计转代码"工具,而是一个设计系统控制平面
很多人一听到"设计系统工具",第一反应是"把 Figma 设计稿转成代码"。Stitch 做的事情比这个底层得多,也重要得多。
Stitch 的核心思路,是把设计决策(design decision)和代码实现(implementation)彻底解耦。设计团队在 Stitch 里维护一套机器可读的结构化数据——它包含设计 token、组件规范、主题变量、变更记录——然后由 Stitch 生成各个端需要的产物。你可以把它理解为设计系统的"数据库和 API 网关",Figma 是前端编辑器,代码仓库是执行引擎,而 Stitch 是中间那一层控制平面。
举个例子。设计团队决定把主色从蓝色调成紫色,在没有 Stitch 的团队里,这个改动要经历:设计稿改色 -> 设计师截图发群里 -> 前端工程师手动改 SCSS 变量 -> 测试肉眼检查所有页面。而在有 Stitch 的团队里,设计团队只需要改一个 token 的值,生成产物自动更新,所有引用这个 token 的组件和页面在下次构建时自动生效,Stitch 还会自动生成一份"受影响页面清单"。
对重构场景来说,这个能力非常关键。因为重构的核心工作恰恰就是"找出所有不一致的地方,把它们统一到一套决策体系下"。
2.2 Stitch 与常见设计系统工具的定位对比
我知道很多团队现在用的是 Style Dictionary、Figma Tokens Studio,或者干脆自研脚本。这些方案各有优劣,我也实际对比过,整理成一张表更方便大家判断。
| 工具/方案 | 覆盖范围 | 优点 | 局限性 |
|---|---|---|---|
| Google Stitch | 设计决策管理、token 生成、主题管理、影响分析、合规检查 | 闭环完整,自动化程度高,适合中大型团队长期治理 | 接入成本偏高,社区资料少,外部团队上手需要一些时间 |
| Style Dictionary | Token 到多平台代码的翻译 | 轻量灵活,输出格式可控 | 不管上游设计,需要自己维护完整的 token 仓库和映射关系 |
| Tokens Studio + Figma | 设计端 token 管理与同步 | 设计师友好,Figma 内体验好 | 到代码端需要额外管道,缺少影响分析和合规检查能力 |
| 自研脚本 | 完全自定义 | 短期贴合团队现状 | 长期维护成本高,容易变成新的"一次性工程" |
Stitch 的优势在于它解决了"从设计决策到代码落地的完整链条",而不是只解决其中一环。但它不适合那种只有两三个前端、十几个组件的微型团队,对这类团队来说,引入 Stitch 反而会变成负担。
2.3 为什么重构场景下 Stitch 特别好用
如果你仔细想一下"重构"这个动作,你会发现它有三个逃不开的死结,而 Stitch 恰好每一刀都砍在死结上。
第一个死结是影响面不可控。改一个颜色值,到底会影响多少个页面?人肉根本数不清。Stitch 的 token 依赖图谱能直接告诉你:这个 token 被哪些 component token 引用,那些 component token 又被哪些组件引用,最终映射到多少个页面入口。
第二个死结是新旧切换的阵痛。重构不可能一步到位,一定会有新旧并存的"双轨期"。Stitch 的别名机制允许你把旧 token 指向新 token,旧组件在过渡期内依然能正常工作,你只需要按优先级逐个迁移组件即可,不需要在某个周末熬夜做"大爆炸式"切换。
第三个死结是迁移完之后的验收。怎么证明新系统没有问题?Stitch 可以生成一份 token 使用合规报告,把所有硬编码的色值、间距、圆角统统标出来。迁移完一个组件,跑一遍报告,绿了就说明这个组件真的干净了。
3. 重构前的资产体检:先摸清家底再动手
明确要用 Stitch 之后,我没有急着把数据导进去。先把家底盘清楚,才敢动刀。
3.1 用 Stitch 把 Figma 样式批量提取成 token
第一步,把 Figma 设计文件接入 Stitch。Stitch 会通过 Figma API 把样式库里的颜色、字体、间距、圆角、阴影、动效等全部读取出来,自动生成一份"token 草稿"。
这里有一个实操建议:提取的时候不要全量提取,而是按页面或者按组件维度分批提取。我们当时先挑了使用频率最高的 8 个基础组件页面(Button、Input、Badge、Card、Tag、Modal、Tooltip、Empty)来做试点。全量提取会产生大量噪音,你根本分不清哪个样式是规范里的、哪个是某个设计师临时拖出来的。
提取完成之后,Stitch 会把每个样式按领域分类,自动命名成类似 color.primary 或 spacing.lg 这样比较规整的名字。但注意,这只是机器生成的"候选名",不能直接当成规范用,后面还需要人工确认。
3.2 Token 盘点:合并同名异义、找出孤儿与硬编码
提取完之后,真正的重头戏是 token 盘点。我们当时得到了一个非常扎眼的数字:颜色 token 一共 247 个,但其中色值相同的重复项占了 40% 以上。
具体来说,core-red、brand-color、color-danger、error-color 这四个 token 指向的其实是同一个色值 #F44336。这种"同名异义、异名同义"的问题,不盘点根本发现不了。
Stitch 的盘点报告会把 token 分成几类:
- 重复 token:色值相同但名字不同,需要合并
- 近义 token:色值相近但又不完全相同,需要设计团队确认是否真的需要区分
- 孤儿 token:定义了但在任何组件和页面中都没有被引用,可以直接删除
- 硬编码引用:代码里直接写的色值/间距/圆角,需要建立映射替换表逐批处理
我当时还做了一个统计,发现全仓库有 300 多个地方在写硬编码 #eee 这类颜色,而规范里的 Token color-border-light 只被引用了两次。这说明规范早就被架空了,大家在长期实践中养成了"自己动手丰衣足食"的习惯。
处理这些问题的策略很简单:
- 先合并色值相同的 token,删除孤儿 token
- 再定义一套语义 token,让旧的 token 通过 alias 关系指向语义 token
- 硬编码值不急着一次性替换,先通过代码扫描生成一份"硬编码替换映射表",在后续组件迁移时顺带处理
3.3 组件依赖图谱:找到真正需要重构的"原子组件"
Token 清完之后,接下来要看组件。Stitch 会扫描整个前端代码仓库的组件引用关系,生成一张组件依赖图谱。
从这张图谱里,我按"被依赖的程度"把组件分成了三层:
- 底层原子组件:Button、Input、Icon、Typography,几乎所有页面都在用,重构优先级最高,因为它们改动一次,影响面最大,也最需要提前验证
- 复合组件:比如带下拉选项的 Select、带校验的 FormItem,它们依赖多个原子组件,重构时需要同时关注原子组件的变化
- 业务组件:直接承载业务逻辑的组件,重构时要把注意力放在"外观是否变化"上,而不是去动里面的业务代码
重构顺序一定是从底层往上,先原子后复合。如果反着来,底层组件一改,上面所有复合组件全要跟着改,局面根本控制不住。
4. Stitch 落地重构的四步法:从 Schema 到灰度发布
资产盘点做完之后,正式的重构就开始了。我把整个流程总结成四步,每一步都能独立验证结果,这样每一步走完都有安全感。
4.1 第一步:建立 Schema,把设计系统变成"数据库"
Stitch 的所有数据都基于一份结构化的 Schema。我建立了一套四层 token 模型:primitives(原始值层)、semantic(语义层)、components(组件层)、themes(主题层)。
以颜色为例,YAML 配置大致长这样:
yaml复制primitives:
color:
red-500: { value: "#F44336" }
red-700: { value: "#D32F2F" }
gray-900: { value: "#212121" }
semantic:
color:
danger: { value: "{primitives.color.red-500}", type: "color" }
danger-hover: { value: "{primitives.color.red-700}", type: "color" }
page-background: { value: "{primitives.color.gray-900}", type: "color" }
components:
button:
background: { value: "{semantic.color.danger}", type: "color" }
background-hover: { value: "{semantic.color.danger-hover}", type: "color" }
themes:
dark:
semantic:
color:
page-background: { value: "{primitives.color.gray-900}", type: "color" }
这一层最需要注意的是命名规范。语义 token 的命名必须按"用途"来,不能按"表现"来。比如 text-color-primary 是好的命名,因为它描述的是"主要文字颜色"这个用途;text-color-blue 就是坏的命名,因为哪天把主色换成紫色,这个 token 名就成了谎言。
Stitch 里可以配置命名规范的校验规则,凡是命名字段不符合正则表达式的 token,直接拒绝入库。这一步从一开始就堵住了"又造出一堆新名字"的口子。
4.2 第二步:配置多主题,让品牌升级可控
我们这次重构的背景是品牌升级,需要同时支持多个品牌主题。也就是说,同一套组件,在品牌 A 下用一种主色,在品牌 B 下用另一种主色,在深色模式下还要有一套对应的暗色变量。
Stitch 的主题机制解决这个问题非常优雅。它的核心规则是:主题只能在 semantic 层做覆盖,不能直接改 primitives 层,更不能改 components 层。也就是说,原始色板只有一个,但"页面背景""主按钮背景"这些语义 token 可以在不同主题下有不同值。
生成出来的 CSS 变量效果大概是这样:
css复制:root {
--color-red-500: #F44336;
--color-danger: #F44336;
--button-background: #F44336;
}
[data-theme="brand-b"] {
--color-danger: #7E57C2;
--button-background: #7E57C2;
}
[data-theme="dark"] {
--color-page-background: #212121;
}
所有组件代码里只引用语义 token 对应的 CSS 变量,比如 var(--button-background),这样主题切换完全发生在 CSS 层面,React/Vue 组件代码一行都不用改。
我在这一步还做了一件很重要的事:把旧的 SCSS 变量映射到新的 CSS 变量上。比如旧代码里可能大量使用 $brand-primary,我就在入口文件里做一层兼容:
scss复制$brand-primary: var(--button-background);
这样双轨期内新旧组件都能正常工作,不会出现某个页面因为变量没定义而样式崩溃的情况。
4.3 第三步:渐进式迁移组件代码
进入组件迁移阶段,最容易犯的错误是"想一口气把所有组件全部改完"。我们当时定了一个死规矩:每天只迁移一个组件,迁移完必须在当天跑完视觉回归和合规检查。
以 Button 为例,迁移前后的代码对比是这样的:
重构前:
css复制.primary-btn {
background-color: #F44336;
border-radius: 4px;
padding: 8px 16px;
}
重构后:
css复制.primary-btn {
background-color: var(--component-button-background);
border-radius: var(--component-button-radius);
padding: var(--component-button-padding);
}
看起来只是把硬编码值换成了变量,但这一步的意义在于:从此以后,这个组件的外观不再依赖任何人的自觉,而是完全由 Stitch 里的设计决策驱动。设计团队想调整按钮圆角,只需要改 Stitch 里的 component token,前端什么都不用动。
每个组件迁移完之后,我们会在 Stitch 里生成一份组件级的 token 使用报告,检查里面是否还有硬编码值。如果报告是绿的,这个组件就算"合规"了。
4.4 第四步:灰度验证与回归测试
组件全部迁移完成,不代表可以一次性发布。我们通过 Stitch 的影响分析功能,拿到了一份完整的"改动波及页面清单",按三个梯队灰度:
- 第一批:内部后台管理系统,出了问题不直接影响用户
- 第二批:低流量业务页面,比如帮助中心、活动页
- 第三批:核心链路页面,比如首页、下单流程、个人中心
每一批上线之后,都会跑三样检查:
- 视觉回归测试(我们用 Chromatic),对比迁移前后的页面截图,差异超过 0.1% 就要人工确认
- Stitch 的 token 合规报告,确保新页面里没有硬编码值混入
- 团队内部的 UI 走查,找几个平时对视觉比较敏感的设计师,让他们肉眼过一遍核心页面
三样全过,才进入下一批。
5. 重构过程中踩过的坑:完整排查链路
任何重构都不可能一帆风顺,我记录几个记忆最深的坑。写这些不是说 Stitch 不好,恰恰相反,这些坑证明了"设计系统治理"这件事的复杂度远超想象,工具能帮你暴露问题,但不能替你决策。
5.1 别名环导致的编译死循环
第一个坑出现在配置 component token 的时候。当时我想让按钮的背景色引用一个语义 token color.danger,但是因为某些历史原因,我又在语义层里把 color.danger 设置成了引用 component.button.background。结果就是两个 token 互相引用,Stitch 编译直接报错:Circular reference detected。
排查链路其实很清晰:Stitch 的报错信息里带了 token ID,我顺着 ID 找到对应的两个 YAML 文件,一眼就看出了互相引用的问题。但难点在于,项目大了之后,这种交叉引用可能藏在三四层别名中间,肉眼很难发现。
解决办法有两层。第一层是规定引用方向,只允许 primitives -> semantic -> component 单向降级,禁止反向引用。第二层是加 Lint 检查,在 CI 里跑 stitch lint --rule=no-circular-alias,一旦出现环就直接 fail,从机制上杜绝。
5.2 设计稿与代码二次脱节
重构完成两周后,设计团队在 Figma 里把 Button 的圆角从 4px 改成了 6px。结果没人同步到 Stitch,代码里还是旧值。这其实不怪工具,Stitch 的 Figma 同步是"事件驱动"的,需要有人手动触发,而不是像 CI 一样持续跑。
这件事给我的教训是:重构完成不等于治理完成,设计系统是活的东西,它一直在变,所以必须建立"变更提醒"机制。
我们的解决方案很简单:在 CI 里加了一个定时任务,每天凌晨 2 点自动拉取 Figma 的样式变更,和 Stitch 里的当前值做 diff,生成一份"设计稿变更报告"发到团队群里。设计或开发看到报告后,再去决定这次变更要不要 merge 到 Stitch 里。这样就不会出现"改了两周才发现线上线下不一致"的尴尬。
5.3 多主题切换下的组件样式串扰
第三个坑是深色模式下某个组件背景色错误。排查过程挺曲折的:先在浏览器里看到 Dark 模式下一个卡片组件背景色是白色,于是去查这个组件的 CSS,发现它引用的变量是 var(--component-card-background),这个 token 在 dark 主题下应该被覆盖成深灰色。但实际颜色不对。
继续往下查,发现 component.card.background 这个 token 在配置的时候,没有引用语义 token semantic.color.background-primary,而是直接引用了原始色板 primitives.color.white。这样在 light 主题下看起来没问题,但 dark 主题的覆盖根本不起作用,因为组件 token 跳过了语义层。
这个坑的本质,就是 4.2 里反复强调的"主题只能在语义层覆盖"这条规则被破坏了。我把这个 case 写进了团队规范文档,并且给 Stitch 加了一条自定义规则:任何 component token 的引用链上,只要出现 primitives 直接引用,CI 就报警。
5.4 生成物体积膨胀
Stitch 默认会把所有主题的所有 token 全量生成一份 CSS 变量文件。我们当时配置了 4 个主题,每个主题 2000 多个 token,四个主题的 CSS 文件加起来有 80KB。对一个设计系统来说,这有点过于庞大了。
排查之后发现,规范里确实定义了 2000 多个 token,但实际组件代码里引用的只有不到 300 个,剩下的大部分是"预定义但尚未使用"的变量,比如未来某个组件可能会用到的颜色。
解决方法是开启 Stitch 的"按需生成"模式,只生成组件实际引用的 token。同时在构建流水线里加了一个 PostCSS 插件,在产物里删除所有未被引用的 CSS 变量。经过这两步,CSS 体积从 80KB 降到了 12KB,因为样式文件本身很小了,页面首屏的样式加载速度也快了不少。
6. 重构之后:把"防腐化"做成自动化
重构完成只是起点,真正的挑战在于让这套系统在未来的三年里不再腐烂。我见过太多团队在重构完成那一天兴奋不已,半年后又回到老路上。所以我特意把"持续治理"作为重构的一部分来设计。
6.1 CI 里的设计系统合规门禁
Stitch 提供了一套 CLI 工具,可以集成到 CI 流水线里做设计系统合规检查。我们配置了三条规则,全部不满足就直接拦截 merge request:
- 代码中不允许出现硬编码的颜色、字体、间距、圆角值
- 新组件必须引用语义 token,不允许直接引用原始色板
- 不允许出现未在 Stitch 中注册的 className 前缀
对应的命令大致是这样:
bash复制npx stitch check --rule=no-hardcode-color --path=src/
npx stitch check --rule=forbid-primitive-in-component --path=src/
npx stitch check --rule=namespace-allowlist --allowed=btn,input,card,badge
这些规则跑一次只需要几秒钟,但对开发者的行为约束非常大。以前代码评审靠人肉提醒"这里怎么写了个 #333",现在机器直接拦,连提交流程都进不了。
6.2 设计变更提案流程
数据层的治理光靠工具还不够,流程也得跟上。我们建立了一个"设计变更提案"机制,最基本的规则是:任何设计改动,先在 Stitch 里改,再通过 merge request 走评审,通过后才能生效。
这个流程和代码 review 类似,Stitch 会生成结构化的 diff:什么 token 从什么值改成了什么值,影响了多少组件和页面,有没有风险等等。评审人不需要打开 Figma 也能看懂这次变更,因为 diff 本身就是完整的说明。
这个机制带来的额外收益是,设计决策终于有了历史记录。以后有人问"为什么主色是紫色而不是蓝色",翻一下 Stitch 的变更历史,就能看到最初的决策背景和评审讨论,不再靠老员工口口相传。
6.3 AI 辅助设计系统重构的尝试
最后聊一个偏前瞻的内容。最近看到 vivo 内部用 AI 两天重构了 2 万行 Vue 项目的案例,很受启发。设计系统重构里最枯燥的部分,其实就是"找旧的硬编码值、对照映射表、替换成新变量",这个模式非常适合 AI 批处理。
我在重构的第二批组件里做了一个实验:把所有需要替换的旧值整理成一张"映射表",然后用大模型写了一段批量替换脚本,允许它自动替换代码里所有能精确匹配的颜色值、间距值。整个过程大概覆盖了 60% 的替换工作,剩下 40% 因为涉及上下文判断,比如某个颜色值是用于边框还是文字,机器拿不准,还是交给人工处理。
比较关键的一点是:AI 生成的替换结果绝对不能直接 merge,必须跑一遍视觉回归和 token 合规报告。我们当时就发现有一处替换把 hover 状态的颜色错误地替换成了默认状态的颜色,这种细节 AI 很难把握,但视觉回归一眼就能看出来。
AI 在这里的定位是"提效工具",而不是"决策者"。真正决定设计系统怎么演进的,还是人。
我个人在实际操作中的体会是:Google Stitch 这个工具本身到底叫不叫 Stitch、以后会不会有更强大的版本,其实没那么重要。真正改变我的,是它逼着我把设计系统的建设从"文档和设计稿"变成了"代码和数据结构"。重构设计系统最怕的不是工作量大,而是没有一个机制让所有设计决策都有据可依、自动落地。如果你团队的场景和我类似,但暂时用不了 Stitch,完全可以把这套思路移植到 Tokens Studio 加 Style Dictionary 的组合上,从建立语义 token 分层开始,一步一步把设计系统从"一堆文件"变成"一个产品"。
