小岛AIAI 航行日志

小岛 / 岛刊社 / 文章

CSS-in-JS 要不要迁?GitHub 的三道灰度线值得抄

小岛AI

GitHub 的 55% 不是换个 CSS 写法就有。先量运行时成本,保住兼容层,再把视觉与性能一起灰度,才是能安全下线旧样式系统的迁移路线。

AI 编程AI 安全算力与本地部署

你线上 SSR 的首屏慢了一截,Chrome Performance 里又看见一串样式初始化。有人提议把 styled-components 或 emotion 一把梭迁走。这个时刻,最危险的不是样式方案本身,是团队误以为自己要做一件“换写法”的小事。

GitHub 刚把 dotcom 的 CSS-in-JS 体系完整迁到 CSS Modules。他们交出来的数字很醒目,Primer 组件迁完后,页面服务端渲染时间少了 55%,组件初始化时间少了 25%。但这篇复盘真正值钱的部分,藏在数字后面。GitHub 没有押注一次巨型重构,而是给旧路径留了门,给新路径立了验收线,再把每一步放进灰度里慢慢跑。官方原文在这里。

如果你的 React 项目也在考虑这件事,先别问“CSS Modules 有没有更先进”。先把下面三道线画出来。

第一条线,先证明运行时样式真是瓶颈

CSS-in-JS 并不天然有罪。它给 TypeScript、设计 Token 和组件共置带来过很舒服的体验。问题通常出在规模变大以后,动态样式对象要在客户端或服务端执行、收集、注入。页面组件越多,这笔账越像一根没关的水龙头。

GitHub 在 2023 年观察到的就是这个组合,首屏要等样式初始化,SSR 需要额外收集样式,页面上的组件一多,样式更新也越来越难控。于是他们把样式搬到构建期,让浏览器拿到已经生成的 CSS,而不是在渲染时临时算一遍。

这里有个容易被 55% 带偏的坑。那是 GitHub 的 Primer 组件体系得到的结果,不是任何项目的保底收益。后面推广到站内不同页面时,GitHub 记录到的 SSR 改善在 1% 到 22% 之间,github/code_view 的一个 commit 页面达到 21.97%。差距本身就是提醒,先分页面量基线,再决定要不要动刀。

GitHub 公布的多个服务端渲染改善值

GitHub 的页面级数据并不整齐,迁移优先级应该跟着真实热点走。

我会先盯三组数,别一上来就统计仓库里有多少 styled 调用。

  1. 关键路由的 SSR 耗时和客户端 hydration 耗时,要有改造前基线。
  2. 每个路由上动态样式对象、sx 一类运行时入口的数量,要能按包和负责人切开。
  3. 首屏视觉回归、主题切换和无障碍快照,要能在同一套 CI 里复现。

三组数缺任何一组,迁移都还不该启动。你只是知道“有人觉得慢”,还不知道该从哪儿切,也不知道改完坏在哪儿。

第二条线,旧能力先留在兼容层里

GitHub 最开始没有让所有人同时改。Primer 每个组件都有一份对应的 CSS Modules 实现,同时挂在 feature flag 后面。视觉回归测试比对新旧快照,发布范围从团队到内部员工,再到全量用户。

这条路线看着慢,实际是在给迁移买保险。因为真正难迁的往往不是 Button,而是多年积累下来的 sx。它能接收动态对象,也和 Token 绑得很紧。直接删掉旧包,调用方会在同一天发现自己原来拿样式当编程语言用了。

GitHub 的做法很工程化。他们保留了 @primer/styled-react 这个过渡包装层。仍依赖 sx 的调用继续从它导入,已经摆脱运行时样式的调用则直接走 @primer/react。新旧两条路并存,旧调用不被突然掀桌,新调用也能立刻得到收益。

这就是第二条线,兼容层必须有明确入口和明确债务表。每个模块迁移前都要回答三件事。

  1. 这个动态样式到底是静态 Token、条件 class,还是确实需要运行时计算。
  2. 如果暂时不能迁,它走哪一个兼容入口,谁负责把它从清单里拿掉。
  3. 如果新实现出问题,feature flag 能否在分钟级退回旧路径。

没有兼容层的大迁移,表面上代码更干净,线上出了视觉问题时只会更狼狈。好家伙,最后常常变成大家半夜查一条不知道谁写的主题覆盖规则。

第三条线,让 AI 帮搬运,别替你签收

GitHub 这次还有一个很具体的变化。2025 年启动时,迁移清单最高有约 7,760 个 sx prop。一组 8 人工程师花了 6 个月迁了 6,419 个。到 2026 年重新推进时,剩余数量从 895 到 0,只用了两位工程师三周,并且用了 Copilot coding agent。

GitHub 记录的 sx prop 清理曲线

曲线能说明自动化搬运提速,却不能替代每一批的验收证据。

这组数字很诱人,但别把它读成“让 Agent 跑一晚上,旧样式就能消失”。AI 很适合做模式重复的活,识别静态 sx,生成 CSS Modules 文件,替换导入,补上大部分机械测试。它不适合替团队判断一个动态样式是否承载了交互状态、主题语义或无障碍反馈。

所以第三条线是验收线。每次让 Agent 提一批迁移,别只看 diff 漂不漂亮。至少要有下面这张小单子。

路由或组件:____________
改造前后 SSR 与 hydration:____________
新旧视觉快照:通过 / 不通过
高对比和全部主题:通过 / 不通过
兼容层调用数:____________
灰度比例与观察窗口:____________
回滚开关与负责人:____________

这张单子不酷,却很有用。代码 Agent 解决的是搬运速度,性能专项真正要保护的是证据链。你得知道改动在哪个路由生效、指标有没有掉、视觉出了问题怎么撤。

不要把 GitHub 的路线误读成 CSS Modules 必胜

GitHub 的结果来自自己的设计系统、主题架构、部署节奏和大量既有测试。它的结论并不是所有 React 应用都该立刻抛弃 CSS-in-JS。

如果你的页面主要在浏览器端运行,动态主题和基于状态的样式很多,运行时成本也没有出现在关键指标里,迁移优先级很可能很低。反过来,SSR 路由多、设计系统覆盖广、样式初始化已经挤压首屏,而且团队有视觉回归和灰度能力,这类改造才值得排到前面。

GitHub 最后还把 styled-components、styled-system 和历史 sx 从 dotcom 下掉了,但他们连移除依赖这一步都放进 feature flag。厉害了,连收尾也当成一次可回滚发布,而不是合并 PR 后祈祷。

把样式从运行时挪到构建期,听起来像前端的卫生清扫。做大了以后,它更像一次小型平台迁移。先量哪儿慢,再给旧能力留出口,最后才让自动化加速搬运。顺序别反。

你们项目里最难迁的动态样式,是主题、交互状态,还是那些谁也不敢删的历史 sx?