Tailwind 底层生成机制 & purge 原理
Tailwind 底层生成机制 & purge 原理,是理解「为什么这样写才安全 / 高效」的关键。
我会按 “从构建 → JIT → purge” 的完整链路给你拆,本文结合 Vite / Vue / 编译原理方向的背景来讲。
# 一、整体流程总览(先有全局图)
Tailwind 构建时大概经历:
tailwind.config.js
↓
解析 theme / plugins
↓
生成“候选类名规则表”
↓
扫描 content(源码)
↓
JIT 即时生成 CSS
↓
输出最终 CSS
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
📌 关键点
- ❌ 不是预生成所有类
- ✅ 是 按需生成(JIT)
# 二、Tailwind JIT 是怎么工作的?
# 1️⃣ 早期(v2 之前)
- 预生成 上万条 CSS
- 再 purge 没用的
- 构建慢、CSS 大
# 2️⃣ 现在(v3+,默认 JIT)
# 核心思想
看到一个 class → 立刻生成一条 CSS
例如:
<div class="px-4 text-red-500"></div>
1
JIT 会:
- 拆分 class token
- 匹配规则
- 查 theme 值
- 生成 CSS
.px-4 { padding-left: 1rem; padding-right: 1rem }
.text-red-500 { color: #ef4444 }
1
2
2
# 3️⃣ 内部抽象模型(重要)
# 每一个 class = 一个「函数调用」
text-red-500
│ │ │
│ │ └── scale value
│ └────── theme key
└────────── utility name
1
2
3
4
5
2
3
4
5
等价于:
text(color = theme.colors.red[500])
1
📌 所以 Tailwind 非常像一个 DSL
# 三、content 扫描机制(purge 的核心)
# 1️⃣ 扫描的是什么?
content: ['./src/**/*.{vue,js,ts,html}']
1
Tailwind 会:
- 读取文件
- 用 正则 抽取可能的 class token
例如:
<div class="px-4 text-red-500"></div>
1
会被拆成:
px-4
text-red-500
1
2
2
# 2️⃣ 关键事实(很多人不知道)
Tailwind 不理解 JS / Vue 语法
它只是扫描“字符串”
📌 所以👇
❌
`bg-${color}-500`
1
扫描结果:
bg-
-color-
-500
1
2
3
2
3
👉 匹配失败 → 不生成 CSS
# 四、为什么动态 class 会被 purge 掉?
# 原因不是 purge,而是:
JIT 根本“看不到完整类名”
例如:
<div :class="`bg-${type}-500`"></div>
1
Tailwind 扫描到的只是:
bg-
-type-
-500
1
2
3
2
3
所以:
- 没有合法 token
- 不会触发生成
# 五、JIT 的 token 匹配规则(很重要)
# 合法 token 示例
bg-red-500
hover:bg-blue-500
md:hover:text-sm
1
2
3
2
3
# 内部处理顺序
variants → utility → value
1
例如:
md:hover:bg-red-500
1
等价于:
@media (min-width: 768px) {
.md\:hover\:bg-red-500:hover { background: ... }
}
1
2
3
2
3
# 六、variants 是怎么工作的?
# Tailwind 把 variant 当成「包装器」
hover: → &:hover
md: → @media
dark: → .dark &
1
2
3
2
3
所以:
<div class="dark:hover:bg-gray-800"></div>
1
等价于:
.dark .dark\:hover\:bg-gray-800:hover {
background-color: #1f2937;
}
1
2
3
2
3
# 七、插件生成的类,为什么不会被 purge?
# 原因:
插件是在 JIT 之前 注入规则的
plugin
↓
addUtilities / addComponents
↓
规则表
↓
content 扫描
1
2
3
4
5
6
7
2
3
4
5
6
7
📌 插件里的类:
- 属于「已注册规则」
- 不需要扫描命中
# 八、safelist(白名单)机制
# 用于:你明确知道要用,但扫描不到
safelist: [
'bg-red-500',
'bg-blue-500',
{
pattern: /bg-(red|blue|green)-(100|500)/
}
]
1
2
3
4
5
6
7
2
3
4
5
6
7
📌 慎用
- safelist = 强制生成
- 用多了 CSS 会变大
# 九、为什么 Tailwind 体积还能这么小?
因为:
- 没命中的类 → 根本不生成
- 没用的 variant → 不会包
- 插件也是按需生效
一个 Vue 项目:
- 实际 CSS:10~30KB
- 而不是 MB 级
# 十、Tailwind & Vite 的配合(你会很有共鸣)
# 为什么 Vite 下 Tailwind HMR 飞快?
文件变更
↓
Vite 通知
↓
Tailwind 重新扫描这个文件
↓
增量生成 CSS
1
2
3
4
5
6
7
2
3
4
5
6
7
📌 不是全量 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️⃣ 公共体系 → 插件
上次更新: 2025/12/26, 01:42:33