WSの小屋

CSS Modules vs CSS-in-JS vs 原子化 CSS:样式方案终极横评

引言

前端 CSS 方案经历了至少三次革命:CSS Modules(2015,构建时作用域隔离)、CSS-in-JS(2014-2020,运行时样式注入)、原子化 CSS(2017-2025,Tailwind/UnoCSS 等)。每种方案都解决了一些问题,也带来新的问题。本文将深入对比这三大方案的原理、优缺点、性能影响、开发体验,并给出选型决策树。


一、CSS Modules:构建时作用域隔离

1.1 原理

CSS Modules 通过构建工具在编译时对 class 名做哈希重命名,实现样式作用域隔离。

/* Button.module.css */
.button { background: blue; color: white; }
.primary { composes: button; background: darkblue; }
import styles from './Button.module.css'
function Button({ primary }) {
  return <button className={primary ? styles.primary : styles.button}>Click</button>
}

编译后:<button class="Button_primary__a1b2c">Click</button>

1.2 优点

  • 零运行时开销:所有处理在构建时完成
  • 原生 CSS 语法:学习成本接近零
  • 作用域隔离:天然避免样式冲突
  • 支持预处理器:可无缝使用 Sass/Less/Stylus

1.3 缺点

  • 样式与组件分离:跨文件管理,组件内聚性降低
  • 复用困难:跨组件复用需要 composes 或全局 class
  • 动态样式受限:基于状态的样式切换需要多个 class

1.4 Vue 中使用

<template>
  <button :class="[$style.button, { [$style.primary]: isPrimary }]">Click</button>
</template>
<style module>
.button { padding: 8px 16px; }
.primary { background: blue; }
</style>

二、CSS-in-JS:把样式写进 JS

2.1 运行时方案(styled-components / emotion)

import styled from '@emotion/styled'

const Button = styled.button<{ primary?: boolean }>`
  background: ${props => props.primary ? 'blue' : 'gray'};
  color: white;
  padding: 8px 16px;
  &:hover { background: ${props => props.primary ? 'darkblue' : 'darkgray'}; }
`

2.2 零运行时方案(vanilla-extract / linaria)

// button.css.ts (vanilla-extract)
import { style, styleVariants } from '@vanilla-extract/css'

export const button = style({ padding: '8px 16px', color: 'white' })
export const variant = styleVariants({
  primary: { background: 'blue' },
  secondary: { background: 'gray' },
})

2.3 优缺点

  • ✅ 强类型 + 动态主题、样式与组件同文件、动态样式容易、死代码自动清理
  • ❌ 运行时开销(仅运行时方案)、包体积大、SSR 复杂、DevTools 调试难

2.4 性能基准

指标 CSS Modules styled-components vanilla-extract
首次渲染耗时 50ms 280ms 55ms
包体积(gzip) ~3KB ~12KB+ ~3KB
SSR 注水大小
DevTools 可读性 良好 良好

三、原子化 CSS:Tailwind / UnoCSS

3.1 原理

原子化 CSS 预先生成一组最小 CSS class,每个 class 只做一件事,开发者通过组合 class 表达样式。

<button className="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded">Click</button>
方案 生成时机 CSS 体积
Tailwind 4 (CSS-first) 构建时扫描 按需 ~10-30KB
UnoCSS 按需扫描 + 即时生成 极致小

3.2 UnoCSS 的可扩展性

// uno.config.ts
export default defineConfig({
  presets: [presetUno(), presetAttributify(), presetIcons()],
  rules: [
    ['m-1', { margin: '0.25rem' }],
    [/^m-(\d+)$/, ([, d]) => ({ margin: `${d / 4}rem` })],
  ],
  shortcuts: { 'btn': 'px-4 py-2 rounded' },
})
<!-- 属性化写法 -->
<button bg="blue-500 hover:blue-600" text="white" px="4" py="2" rounded>Click</button>
<i class="i-mdi-home text-2xl" />

四、核心能力对比

维度 CSS Modules CSS-in-JS (运行时) CSS-in-JS (零运行时) Tailwind UnoCSS
作用域隔离 ✅ 自动 ✅ 自动 ✅ 自动 ⚠️ 全局 ⚠️ 全局
动态样式 ⚠️ 有限 ✅ 极强 ✅ 中等 ⚠️ 有限 ⚠️ 有限
运行时开销 ❌ 无 ⚠️ 大 ❌ 无 ❌ 无 ❌ 无
包体积 🟢 小 🔴 大 🟢 小 🟢 小 🟢 极小
类型安全 ⚠️ 中 ✅ 强 ✅ 强 ⚠️ 弱 ⚠️ 弱
SSR ✅ 简单 🔴 复杂 ✅ 简单 ✅ 简单 ✅ 简单

五、选型决策树

是否需要严格的组件样式隔离?
├── 是 → 是否需要强类型和动态样式?
│   ├── 是 → vanilla-extract / Stitches
│   └── 否 → CSS Modules
└── 否
    ├── 是否重视设计系统一致性 + 极致性能?
    │   ├── 是 → Tailwind 4
    │   └── 否 → UnoCSS

各方案适用场景

场景 推荐方案 理由
大型企业后台 Tailwind / UnoCSS 复用度高、设计系统化
设计稿强自由的 C 端 styled-components 动态样式灵活
Vue 设计感组件库 CSS Modules 与 SFC 配合好
跨框架 Monorepo vanilla-extract 类型友好、零运行时
极致性能要求 UnoCSS / Tailwind 零运行时,CSS 最小

六、性能深度剖析

6.1 首屏渲染

  • CSS Modules / Tailwind:CSS 静态加载,HTML 渲染后立即应用样式
  • CSS-in-JS 运行时:JS 需先下载执行才能生成 CSS,存在 FOUC 风险

6.2 重新渲染性能

在 1k 组件重渲染场景下:

  • styled-components:~120ms(每次 props 变化都重新计算样式字符串)
  • Tailwind:~5ms(className 字符串拼接)

6.3 CSS 体积

  • Tailwind/UnoCSS:通常 10-30KB(gzip)
  • CSS Modules:按页面加载,单页面可能更小
  • styled-components:CSS + 运行时 JS,总体积大

七、迁移与共存

// before: styled-components
const Btn = styled.button<{ primary?: boolean }>`
  background: ${p => p.primary ? '#3b82f6' : '#6b7280'};
  color: white;
  padding: 8px 16px;
  border-radius: 4px;
`

// after: Tailwind
function Btn({ primary, children }) {
  return (
    <button className={cn(
      'px-4 py-2 rounded text-white hover:opacity-90',
      primary ? 'bg-blue-500' : 'bg-gray-500',
    )}>
      {children}
    </button>
  )
}

八、未来趋势

CSS Houdini + CSS Properties & Values API

@property --color-primary {
  syntax: '<color>';
  initial-value: #3b82f6;
  inherits: true;
}
/* JS 中只需更新变量 */
document.documentElement.style.setProperty('--color-primary', '#ef4444')

原生 CSS Nesting

.button {
  background: blue;
  &:hover { background: darkblue; }
  &.primary { background: red; }
}

九、总结

三大方案各有定位:

  • CSS Modules —— 经典、稳定、低开销
  • CSS-in-JS —— 动态灵活,建议优先选零运行时方案
  • 原子化 CSS —— 性能极致、设计系统化

没有最好的方案,只有最合适的方案。选型时关注:团队规模、项目生命周期、性能要求、设计系统自由度。

Comments | 0条评论