Skip to content

启程:为什么选择 UnoCSS

关于样式工程,首先要问一个问题:我们到底在解决什么工程问题?

CSS 的问题,从来不是"能不能实现效果"——任何一个前端开发者都能用 CSS 把设计稿还原出来。真正的问题在于能不能在多人协作下长期维护,能不能在复杂产品形态下保持一致性,能不能在频繁需求变更中快速响应,以及能不能在性能和体积上做到可控。

思考一下,你有没有经历过这样的场景:项目里的 CSS 文件越来越大,却没人敢删任何一行代码,因为不知道哪里还在用?或者说,团队里每个人对"间距应该用多少像素"的理解都不一样,最后设计稿和实现总是对不上?

这才是样式工程的真正痛点。

本章的目标,是把 UnoCSS 放回这个工程背景下,解释为什么在今天的前端栈中,它值得被认真评估和采用。


1. 样式工程的长期矛盾

现在我要问第二个问题:为什么样式工程这么难做好?

答案是:样式工程始终在几组矛盾之间摇摆。第一组是表达自由度与约束规范之间的矛盾,CSS 太自由了,同一个效果有无数种写法。第二组是局部开发效率与全局可维护性的矛盾,写代码时很爽,维护时想哭。第三组是灵活定制与设计一致性的矛盾,每个人都在"灵活定制",最后产品长得像拼图。第四组是快速迭代与构建性能的矛盾,功能加得越多,打包越慢,文件越大。

回顾主流方案的阶段性特点,你会发现一个有趣的现象——每个方案都在解决上一个方案的问题,同时引入新的问题

先来看原生 CSS 加上约定式命名的方案,比如 BEM。这种方案的好处是没有工具绑架,学习成本也低。但它的代价同样明显:命名困难,样式复用的粒度太粗,而且样式文件会持续膨胀。想想看,你有多少时间花在了"这个类名叫什么好"这个问题上?

再来看预处理器,比如 Sass 和 Less。它们引入了变量、mixin、嵌套等特性,确实提高了复用能力。但工程规模变大后,样式依然集中在大文件中,难以做到真正的"按需使用"。你定义了 200 个颜色变量,但真正用到的可能只有 20 个,其余的全都变成了死代码。

然后是 CSS Modules 和 CSS-in-JS。它们引入了模块化和作用域隔离,避免了全局污染的问题。但代价是样式与逻辑耦合程度提高了,运行时开销和构建复杂度都显著增加。

有没有发现?这些方案都在做权衡,都在牺牲一些东西来换取另一些东西。Tailwind CSS 的流行则标志着另一个方向:原子化 CSS


2. 原子化 CSS 的价值与瓶颈

现在要问第三个问题:原子化 CSS 解决了什么问题?又带来了什么新问题?

原子化 CSS 的核心理念是:通过组合单一职责的工具类来构建界面

假设你要实现一个按钮,传统写法是这样的:

css
/* 你需要先想一个类名 */
.primary-button {
  padding: 8px 16px;
  background-color: #3b82f6;
  color: white;
  border-radius: 4px;
}

而原子化的写法是:

html
<button class="px-4 py-2 bg-blue-500 text-white rounded">
  按钮
</button>

有没有感觉到不同? 你不需要再绞尽脑汁想类名了,也不用在 CSS 文件和 HTML 文件之间来回切换。

这类方案带来的工程收益是显而易见的。首先,你不再需要为每个组件起独立的类名,降低了命名负担——样式即组合。其次,一套原子类可以复用到无数组件上,复用性极强。最后,从类名基本可以推测最终样式效果,实现了更直接的"所见即所得"。

但是(技术选型没有"但是"怎么行呢),在大规模使用时,原子化方案自身也暴露出一些新问题。

第一个问题是框架的"主见"太强。Tailwind 自带一套完整的设计系统——spacing 是 4 的倍数、颜色有 50-950 的色阶、字号有 xs 到 9xl。这本身没问题,但如果你的设计师说"我要 18px 的间距",你就得开始和 Tailwind 的配置系统搏斗了。

第二个问题是定制成本高。当项目需求超出默认能力边界时,你需要深入理解 Tailwind 的插件机制、配置结构、PostCSS 流程。对于一个"只想用工具类"的开发者来说,这个学习成本不低。

第三个问题是构建性能。在 Vite、Webpack 等工具链下,构建性能与 HMR 体验会随着类名数量、扫描文件量增加而恶化。你改了一行代码,热更新却要等好几秒,这种体验谁受得了?

换句话说,原子化本身是合理方向,但**"带强主见的原子化框架"**,在一些工程环境里会成为新的限制。


3. UnoCSS 的定位:原子化 CSS 引擎

现在我要问第四个问题:有没有一种方案,既能享受原子化的好处,又能避免框架的限制?

UnoCSS 的回答是:做引擎,不做框架

它不是"一个新的 Tailwind",而是一个按需生成的原子化 CSS 引擎。这句话包含三层含义,我们一个一个拆解。

3.1 引擎,而不是框架

思考一下,框架和引擎有什么区别?

框架告诉你"应该怎么做"——它提供一套完整的解决方案,你按它的规则来。Tailwind 就是这样,它告诉你间距用 4 的倍数、颜色用这套色板、响应式用这些断点。

引擎只告诉你"怎么把类名变成 CSS"——它不预设任何设计语言,只提供转换能力。

UnoCSS 核心只关心一件事:如何基于一组规则,将类名映射为 CSS 片段。它不强行插入设计语言,不强迫你接受某种 spacing 或 color 体系,样式设计由你来定义,UnoCSS 负责"按需生成"和"高效维护"。

3.2 按需生成(On-demand Generation)

传统方案怎么处理 CSS 体积问题?一般是两种思路。第一种是预生成加裁剪,先生成一大堆 CSS,构建时再把没用的删掉。第二种是扫描加生成,在构建期做一次集中扫描,生成对应产物。

这两种方式都有一个问题:处理顺序是反的。先生成一堆东西,再去删减,本身就是浪费。

UnoCSS 从一开始就以即时按需为前提设计。扫描到类名时,即时匹配规则并生成对应 CSS。没有使用的类,永远不会出现在产物中。开发时改动一个类名,几乎是毫秒级响应。

这才是真正的"按需"——不是"生成完再删",而是"要什么生成什么"。

3.3 高度可组合的规则体系

UnoCSS 的另一个特点是一切皆可组合。规则定义"类名如何映射为 CSS",变体定义"hover:md: 等前缀如何修饰选择器",预设则是一组规则、变体、主题的集合。

你可以完全使用 preset-wind 这种 Tailwind 风格的预设,如果你喜欢 Tailwind 的类名的话。或者只启用 preset-mini 这种更精简的预设。也可以从零开始构造自己的预设体系。

这种灵活性意味着什么? UnoCSS 更适合作为设计系统的承载层,而不是一个"设计系统本身"。


4. 和 Tailwind 等方案的核心差异

可能很多人会问:说了这么多,UnoCSS 和 Tailwind 到底有什么本质区别?

这是个好问题。我们不做"孰优孰劣"的价值判断,只从能力边界与成本结构的角度分析。

4.1 设计主见程度

Tailwind 的哲学是:我给你一套成熟的设计系统,你按我的来就好。这本身没问题,甚至对很多项目来说是好事——不用自己设计 spacing scale,不用纠结颜色命名。但代价是:当你需要突破这套体系时,定制成本会变高。

UnoCSS 的哲学则是:你告诉我你要什么,我帮你生成。核心默认几乎"无主见",真正具备"主见"的是可插拔的预设。你可以选择接受某个预设的设计语言,也可以完全自定义。

4.2 构建模型与性能

这里有一个关于性能的真相:UnoCSS 比 Tailwind 快,不是因为代码写得好,而是因为架构不同

Tailwind 基于 PostCSS,需要扫描所有文件、提取类名、生成 CSS、再通过 PostCSS 处理。UnoCSS 从一开始就为"即时"设计:扫描到类名、匹配规则、输出 CSS。步骤更少,中间状态更少,自然更快。

但这也有代价:如果你习惯了 PostCSS 生态的各种插件,迁移到 UnoCSS 可能需要找替代方案。

4.3 可编程扩展能力

这是 UnoCSS 最强大的地方。

在 Tailwind 中,如果你想自定义一个规则,需要学习它的插件 API:

js
// Tailwind 插件
plugin(function({ addUtilities }) {
  addUtilities({
    '.custom-class': {
      // ...
    }
  })
})

在 UnoCSS 中,规则就是一个简单的映射函数:

ts
// UnoCSS 规则
rules: [
  ['custom-class', { /* CSS */ }],
  [/^m-(\d+)$/, ([, d]) => ({ margin: `${d * 4}px` })],
]

有没有感觉到区别? UnoCSS 的规则更直观、更可组合、更容易理解。


5. UnoCSS 解决的关键工程问题

理论讲完了,现在来看看实际能解决什么问题。

5.1 样式体积的可控性

按需生成是 UnoCSS 的默认行为,不是附加优化策略。未出现在源码中的类名,对构建产物没有任何成本。你不必维护庞大的"基础 CSS 文件 + Purge 配置",通过 safelist 还可以精确控制运行时拼接的类名。

对于体量较大的前端应用,这意味着 CSS 产物始终与代码状态一致,不会随着项目增长而无限膨胀。

5.2 统一设计语言与代码路径

假设你的设计师说:"我们的品牌色是 #3b82f6,主要间距是 16px 和 24px"。

在 UnoCSS 中,你只需要在 theme 中定义一次:

ts
theme: {
  colors: {
    brand: '#3b82f6'
  },
  spacing: {
    md: '16px',
    lg: '24px'
  }
}

然后全项目都用 bg-brandp-mdp-lg设计语言和代码语言对齐了。当设计师说"品牌色要换成 #2563eb",你改一行配置,全项目生效。

5.3 可维护的定制能力

当原子类体系不足以覆盖业务需求时,常见做法是直接写覆盖样式,或者在 CSS 层重新定义一批"组件类"。长期来看,这会重新走回"散落在各处的样式定义"的老路。

在 UnoCSS 中,我们推荐的路径是将高频复合样式抽象为快捷方式(Shortcuts),将业务专用抽象设计为单独的预设模块,对于跨领域能力(如图标系统)则通过专门的预设接入。所有"新增样式能力"都以规则或预设的形式集中管理,不会出现"某个组件局部写了一段 CSS,没人记得它在哪里"的情况。

5.4 工具链集成与开发体验

UnoCSS 在工具链集成方面提供了官方的 Vite、Webpack、Nuxt 等框架集成方案。VS Code 插件可以高亮和提示类名,Inspector 工具可以调试当前页面实际使用到的规则。从编辑器到运行时的闭环支持,可以显著降低学习成本和调试成本。


6. 什么场景适合 UnoCSS

UnoCSS 不是银弹,不是每个项目都必须使用。但在某些场景中,它往往能体现明显优势。

中大型前端应用是第一类适合的场景,比如后台管理、数据可视化平台、多业务门户等。这类项目样式规模大、组件体系复杂,需要统一设计语言,同时保留部分业务线的差异化能力。

多项目共享组件库或设计系统是第二类适合的场景。如果你希望用一套规则或预设,在多个项目中提供统一的"样式 API",需要快速扩展新的设计 token 而不破坏现有项目,UnoCSS 的可组合性会非常有价值。

需要高度定制的品牌项目是第三类适合的场景。当默认的 Tailwind 等预设体系不完全符合品牌视觉,而你又不希望为了贴合框架而妥协设计时,UnoCSS 的"无主见"特性就能发挥作用。

追求构建与 HMR 性能的团队是第四类适合的场景。如果你的团队对开发体验敏感,对热更新延迟要求高,站点或系统规模较大,希望 CSS 产物保持稳定可控,UnoCSS 的即时按需生成架构会带来明显的体验提升。


7. 什么场景需要谨慎评估

理性地看,UnoCSS 并不适合所有团队、所有项目。

完全静态的小型站点或营销页是第一类需要谨慎评估的场景。如果样式简单、变更频率低,直接写少量 CSS 或使用 UI 框架即可。引入 UnoCSS 可能会让工程复杂度高于收益。

团队完全没有原子化 CSS 使用经验是第二类需要谨慎评估的场景。UnoCSS 的思路默认是"类名即样式 API",如果团队尚未适应原子化思维,强行导入会带来认知和审查成本。这种情况下,可以先从 Tailwind 这类"更有主见"的方案起步,等团队习惯后再迁移。

对工具链控制力较弱的场景是第三类需要谨慎评估的情况。UnoCSS 强调与构建工具深度集成,如果技术栈过于封闭,无法接入定制构建,收益会受限。

记住:不要为了"新"和"酷"而使用 UnoCSS,而是要从项目的生命周期、团队能力、未来规划出发,评估它是否真正解决你的问题。


8. 小结

本章我们从样式工程的痛点出发,回答了几个关键问题。

样式工程难在哪?始终在表达自由与约束规范、开发效率与可维护性之间摇摆,每个方案都在做权衡。

原子化 CSS 解决了什么?降低命名负担、提高复用性、让样式更可预测。但"带强主见的框架"会带来定制成本和性能问题。

UnoCSS 有什么不同?它是引擎而不是框架——按需生成、高度可组合、不强加设计主见。

什么时候用 UnoCSS?中大型应用、设计系统、高定制需求、性能敏感场景。小项目或原子化新手可以先观望。

在后续章节中,我们会以更具工程视角的方式展开。在"快速上手"中,我们会搭建一个最小可用的 UnoCSS 项目。在"核心概念"中,我们会系统性梳理规则、预设、变体的抽象模型。随后的章节会分别从工具类、核心功能、高级定制等维度展开。

如果你所在的团队正在寻找一套既能支撑长期演进,又不过度绑架设计的样式基础设施,那么 UnoCSS 值得你继续读下去。

启程:为什么选择 UnoCSS has loaded