直接换用linaria或vanilla-extract可消除80%+样式相关js体积,因其零运行时架构将样式处理全移至构建期,彻底剔除解析、哈希、insertrule等运行时逻辑,而非依赖压缩或tree-shaking。

styled-components 和 @emotion/react 的包体积膨胀不是配置没调好,是它们必须把整套样式引擎打进 bundle:模板解析器、哈希生成器、insertRule 封装、SSR 适配逻辑、dev-only 校验代码——这些都无法被 tree-shaking 清掉。
换用 linaria 或 vanilla-extract 是真正有效的解法,不是“再压一压”,而是让那部分 JS 根本不出现。
为什么直接切到 Linaria 能砍掉 80%+ 样式相关 JS 体积
Linaria 的核心机制是零运行时:所有 css 标签在构建时就被静态分析、提取为纯 CSS 文件,JS bundle 里只留下一个类名字符串(比如 "atm_bg_blue_123abc"),没有解析、没有哈希、没有动态注入逻辑。
-
@linaria/core只提供编译期类型和 AST 处理能力,不进客户端 bundle -
@linaria/react仅含轻量 hook(如useCss),无样式计算逻辑 - 你写的
css`color: ${props => props.color}`中的函数体,在构建时就被常量替换或转为 CSS 变量,不会留下闭包
迁移时最常踩的坑:Babel 配置漏掉服务端预设
如果你用 SSR(Next.js / Express / Remix),只配了客户端 Babel preset 是不够的。服务端渲染 HTML 时,collect 函数需要能识别出组件实际用到的类名——这依赖服务端也经过 Linaria 编译。
- 必须安装
@linaria/babel-preset(不是@linaria/core自带的) - 服务端入口(如
server.js或 Next.js 的app/layout.tsx)需启用该 preset - 否则
@linaria/server.collect()拿到的是原始类名(如header__1a2b3c),匹配不到提取出的 CSS 规则
原子化模式下如何避免 CSS 体积反弹
启用 @linaria/atomic 后,看似每个属性都生成独立类(atm_p_8, atm_text_blue),但体积反而可能变大——如果没控制好作用域和复用粒度。
- 不要在组件内定义仅用一次的原子组合:
const btn = css`p-4 bg-blue text-white`→ 这会生成 3 个原子类 + 1 个组合类,白费 - 优先用设计系统级原子导出:
import { p4, bgBlue, textWhite } from '@/styles/atoms' - 禁止跨文件重复定义相同语义的原子,比如
margin-16在两个地方分别写成css`margin: 16px`和css`m: 16px`,Linaria 不会合并它们
关键 CSS 提取必须配合 HTML 字符串传入 collect
@linaria/server.collect() 不是魔法函数,它靠解析 HTML 字符串里的 className 值来反查哪些 CSS 规则实际被用到。如果你在 SSR 中返回的是 React Element 对象、或用了 renderToPipeableStream 流式渲染而没拼出完整 HTML 字符串,collect 就会漏规则。
- 确保调用位置在
renderToString()或renderToStaticMarkup()之后 - 传入的 HTML 必须包含最终生成的所有类名(包括动态拼接的,如
className={`${base} ${isActive ? active : ''}`}) - 若用
getServerSideProps,收集逻辑应放在renderToString后、res.send()前
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











