动态类名被删是因为PurgeCSS仅扫描字符串字面量,不执行JS或解析响应式逻辑;需用绝对路径配置content、正则safelist精准匹配(如/^(?:hover|md):text-(red|blue)-[0-9]{3}$/)、确保PostCSS顺序为postcss-import→tailwindcss→purgecss→autoprefixer→cssnano并关闭cssnano的discardUnused。

动态类名被删,是因为 PurgeCSS 根本没“看见”它们
PurgeCSS 不执行 JavaScript,也不解析 Vue/React 的响应式绑定逻辑。它只扫描源码文件里的字符串字面量——比如你写 className={`btn btn-${type}`},它提取到的只有 btn 和 btn-${type} 这两个模板串,btn-primary 这个真实类名压根不会出现在它的匹配列表里。
常见错误现象包括:.modal、.is-open、.hover:bg-blue-500 在生产环境消失,但本地开发一切正常;Lighthouse 报“CSS 规则数为 0”;构建后 CSS 文件只剩 @charset "UTF-8";。
- 别指望
content: ["./src/**/*.vue"]自动覆盖所有拼接结果——它扫的是未编译的原始文本,不是运行时渲染出的 DOM 类名 - Vue 的
:class、React 的className={...}、甚至el.classList.add('hidden')都属于“不可见”范围 - 第三方库如 Ant Design 的
ant-modal、Swiper 的swiper-slide,只要没在你的源码里明文出现,就默认被删
safelist 不是随便加几个字符串就能兜底
写死单个类名(如 'hidden')只适合极少数固定值;一旦涉及前缀变体(md:hidden)、伪类(hover:bg-blue-500)或动态拼接(text-${color}-500),必须用正则,且要精准匹配最终生成的完整类名。
- 错误写法:
/text-.*/—— 会意外保留text-clip、text-overflow等原生 CSS 属性 - 正确写法:
/^(?:md|lg|hover):text-(red|blue|green)-[0-9]{3}$/,非捕获组避免语法错误,数字限定三位更安全 - 对
data-bs-toggle="modal"这类属性选择器,PurgeCSS 不处理属性本身,但会删掉你自定义的[data-bs-toggle="modal"]规则,需加进 safelist:/^\[data-bs-toggle=/ - 函数式 pattern 更可控:
{ pattern: /btn-(primary|secondary|danger)/ },比写一堆字符串更易维护
PostCSS 插件顺序错一位,CSS 就可能全空
PurgeCSS 必须在 CSS 被压缩或重命名之前运行。如果它在 cssnano 后面,或者和 tailwindcss 冲突,就会处理已损坏或已优化过的 CSS,导致误判甚至输出空文件。
立即学习“前端免费学习笔记(深入)”;
- 正确顺序永远是:
postcss-import→tailwindcss(如有)→@fullhuman/postcss-purgecss→autoprefixer→cssnano -
cssnano必须关掉重复清理:{ discardUnused: false },否则和 PurgeCSS 功能重叠,可能二次误删 - Tailwind v3+ 用户请直接用
content字段配置,删掉postcss.config.js里多余的@fullhuman/postcss-purgecss插件——多装一个反而冲突 - Webpack 用户务必确认
PurgeCSSPlugin放在MiniCssExtractPlugin之后,否则处理的是未提取的模块,不是最终 CSS
content 路径配错,等于没配
路径不是越宽越好,也不是写相对路径就完事。Windows 下 ./src/**/*.vue 会因分隔符失效;用 glob.sync() 读未编译的 .vue 文件,里面 class="btn {{ type }}-btn" 根本没有真实类名。
- 必须用绝对路径:
path.join(__dirname, 'src', '**', '*.{js,ts,jsx,tsx,vue}') - 若用了
vite-plugin-css-injected-by-js,它的 JS 注入 CSS 不在默认 content 范围内,得手动加进数组,否则直接清空 - Vite 用户优先用
vite-plugin-purgecss,它在 Rollup 转换后钩子中运行,能拿到编译后的 class 字符串 - import.meta.glob 动态导入的组件路径不会被自动捕获,得手动加进 content 数组


















