PurgeCSS仅通过正则扫描源码中的字面量类名,无法识别JS拼接、模板字符串、响应式绑定等动态生成的类名,必须通过safelist显式声明或改用UnoCSS等能解析AST的工具。

它根本没看到动态类名,不是删错了,是压根没扫到
PurgeCSS 不执行 JavaScript,也不解析 Vue/React 的响应式绑定逻辑。它只做一件事:在你 content 配置列出的文件里,逐行匹配字符串形式的类名(比如 class="btn btn-primary" 或 className="alert")。一旦类名是拼出来的,就直接消失在扫描视野里。
常见现象包括:
-
v-bind:class="['btn', `btn-${type}`]"→btn-primary不会被识别 -
element.className = 'd-' + breakpoint + '-block'→d-md-block完全漏掉 -
className={isShow ? 'block' : 'hidden'}→ 两个类都可能被删
content 配置再全也没用,JS 运行时行为它不处理
哪怕你把 src/**/*.{js,jsx,ts,tsx,vue} 全列进 content,只要类名不是以字面量形式出现,PurgeCSS 就无能为力。它不会运行 String.raw、不会展开模板字符串、不会模拟 useState 的状态切换。
关键限制点:
立即学习“前端免费学习笔记(深入)”;
- 不解析 AST,只做正则级文本扫描
- 对
v-html、dangerouslySetInnerHTML、innerHTML注入的内容完全无视 - 第三方组件库的 JS 控制类(如
data-bs-toggle="modal"对应的.modal)也不会被推导
safelist 不是补救,而是必须提前写好的保命清单
动态类名无法靠“猜路径”解决,只能靠 safelist 显式声明。这不是可选项,是上线前必填项。
实操建议:
- 静态类名直接写字符串:
safelist: ['active', 'disabled', 'has-error'] - 动态前缀用正则:
safelist: [/^btn-/, /^text-/, /^d-/, /^m-/] - Tailwind 变体要单独列:
variants: ['hover', 'focus', 'active'],否则hover:bg-blue-500会被截成hover:bg导致匹配失败 - 属性选择器如
[data-state=open]必须进safelist,默认提取器不识别方括号语法
别指望插件顺序或 PostCSS 配置能绕过这个限制
无论你把 @fullhuman/postcss-purgecss 放在 tailwindcss 前还是后,无论是否启用 greedy 模式,只要类名没在源码中以完整字符串形式存在,PurgeCSS 就不会把它当“已使用”。
真正有效的替代方案只有两个:
- 改用
UnoCSS—— 它在构建阶段解析最终 HTML AST,能捕获 JS 拼接、v-html、甚至useColorMode()生成的类 - 放弃动态拼接,改用显式枚举:
['btn-primary', 'btn-secondary']或classList.toggle('is-open', isOpen)
最常被忽略的一点:safelist 中写 btn-primary 和 .btn-primary 效果不同,前者匹配类名字符串,后者匹配完整选择器;PurgeCSS 默认按类名字符串匹配,优先用带点的写法更稳妥。


















