Skip to content

Tailwind 底层生成机制 & purge 原理,是理解「为什么这样写才安全 / 高效」的关键。

我会按 “从构建 → JIT → purge” 的完整链路给你拆,本文结合 Vite / Vue / 编译原理方向的背景来讲。

一、整体流程总览(先有全局图)

Tailwind 构建时大概经历:

tailwind.config.js

解析 theme / plugins

生成“候选类名规则表”

扫描 content(源码)

JIT 即时生成 CSS

输出最终 CSS

📌 关键点

  • ❌ 不是预生成所有类
  • ✅ 是 按需生成(JIT)

二、Tailwind JIT 是怎么工作的?

1️⃣ 早期(v2 之前)

  • 预生成 上万条 CSS
  • 再 purge 没用的
  • 构建慢、CSS 大

2️⃣ 现在(v3+,默认 JIT)

核心思想

看到一个 class → 立刻生成一条 CSS

例如:

html
<div class="px-4 text-red-500"></div>

JIT 会:

  1. 拆分 class token
  2. 匹配规则
  3. 查 theme 值
  4. 生成 CSS
css
.px-4 { padding-left: 1rem; padding-right: 1rem }
.text-red-500 { color: #ef4444 }

3️⃣ 内部抽象模型(重要)

每一个 class = 一个「函数调用」

txt
text-red-500
│    │    │
│    │    └── scale value
│    └────── theme key
└────────── utility name

等价于:

js
text(color = theme.colors.red[500])

📌 所以 Tailwind 非常像一个 DSL

三、content 扫描机制(purge 的核心)

1️⃣ 扫描的是什么?

js
content: ['./src/**/*.{vue,js,ts,html}']

Tailwind 会:

  • 读取文件
  • 正则 抽取可能的 class token

例如:

vue
<div class="px-4 text-red-500"></div>

会被拆成:

txt
px-4
text-red-500

2️⃣ 关键事实(很多人不知道)

Tailwind 不理解 JS / Vue 语法

它只是扫描“字符串”

📌 所以👇

js
`bg-${color}-500`

扫描结果:

txt
bg-
-color-
-500

👉 匹配失败 → 不生成 CSS

四、为什么动态 class 会被 purge 掉?

原因不是 purge,而是:

JIT 根本“看不到完整类名”

例如:

vue
<div :class="`bg-${type}-500`"></div>

Tailwind 扫描到的只是:

txt
bg-
-type-
-500

所以:

  • 没有合法 token
  • 不会触发生成

五、JIT 的 token 匹配规则(很重要)

合法 token 示例

txt
bg-red-500
hover:bg-blue-500
md:hover:text-sm

内部处理顺序

txt
variants → utility → value

例如:

txt
md:hover:bg-red-500

等价于:

css
@media (min-width: 768px) {
  .md\:hover\:bg-red-500:hover { background: ... }
}

六、variants 是怎么工作的?

Tailwind 把 variant 当成「包装器」

txt
hover:   → &:hover
md:      → @media
dark:    → .dark &

所以:

html
<div class="dark:hover:bg-gray-800"></div>

等价于:

css
.dark .dark\:hover\:bg-gray-800:hover {
  background-color: #1f2937;
}

七、插件生成的类,为什么不会被 purge?

原因:

插件是在 JIT 之前 注入规则的

txt
plugin

addUtilities / addComponents

规则表

content 扫描

📌 插件里的类:

  • 属于「已注册规则」
  • 不需要扫描命中

八、safelist(白名单)机制

用于:你明确知道要用,但扫描不到

js
safelist: [
  'bg-red-500',
  'bg-blue-500',
  {
    pattern: /bg-(red|blue|green)-(100|500)/
  }
]

📌 慎用

  • safelist = 强制生成
  • 用多了 CSS 会变大

九、为什么 Tailwind 体积还能这么小?

因为:

  • 没命中的类 → 根本不生成
  • 没用的 variant → 不会包
  • 插件也是按需生效

一个 Vue 项目:

  • 实际 CSS:10~30KB
  • 而不是 MB 级

十、Tailwind & Vite 的配合(你会很有共鸣)

为什么 Vite 下 Tailwind HMR 飞快?

txt
文件变更

Vite 通知

Tailwind 重新扫描这个文件

增量生成 CSS

📌 不是全量 rebuild

这点和你研究的 Vite 虚拟模块 / HMR 本质一致。

十一、常见“诡异 bug”的底层解释

1️⃣ class 写了但没效果

  • 拼写不合法
  • theme 中没这个值
  • 被动态拼接

2️⃣ dev 有,build 没有

  • content 路径漏了
  • 生产环境 class 写法不同
  • 依赖包里的 class 没被扫描

十二、和你之前 Vue / 编译方向的对照总结

Tailwind编译原理类比
utilityAST node
variantwrapper
themesymbol table
JITon-demand codegen
purgedead code elimination

📌 Tailwind 本质 = CSS 编译器

十三、正确使用 Tailwind 的“心法”

  • 1️⃣ 类名 必须是静态字符串
  • 2️⃣ 设计变量 进 theme / plugin
  • 3️⃣ 复杂动态 → 映射表
  • 4️⃣ 公共体系 → 插件