vanilla-extract
TypeScript-first 的零运行时(zero-runtime)样式方案——官方标语「Zero-runtime Stylesheets-in-TypeScript」。当前版本 @vanilla-extract/css 1.21.1(MIT)。它的核心模型是把样式写进独立的 .css.ts 文件:用 style()、createTheme()、keyframes() 等 API 以 TypeScript 对象声明样式,构建期由打包器插件执行这些文件、抽取并生成静态 CSS 文件 + 局部作用域化的类名/变量名,运行时零样式生成开销。它和 Sass/Less 一样「样式在构建期就固化」,却又拥有 TypeScript 的类型安全——设计令牌、主题契约(theme contract)都带类型,改名/漏值编译期即报错。围绕核心包还生长出一整套官方生态:@vanilla-extract/recipes(多变体组件样式,类似 cva/Stitches)、@vanilla-extract/sprinkles(零运行时、类型安全的原子化工具类,可自建 Tailwind/Styled System)、@vanilla-extract/dynamic(assignInlineVars 运行时改 CSS 变量值实现动态主题),以及 Vite/webpack/esbuild/Next/Rollup/Parcel/Gatsby/Astro 各打包器插件。放进「CSS-in-JS」谱系里,它与 StyleX、Panda CSS 同为零运行时,差异在于它是 TS-first 的 .css.ts 文件模型——样式与组件分离、更贴近手写 CSS 的心智,而非 Babel 就地共置或配置驱动 codegen。
评价
优点
- 真·零运行时 + 静态 CSS:样式在构建期编译成静态 CSS,浏览器运行时不注入样式,包体更小、可被 CDN/HTTP 缓存,天然对 SSR 与 React Server Components 友好,避开 styled-components/Emotion 运行时注入与注水(hydration)开销
- 端到端类型安全:用 TS 写样式,属性由 csstype 校验;
createTheme/createThemeContract让设计令牌成为类型化契约,主题结构变更、漏赋值都在编译期暴露,杜绝令牌漂移 - 作用域化彻底:类名、动画名(
keyframes)、字体名(fontFace)、变量名(createVar)、容器名(createContainer)全部自动哈希、局部作用域,从根上消除全局命名冲突 .css.ts文件模型清爽:样式与组件分离、无标签模板 DSL,style([base, ...])数组组合、selectors/@media/@supports/@container/@layer一应俱全,接近手写 CSS 的直觉- 生态完整:recipes 管多变体、sprinkles 管原子化、dynamic 管运行时动态变量,三者可组合,覆盖从设计系统到运行时主题的全链路
- 框架 & 打包器无关:产出的是中立的类名字符串,React/Vue/Solid/Svelte 皆可用;官方覆盖 Vite/webpack/esbuild/Next/Rollup/Parcel/Gatsby/Astro
缺点
- 必须走构建期编译:依赖打包器插件处理
.css.ts,无法像运行时 CSS-in-JS 那样「零配置直接跑」,也不能在浏览器里裸引入 - 静态求值约束:
.css.ts在构建期执行,值必须能静态求值——不能直接用组件 props、window、请求数据算样式,运行时动态只能走 CSS 变量占位 +assignInlineVars,心智需转变 - 复杂选择器有规则:
selectors里选择器的目标必须是当前元素(&在主语位),给后代上样式得改用globalStyle,循环引用要用 getter,初学易踩 - 样板与概念偏多:主题契约、sprinkles 的
defineProperties/createSprinkles、recipes 的四段配置等 API 面较大,小项目可能觉得重 - 社区体量小于头部方案:相比 Tailwind、styled-components 生态与教程数量偏少,遇到边缘问题多需读官方文档
文档地址
vanilla-extract 官网 | Styling | Theming | Sprinkles | Recipes
GitHub 地址
vanilla-extract-css/vanilla-extract