PurgeCSS默认只识别源码中静态写死的类名字符串,不处理import的第三方CSS、动态拼接类名或运行时生成的class,必须通过content精确配置扫描路径、safelist手动保留实际使用的类,并确保插件顺序正确。

content 配置必须覆盖所有类名实际出现的位置
PurgeCSS 不读node_modules/bootstrap.css,也不解析 import 'bootstrap',它只扫你源码文件里写死的 class="btn btn-primary" 这种字符串。
- 必须包含所有模板/组件文件:
['src/<strong>/*.vue', 'src/*<em>/</em>.tsx', 'src/</strong>/*.js', 'src/*<em>/</em>.html'] - 若用 Pug、Nunjucks 或 JSX 中的
className,也要加进去,否则btn-primary就不会被提取 - 路径别用
glob.sync('./src/*<em>/</em>.vue')—— Windows 下容易路径分隔符错乱,改用path.join(__dirname, 'src', '*<em>', '</em>.{vue,ts,js}')更稳 - 如果项目有服务端渲染(SSR)或预渲染 HTML,那些生成的 HTML 文件也得进
content,否则is-active这类服务端注入的类会被删
safelist 是保命配置,不是可选项
几乎所有真实项目都需要safelist,尤其当你用了第三方 UI 库、Tailwind 变体或属性选择器时。
- Bootstrap 类前缀:
[/^btn-/, /^alert-/, /^modal-/] - Tailwind hover/focus/group-hover:
[/^(hover|focus|group-hover|active)-/, /[.*]/],否则hover:bg-blue-500和[data-open="true"]全丢 - 动态拼接类名(如
className={<code>card card-${size}}):[/^card-/]比列全名更可靠 -
keyframes: true和fontFace: true要显式开启,否则@keyframes spin和@font-face规则大概率被清空
PostCSS 插件顺序错一位,CSS 就可能全空
@fullhuman/postcss-purgecss 必须在 cssnano 之前、autoprefixer 之后,且不能和 Tailwind 内置 purge 冲突。
- 正确顺序:
postcss-import → tailwindcss → @fullhuman/postcss-purgecss → autoprefixer → cssnano -
cssnano必须关掉discardUnused: { discardUnused: false },否则它会二次扫描并误删已被 PurgeCSS 保留的规则 - Tailwind 项目别同时启用
tailwind.config.js的content和外部@fullhuman/postcss-purgecss,二者功能重叠,冲突概率极高 - 若构建后 CSS 文件只剩
@charset "UTF-8";,基本就是插件顺序或content路径为空导致没提取到任何类名
动态类名漏判不是 Bug,是设计使然
PurgeCSS 的静态分析本质决定了它无法推断type === 'primary' ? 'btn-primary' : 'btn-secondary' 这类逻辑,更不理解 data-bs-toggle 属性触发的 Bootstrap 行为。
- 不要指望它自动识别
class="btn ${type}"—— 提取器看到的是${type}字符串,不是btn-primary - 第三方库建议按需导入(如 Sass 中只
@import "bootstrap/scss/buttons"),比靠safelist更干净 - 对于极复杂场景(如大量运行时 class 注入),考虑换用支持 AST 分析的方案,比如
vite-plugin-purgecss(Vite)或启用purgecss-webpack-plugin的modules: true模式
真正难的不是配出一个能跑的 purgecss.config.js,而是确认你项目里每一个“看似没写死”的类,到底是被 JS 动态加上的、由框架内部逻辑触发的,还是根本没用——这些都得靠人工核对,而不是交给工具猜测。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











