技术知识文章集合TECHNICAL ARCHIVE · 807 DOCUMENTS

显示模式

登录
ARCHIVE DOCUMENTCSS

从 Tailwind CSS 到 UnoCSS——原子化真的是现代前端 CSS 的救星吗?

所属分类
CSS
文件格式
Markdown
原始路径
CSS/20-从 Tailwind CSS 到 UnoCSS —— 原子化真的是现代前端CSS的救星吗
本文目录10 个章节
  1. 小编前言
  2. 概念:原子化 CSS
  3. Tailwind CSS 广受欢迎
  4. UnoCSS
  5. 原子化 CSS 并非现代前端 CSS 的救星
  6. Tailwind CSS 与 UnoCSS 的对比
  7. 使用原子化 CSS 的实践建议
  8. 结语
  9. 总结
  10. 参考资料

从 Tailwind CSS 到 UnoCSS——原子化真的是现代前端 CSS 的救星吗?

Category(分类): CSS Status: 未知

小编前言

追忆往昔,CSS 曾经是前端“三剑客”之一。随着前端工程化和组件化的快速发展,CSS 在现代前端开发中的存在感似乎没有 JavaScript 和框架那么强,但它仍然是构建用户界面不可缺少的一部分。

在了解 UnoCSS 之前,我甚至没有认真接触过“原子化 CSS”(Atomic CSS)这个概念。原子化 CSS 的概念,以及 Tailwind CSS、UnoCSS 的设计方式都比较直接:把常用样式拆分成小粒度的工具类,然后在 HTML 或组件模板中组合使用。

本文主要讨论:原子化 CSS 是不是现代前端解决 CSS 问题的最佳方案?Tailwind CSS 和 UnoCSS 应该如何选择?

文中涉及的工具版本变化较快。Tailwind CSS v3、Tailwind CSS v4 和 UnoCSS 的配置方式并不完全相同,示例会尽量标注版本差异。

概念:原子化 CSS

原子化 CSS 是一种 CSS 架构方式,它倾向于使用小巧、用途单一的 class。class 通常描述一个具体的样式规则或样式状态,而不是一个完整的页面组件。

原子化 CSS 的实现方式并不复杂:预先定义或按需生成大量职责单一的 class,开发时直接在 DOM 或组件模板中组合这些 class,而不必为每个简单样式单独编写选择器。

先预置一组 class

.m-10 {
  margin: 10px;
}

.p-5 {
  padding: 5px;
}

.text-red {
  color: red;
}

在模板中组合 class

<div class="m-10 p-5 text-red">
  测试 DOM
</div>

这些 class 通常遵循一定的命名规律。开发者掌握规则后,可以通过组合多个 class 快速实现效果。

这种思想并不是最近才出现的。Bootstrap 的工具类、早期的 OOCSS、BEM 辅助类和其他 Utility-first CSS 方案,都包含类似的思想。因此,Tailwind CSS 和 UnoCSS 可以看作是对这种思路的现代化、工程化和自动化实现。

Tailwind CSS 广受欢迎

Tailwind CSS 介绍图片

Tailwind CSS 在 GitHub 和 npm 生态中都获得了很高的关注度。原文曾记录 Tailwind CSS 仓库约有 70.8K 个 Star、npm 周下载量超过六百万,但这些数字会持续变化,只能视为文章发布时的历史数据,不能当作当前数据使用。

Tailwind CSS 主张让开发者无需频繁离开 HTML 或组件模板,就能快速构建界面,并提供了许多实用能力:

  • 粒度较小的工具类,减少手写重复样式;
  • 可扩展的主题、颜色、间距和断点配置;
  • 根据源码扫描结果生成实际使用到的 CSS,减少生产环境冗余;
  • 方便的响应式变体和状态变体;
  • 函数、指令、布局、动画和插件系统;
  • 与 Vue、React、Svelte 等组件框架结合使用。

这些能力覆盖了很多原生 CSS 开发中常见的重复劳动,因此 Tailwind CSS 在不少项目中很实用。

Tailwind CSS 的生成方式

Tailwind CSS v3 默认使用 JIT(Just-in-Time)模式。它会扫描配置的源码文件,在开发和生产过程中生成检测到的工具类,而不是简单地等到生产构建阶段才清除 CSS。

需要注意,Tailwind 的源码扫描通常只能识别完整、静态出现的 class 字符串。下面这种动态拼接可能无法被检测到:

const color = 'red'
const className = `bg-${color}-500`

更稳妥的写法是提供完整的候选值:

const colorClasses = {
  red: 'bg-red-500',
  blue: 'bg-blue-500'
}

const className = colorClasses[color]

如果 class 来自 CMS、数据库或其他运行时数据,还需要配置 safelist,或者通过明确的映射限制可使用的样式集合。

Tailwind CSS 与 PostCSS

Tailwind CSS 可以与 PostCSS 等工具结合,但配置方式取决于版本。

Tailwind CSS v3 的配置通常类似:

// postcss.config.cjs
module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {}
  }
}

Tailwind CSS v4 使用单独的 PostCSS 插件:

// postcss.config.mjs
export default {
  plugins: {
    '@tailwindcss/postcss': {}
  }
}

PostCSS 是 CSS 转换平台,Tailwind CSS 是可以接入构建流程的工具之一。Autoprefixer、CSS Modules、Sass 等也可以在不同项目中与 PostCSS 配合使用,但它们解决的问题并不相同。

UnoCSS

Tailwind CSS 在过去几年广受欢迎,UnoCSS 也在 Vite、Vue 等现代前端生态中获得了较多关注。UnoCSS 官网的图标、文字和背景色渐变联动主题也很有特色。

UnoCSS 与 Vite、Vue 3 集成示意图

UnoCSS 的定位不是简单替代 Tailwind CSS,而是提供另一个原子化 CSS 引擎。它通过规则、预设、提取器和转换器组合出适合项目的生成方案。

UnoCSS 的作者 Anthony Fu 长期参与 Vue、Vite 和前端工具链生态。将 UnoCSS 的设计归因于某一个工具或团队并不准确,但它确实体现了现代构建工具对按需生成和开发体验的重视。

1. 按需生成

UnoCSS 会根据源码中实际出现的候选 class 生成 CSS,开发和生产环境都可以使用按需生成:

  • 只生成业务代码中实际使用到的规则;
  • 开发时通过文件监听或构建工具管道及时更新;
  • 生产构建时继续根据源码生成最终 CSS;
  • 避免提前生成一个包含大量可能用不到的工具类的完整 CSS 文件。

Tailwind CSS v3 的 JIT 模式也已经具备类似的按需生成能力,因此不能再简单地说“Tailwind 只有生产环境才清理,UnoCSS 开发环境才按需生成”。两者的扫描方式、默认规则和构建集成仍然有所不同。

传统的完整 CSS 生成方式

按需生成 CSS 的方式

UnoCSS 默认的提取流程通常不需要完整解析语言 AST,而是通过提取器从源码中识别候选 token,再使用规则进行匹配。不过这不是“任何情况下都不使用 AST”:不同文件类型可以配置不同 Extractor,某些 Transformer 或框架集成也可能进行额外解析。

2. 极具灵活性

UnoCSS 更准确的定位是一个 CSS 引擎,而不是一个单独的 UI 框架。它通过 Preset 提供规则集合,可以使用 Tailwind 风格、Windi 风格或自定义风格的工具类。

Tailwind CSS 本身不是 UnoCSS 的官方 preset。当前项目通常使用 presetWind3() 或 presetWind4() 获得 Tailwind 风格的工具类。

UnoCSS 的现代 Vite 配置示例

// vite.config.ts
import { defineConfig } from 'vite'
import UnoCSS from 'unocss/vite'
import {
  presetAttributify,
  presetWind3
} from 'unocss'

export default defineConfig({
  plugins: [
    UnoCSS({
      presets: [
        presetWind3(),
        presetAttributify()
      ]
    })
  ]
})

如果项目使用的是 UnoCSS 的旧版本,导入路径和 preset 名称可能不同,应以对应版本的官方文档为准。应用入口还需要引入生成的虚拟样式表:

// main.ts
import 'virtual:uno.css'

属性化书写(Attributify)

原文中的属性化示例可以改写为:

<!-- 普通工具类写法 -->
<button class="bg-blue-400 hover:bg-blue-500 text-sm text-white font-mono font-light py-2 px-4 rounded border-2 border-blue-200 dark:bg-blue-500 dark:hover:bg-blue-600">
  Button
</button>

<!-- UnoCSS Attributify 写法 -->
<button
  bg="blue-400 hover:blue-500 dark:blue-500 dark:hover:blue-600"
  text="sm white"
  font="mono light"
  p="y-2 x-4"
  border="2 rounded blue-200"
>
  Button
</button>

属性化写法需要启用 presetAttributify(),并不是安装 UnoCSS 后自动生效。不同 HTML 属性、Vue 组件属性和 JSX 属性可能发生冲突,需要根据框架配置前缀或 Transformer。

3. 自定义规则和快捷方式

UnoCSS 支持静态规则、动态规则和 Shortcuts,可以把重复的工具类组合成更有语义的名称:

import { defineConfig, presetWind3 } from 'unocss'

export default defineConfig({
  presets: [presetWind3()],
  shortcuts: {
    btn: 'px-4 py-2 rounded text-white bg-blue-500 hover:bg-blue-600',
    'btn-secondary': 'btn bg-gray-500 hover:bg-gray-600'
  },
  rules: [
    ['content-auto', { 'content-visibility': 'auto' }],
    [/^skew-(\d+)$/, ([, d]) => ({
      transform: `skewY(-${d}deg)`
    })]
  ]
})

动态规则需要注意输入来源。不要把未经限制的用户输入直接当作 class 名生成 CSS,否则可能导致不可控的样式膨胀或构建结果不稳定。

4. 编译和开发体验

UnoCSS 的按需生成和构建工具集成可以减少不必要的 CSS 生成,并改善开发时的反馈速度。但最终性能还取决于:

  • 源码扫描范围;
  • 使用的 Extractor 和 Transformer;
  • 自定义规则数量;
  • 是否开启 Source Map;
  • 开发服务器和构建工具配置;
  • 项目本身的组件数量和文件规模。

因此,不应把“UnoCSS 一定比 Tailwind CSS 快”作为绝对结论。应在具体项目中进行构建时间、开发启动时间和产物体积测试。

原子化 CSS 并非现代前端 CSS 的救星

Tailwind CSS 与 UnoCSS 的特性和使用方法并不是本文唯一想讨论的内容。更重要的问题是:

Tailwind CSS 或 UnoCSS 的原子化 CSS,会是现代前端解决 CSS 问题的最佳实践吗?

从实际使用看,原子化 CSS 更像是一种辅助型的工程工具,而不是所有 CSS 问题的统一解决方案。它在某些业务场景或业务阶段可以提供很大帮助,但面对复杂、多变的前端业务时,也存在边界。

可能出现的问题

  • 当页面结构和交互复杂度提升后,大量工具类可能让模板变长;
  • 如果没有设计令牌和组件边界,相同视觉模式可能出现多种不同的 class 组合;
  • 动态拼接 class 可能无法被 Tailwind 或 UnoCSS 的源码扫描器识别;
  • 工具类组合本身不一定表达业务语义,阅读者需要熟悉项目的设计系统;
  • 过度依赖任意值和复杂变体,可能导致样式规则难以统一;
  • 预设、插件和自定义规则过多时,项目的学习成本也会增加。

不过,原文中“原子化 CSS 无法做组件化拆分”的说法并不准确。原子化 CSS 与 Vue、React 的组件化并不冲突:组件负责封装结构、状态和行为,原子类负责描述样式,二者可以配合使用。

例如,可以把重复的原子类组合封装为组件:

<template>
  <button class="btn-primary">
    <slot />
  </button>
</template>

<style>
.btn-primary {
  /* 这里可以使用普通 CSS、@apply 或构建工具提供的快捷方式 */
}
</style>

在 UnoCSS 中,也可以用 Shortcuts 把重复组合抽象为 btn-primary。Tailwind CSS 也可以通过组件封装、@apply 或语义化 CSS 类解决重复问题。

原子化 CSS 与组件化、函数式编程

从设计思想上看,原子化 CSS 把样式拆成小粒度规则,组件化把界面拆成可复用单元,函数式编程把逻辑拆成可组合函数。这些思想并不互相排斥。

真正需要避免的是:

  • 把几百个工具类直接堆在所有模板中;
  • 让组件内部出现大量难以理解的动态 class 拼接;
  • 不区分页面级布局、组件级样式和设计令牌;
  • 为了追求原子化而放弃语义化组件边界。

适用场景

原子化 CSS 在以下场景中通常有较大优势:

  • 快速开发响应式 H5 页面;
  • 业务复杂度较低的中后台系统;
  • 简单的官网、营销页和活动页;
  • 设计规范明确,间距、颜色和排版高度统一的项目;
  • 需要快速验证设计稿或搭建原型的项目;
  • 已经建立了组件库和设计令牌的团队。

在以下场景中需要更加谨慎:

  • 复杂的 C 端业务和大型系统;
  • 需要长期维护的多团队项目;
  • 样式高度依赖语义、主题和复杂交互状态的组件;
  • 运行时大量生成未知 class 的 CMS 或低代码系统;
  • 需要兼容旧浏览器且构建链较复杂的项目。

这并不意味着复杂项目不能使用原子化 CSS,而是需要配合组件封装、Shortcuts、设计令牌、样式层级和源码扫描配置共同使用。

Tailwind CSS 与 UnoCSS 的对比

对比项Tailwind CSSUnoCSS
定位成熟的 Utility-first CSS 框架可扩展的按需原子化 CSS 引擎
规则来源官方主题、插件和配置Preset、Rules、Shortcuts 和 Transformer
生成方式v3 默认 JIT,v4 使用新的引擎按需提取和生成
生态成熟度文档、组件库和社区更成熟灵活性和 Vite 集成体验突出
默认风格Tailwind 设计系统可使用 Wind3/Wind4 或自定义规则
学习成本规则稳定,资料较多配置自由,但组合能力较多
适合人群希望快速使用成熟方案的团队希望高度定制生成规则的团队

两者并不存在绝对的优劣。选择时应考虑团队经验、现有构建工具、设计系统、浏览器支持和长期维护成本。

使用原子化 CSS 的实践建议

1. 先建立设计令牌

统一颜色、间距、字体、圆角和断点,避免团队成员随意使用大量任意值:

:root {
  --color-primary: #2563eb;
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --radius-md: 0.375rem;
}

2. 为重复模式建立组件或 Shortcuts

不要把同一组几十个 class 在页面中复制多次。可以使用 Vue/React 组件、Tailwind 组件层、UnoCSS Shortcuts 或普通 CSS 类进行抽象。

3. 处理动态 class

构建工具通常无法可靠识别这种写法:

`text-${color}-500`

应使用完整的映射表或显式 safelist,确保开发和生产环境生成相同的 CSS。

4. 控制任意值的使用

任意值适合少量特殊场景,不应成为绕过设计系统的常规手段:

<div class="w-[137px]">特殊宽度</div>

如果同一个值反复出现,应把它加入主题或设计令牌。

5. 关注可访问性

原子化 CSS 只负责生成样式,不会自动保证可访问性。仍然需要检查:

  • 颜色对比度;
  • 键盘焦点状态;
  • 交互元素的语义;
  • 响应式布局;
  • 动画和减少动态效果设置;
  • 屏幕阅读器的内容结构。

结语

原子化 CSS 并不是现代前端 CSS 的“救星”,但它确实是一种非常实用的工程工具。

对于快速开发响应式页面、设计规范明确的产品和已经具备组件体系的团队,Tailwind CSS 或 UnoCSS 可以减少重复样式、统一设计规则并提高开发速度。

对于复杂业务,也不需要简单地排斥原子化 CSS。可以通过组件封装、Shortcuts、设计令牌、语义化类名和普通 CSS 的组合,避免模板中出现难以维护的 class 堆叠。

Tailwind CSS 更适合希望使用成熟生态和约定的团队;UnoCSS 更适合希望按需生成、高度定制和深入控制规则的团队。最终选择应以项目实际需求、团队习惯和长期维护成本为依据,而不是追逐某个工具的流行程度。

总结

  1. 原子化 CSS 是一种样式组织方式,不是完整的 CSS 架构答案;
  2. Tailwind CSS v3 已默认采用 JIT 按需生成,不能再简单描述为“只在生产环境清理 CSS”;
  3. Tailwind CSS v4 的 PostCSS 集成使用 @tailwindcss/postcss,配置方式与 v3 不同;
  4. UnoCSS 是 CSS 引擎,通常通过 Preset、Rules、Shortcuts 和 Extractor 生成样式;
  5. presetWind3() / presetWind4() 可以提供 Tailwind 风格能力,但 Tailwind CSS 本身不是 UnoCSS 的 preset;
  6. UnoCSS 默认提取流程通常不依赖完整 AST,但不能绝对说所有处理都不使用 AST;
  7. 原子化 CSS 与 Vue、React 组件化并不冲突,组件封装可以减少 class 重复;
  8. 复杂项目可以组合使用原子类、语义化组件、设计令牌和普通 CSS;
  9. 动态 class、任意值、构建扫描范围和可访问性是使用时需要重点关注的问题;
  10. Tailwind CSS 与 UnoCSS 的选择应结合生态、性能、配置灵活性和团队维护能力。

参考资料

807 DOCUMENTS · 12 COLLECTIONS
ARCHIVE SEARCH807 篇文章