你打开一个 GitHub 页面,点了一个按钮,等了一秒——这一秒里,浏览器不只是渲染页面,它还要现场帮你"写 CSS"。
听起来荒谬,但这就是 CSS-in-JS 的真实运作方式——把样式直接写在 JavaScript 代码里。它曾是前端开发最时髦的做法:组件、逻辑、样式全放一个文件,干净利落。直到 2023 年,GitHub 的工程师们发现,随着页面上组件数量暴涨,这套方案开始拖垮性能:服务端渲染越来越慢、客户端初始化越来越卡、样式更新量失控。
2026 年 6 月,GitHub 交出了答案:全站 100% 切换到 CSS Modules,服务端渲染时间砍掉 55%,组件初始化快 25%。而这一刀,整整切了三年。
把CSS写进JS,为什么反而拖慢了页面?
想象一家餐厅的后厨。每来一个订单,厨师不是直接炒菜,而是先翻出一张空白纸,当场写完这道菜的菜谱,再照着菜谱做,最后才把菜端出去。下一个订单来了,再写一遍。
这就是 CSS-in-JS——GitHub 用的是 styled-components 这个具体库——的运行时开销。浏览器每渲染一个组件,JavaScript 引擎都要走完一套完整流程:解析样式模板 → 生成唯一类名编码 → 预处理样式规则 → 往页面里注入新的样式标签。单个组件只要几毫秒。但一个 GitHub 页面可能有数百个组件,几百个组件 × 四步流程 × 每次重来,延迟就这样累积起来了。

更伤的是服务端渲染。为了让 HTML 到达浏览器前就把样式嵌进去,styled-components 需要在服务端用一个专门的收集器,把整棵 React 组件树包起来,一边渲染组件一边收集每个组件现场生成的样式规则,最后再统一塞进 HTML。每来一个请求,这套"边渲染边收集"的流程就要从头跑一次。页面上的组件越多,收集负担越重。
React 核心团队成员曾给出一个定性:在 React 的分片渲染机制(并发模式)下——这种机制本意是让浏览器处理大型页面时可以"喘口气"——如果每个组件渲染时都往页面插入新的 CSS 规则,浏览器就必须每帧重新计算所有规则与所有元素的匹配关系。这是架构层面的硬伤,不是优化能解决的问题。
Spot 的开发团队做过一次实测:把同一个列表组件从 CSS-in-JS 库迁移到 Sass Modules 后,渲染时间从 54.3 毫秒降到了 27.7 毫秒——几乎腰斩。
那么问题来了:既然"现写样式"这么贵,能不能把这一步提前做完?
CSS Modules:把样式"提前算好"再出门
回到厨房的比喻。与其让厨师每道菜都现场写菜谱,不如把所有菜谱提前印好——厨师只管照着做,菜谱跟着托盘一起端出去。这就是 CSS Modules 的思路:把"写 CSS"这件事,从运行时搬到构建时。
CSS Modules 不是浏览器原生功能,而是 Webpack 这类打包工具在编译阶段做的自动化操作。当你写一个样式文件时,构建工具在打包时会做三件事:给每个类名自动生成一个全局唯一的编码(比如 .button 变成 Widget1_button_1FUOu),彻底杜绝不同组件之间的类名冲突;生成一份 JavaScript 映射表,让组件代码通过 styles.button 这种干净的方式引用编码后的类名;把所有样式合并成一个静态的 .css 文件,随 HTML 一起下发给浏览器。

整个过程在构建时就完成了。用户打开页面时,CSS 文件已经是现成的——不需要 JavaScript 在运行时"写 CSS",不需要服务端拼命收集样式规则,也不需要浏览器每一帧重新匹配规则和元素。
本质上,CSS Modules 做了一件很朴素的事:它让 CSS 重新变回了 CSS——一个浏览器天生就会处理、专门为样式优化过的静态资源。那个把样式写进 JS 的创意,曾经看起来像是统一了两种语言,实际上却让 JavaScript 背上了它本不该背的包袱。
但选择技术方案只是一瞬间,真正的考验在后面:一个每天服务数千万用户的产品,怎么在不停机、不崩页面的前提下,把整套样式引擎换掉?
不停机换引擎:GitHub 的三年渐进式手术
答案是:不要一次性全换。GitHub 的 Primer 设计系统团队采用的策略,在软件工程里有一个经典名字——Strangler Fig(绞杀榕)模式。新系统沿旧系统边界一点点生长、缠绕、替代,直到旧系统自然枯萎。
GitHub 的做法分了三层。
第一层是 feature flag——功能开关。每个组件迁移时同时保留新旧两套样式,通过一个开关在运行时决定走哪条路。先用自动化视觉回归测试做像素级对比,确认新旧截图完全一致;然后先让 Primer 团队自己用,再逐步放开到全体 GitHub 员工,最后推到所有用户。出了任何问题,关掉开关就能立刻回退,用户毫无感知。
第二层是 wrapper library——包装库。最棘手的问题是 sx prop:GitHub 内部数千个组件都在用这个运行时 prop 做动态样式定制,它是 CSS-in-JS 最方便但也最昂贵的功能。直接删掉会让所有使用它的页面崩掉。于是团队建了一个叫 @primer/styled-react 的过渡库,给已经迁移到 CSS Modules 的组件重新包上一层兼容壳——让那些还在用 sx 的旧代码能继续工作,而已经不用 sx 的新代码则直接从 @primer/react 引入,享受零运行时开销。新旧共存,各走各路。
第三层来得意外:AI 加速。2025 年 4 月,全 GitHub 约有 7,760 个 sx props 等待迁移。8 名工程师花了 6 个月手动迁移了 6,419 个,观察到个别页面 SSR 性能提升 1% 到 22%。到 2026 年 4 月还剩 895 个时,GitHub Copilot 的 AI 编程助手能力已经今非昔比——2 名工程师加上 AI 辅助,三周内把剩下的 895 个全部清零。
还没完。GitHub 支持七套主题,每套还带高对比度变体,全部通过 styled-components 实现。主题变量虽早已用 CSS 自定义属性定义好了,但 JavaScript 层面的主题切换工具与 styled-components 深度绑定。团队又花了两个月逐套解耦,feature flag 全程护航。到 2026 年 6 月,GitHub 正式运行在 100% CSS Modules 上。
从发现问题到彻底告别 CSS-in-JS,整整三年。但期间 GitHub 没有一次因样式迁移导致的线上事故。
你的下一个前端项目,样式方案该押哪边?
GitHub 不是孤例。2025 年 3 月,styled-components 的主维护者 Evan Jacobs 宣布项目进入维护模式——只修关键 bug,不加新功能。他甚至公开建议:"新项目不要再用 styled-components 了。"直接原因很明确:React 推出的新一代服务端组件架构(React Server Components)与运行时样式注入根本性不兼容。
包管理平台 npm 的数据也在讲同一个故事。styled-components 周下载量从 2023 年峰值约 850 万跌到了 2026 年的约 700 万,缩水近两成;另一个主流 CSS-in-JS 库 Emotion 从约 1,000 万跌到约 900 万。同期,原子化 CSS 框架 Tailwind 从约 600 万飙到约 1,200 万——翻了一倍。另一个零运行时方案 Panda CSS 也翻了一倍半。
但这不意味着 CSS-in-JS 死了。准确的说法是:运行时模型在退潮,但存量极其巨大——styled-components 累计 15 亿次下载,相当于全球每人下载过两次,大量企业应用的组件库里嵌着它的代码。新项目不推荐用,老项目也不必急着换。知名技术公司 Sanity 的工程团队做了一个过渡版本,通过 React 专为样式注入优化的新接口让初始渲染快了 40%,但明确表示只是权宜之计。
那么,新项目该选什么?如果你的应用有大量服务端渲染,或者页面组件数量会随着业务增长而膨胀——CSS Modules 或 Tailwind 这类构建时方案,现在几乎没有争议。如果你需要高度动态的用户自定义主题,CSS 自定义属性配合构建时方案已经能覆盖绝大部分场景。真正需要运行时能力的场景——比如在线设计工具的实时样式编辑——已经越来越边缘,而且这些大多不是"新项目"的典型场景。
当然,这不等于 CSS Modules 就是终极答案。迁移过程中最典型的陷阱是动态样式丢失——styled-components 里那些根据 props 变化实时切换样式的逻辑,迁移到 CSS Modules 后需要你手动把每个 prop 状态映射成一串类名,代码会变冗长。主题系统的解耦也需要工程纪律。但如果迁移方案设计得当,这些都不是不可逾越的障碍——GitHub 已经替你趟过路了。
GitHub 这次迁移的真正价值,不在于 CSS Modules 比 styled-components 好——技术选择永远因场景而异。它最有价值的部分,是向整个前端社区演示了一件事:一个服务数千万用户的巨型产品,在不停机、不崩页面的前提下,用三年时间,把引擎换了。而这套"绞杀榕 + feature flag + 过渡包装层"的手术方案,你可以直接抄走。
把样式从 JS 里拆出来,GitHub 的前端才真正轻装上阵。