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 会:
- 拆分 class token
- 匹配规则
- 查 theme 值
- 生成 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-5002️⃣ 关键事实(很多人不知道)
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 | 编译原理类比 |
|---|---|
| utility | AST node |
| variant | wrapper |
| theme | symbol table |
| JIT | on-demand codegen |
| purge | dead code elimination |
📌 Tailwind 本质 = CSS 编译器
十三、正确使用 Tailwind 的“心法”
- 1️⃣ 类名 必须是静态字符串
- 2️⃣ 设计变量 进 theme / plugin
- 3️⃣ 复杂动态 → 映射表
- 4️⃣ 公共体系 → 插件