直接换用 linaria 或 vanilla-extract 是唯一实质性解决 css-in-js 包体积增长的路径,因其将样式编译移至构建期,剔除运行时引擎;而 styled-components 等因硬编码基础设施无法 tree-shaking,gzip 后仍占约 28kb。

直接换用 Linaria 或 vanilla-extract 是唯一能实质性解决 CSS-in-JS 包体积增长的路径,压缩、tree-shaking、代码分割都治标不治本——因为 styled-components 和 @emotion/react 的运行时引擎本身就必须打进 bundle。
为什么 tree-shaking 对 styled-components 无效
它不是“没写好 import”,而是核心逻辑根本无法被摇掉:styled.div 依赖模板字符串解析器、css 函数依赖哈希生成与 insertRule 封装、SSR 支持需要整套服务端样式收集逻辑、dev 模式还附带校验代码。这些都在 node_modules/styled-components 里硬编码,Webpack 不会把它们当“未使用函数”删掉。
- gzip 后一个中等项目里 styled-components@6 占 JS 体积约 28KB,其中至少 22KB 是运行时基础设施
-
babel-plugin-styled-components只能优化 dev 体验(如保留组件名),不减少生产包体积 - 哪怕你只用了
styled.button,整个引擎仍完整打包
Linaria 迁移必须配对的服务端 Babel preset
Next.js / Express / Remix 等 SSR 场景下,@linaria/server.collect() 拿不到真实类名,是因为服务端没走 Linaria 编译流程——它看到的是原始 header__1a2b3c,而提取出的 CSS 文件里是 atm_bg_blue_123abc,完全匹配不上。
- 必须安装
@linaria/babel-preset(不是@linaria/core自带的那个) - 服务端入口文件(如
server.js或app/layout.tsx)要显式启用该 preset - 否则首屏 HTML 里 className 是 raw 字符串,CSS 规则全失效
vanilla-extract 要求样式与组件物理分离
它不接受“在组件文件里写 css({ color: props.primary ? 'blue' : 'gray' })”这种写法——构建插件扫描不到,直接报错 VanillaExtract: No styles found。
-
createTheme、globalStyle、styleVariants必须写在.css.ts文件里 - 动态逻辑要用
recipe+variants声明,比如按钮颜色/尺寸组合提前枚举 - SSR 必须调用
getStylesheet()或extractCss(),Vite 用户还得加vite-plugin-vanilla-extract
真正难的不是语法替换,是把“运行时决定样式”的思维切换成“构建期穷举样式组合”。一旦跨过这道坎,JS 包里就只剩类名字符串和极轻量工具函数——那些被反复抱怨的卡顿、首屏无样式、体积暴增问题,会连根消失。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











