在 SCSS 中,这类通过循环或插值动态生成的类名,通常不会出现在 PurgeCSS 的扫描结果中。原因其实很直接:PurgeCSS 只会提取 HTML、JS 里的字面量字符串,而像 @each、#{$color} 这类尚未编译的 Sass 语法,并不在它的识别范围内。因此,想保留这部分 CSS 样式,就需要通过正则白名单显式保留,例如 /^bg-/;另外,PostCSS 插件的执行顺序也非常关键,必须放在 cssnano 之前,才能确保 PurgeCSS 处理到的仍然是原始类名。

SCSS动态类名根本不会出现在PurgeCSS扫描结果里
PurgeCSS 的工作原理,说到底就是扫描文件中的字面量字符串。问题也正好出在这里:像 SCSS 里的@each、@for,以及#{}这类插值语法生成的类名,比如.bg-#{$color},在编译前本质上仍然只是 Sass 代码,并不是真实存在的 class 名称。只有在编译完成之后,它们才会变成最终的 CSS 选择器;但 PurgeCSS 并不会主动解析 SCSS 源码,它能接触到的通常只是编译后的 CSS 文件。到了这个阶段,虽然类名已经固定,但这些类名如果没有出现在content配置指定的 HTML、JS 或模板文件中,依然会被当作未使用样式清理掉。
常见表现包括:.bg-red、.text-sm 这类动态生成的工具类被误删;@import "utils.scss" 中通过循环输出的.p-1到.p-12,最终构建后只保留了一部分。
- 不要指望 PurgeCSS 能“理解”SCSS 逻辑——它甚至会把
$spacing: (1: 0.25rem, 2: 0.5rem)这类 map 配置直接忽略 - 如果你使用了
font: #{$size}/#{$lh} sans-serif这类带/的写法,SCSS 编译阶段就可能报错,导致整份 CSS 没有正常输出,PurgeCSS 自然也无从扫描 - 真正被删除的并不是“动态类”本身,而是那些没有在
content路径中以硬编码字符串形式出现过的类名
SCSS里写死的类名也可能被误删
即使你在 SCSS 中直接写了.btn-primary { ... },只要content没有扫描到"btn-primary"这个字符串,它同样会被清理。PurgeCSS 不关心 CSS 里是否定义过这个类,它只认模板、页面或脚本中有没有实际引用。
典型场景是:你把所有 Bootstrap 工具类都通过@include引入到一个base.scss中,但 HTML 里并没有直接写class="btn-primary",而是通过 JS 动态拼接,例如el.className = 'btn btn-' + type——那么btn-primary就永远不会以完整字符串形式出现在扫描文本中。
- 必须将 SCSS 生成的类名前缀显式加入
safelist,例如/^btn-/、/^p-/、/^text-/ - 不要只写
'btn-primary'这一种情况——一旦动态值增多,单个字符串根本无法覆盖所有结果 - 如果使用了
@apply(例如 Tailwind 场景),要确保content路径包含所有使用@apply的 SCSS 文件,否则像@apply bg-red-500这样的样式同样可能被漏扫
PostCSS插件顺序让SCSS和PurgeCSS彻底脱节
SCSS 编译过程(如sass-loader或postcss-scss)通常发生在 PostCSS 处理链的前期;而如果 PurgeCSS 插件被放到cssnano之后,那么它接触到的往往已经是压缩过、甚至可能被重写过选择器的 CSS,结果自然无法准确匹配原始类名。
一个常见错误配置是:在postcss.config.js中把@fullhuman/postcss-purgecss写在cssnano后面,最终构建出来的 CSS 可能只剩下@charset "UTF-8";。
- 推荐顺序应为:
postcss-import→postcss-scss(如有)→tailwindcss(如有)→@fullhuman/postcss-purgecss→autoprefixer→cssnano cssnano最好关闭discardUnused: true,否则它可能再次清理已被 PurgeCSS 处理过的选择器,进而造成空规则或输出异常- 对于 Webpack 用户,更建议使用
purgecss-webpack-plugin,因为它可以在 CSS 提取完成后、压缩之前介入,比单纯的 PostCSS 插件更接近真实构建产物
SCSS变量和混合宏里的类名无法被静态提取
例如@mixin button-variant($bg, $border) { .btn-#{$bg} { background: $bg; } }这类写法中,像.btn-red这样的类名只存在于编译阶段的上下文里,PurgeCSS 既拿不到$bg的具体值,也看不到#{}最终展开后的结果。
这也是很多项目容易踩坑的原因:你可能写了几十个@include button-variant(red),但在 PurgeCSS 的日志中,rejected列表里依然全是.btn-red——本质原因就是它从未在任何.html或.js文件中见到这个完整类名字符串。
- 最稳妥、最可靠的方案仍然是使用正则白名单:
/^btn-/、/^bg-/、/^shadow-/ - 如果 SCSS 中使用了
[data-theme="dark"] .text-primary这类属性选择器,也建议把对应规则加入safelist,例如/^data-theme/,否则 PurgeCSS 也可能将其当作未使用样式移除 - 不要依赖
defaultExtractor去解析 SCSS——默认提取器主要识别 HTML、JS 中的字符串,对 Sass 语法本身几乎没有解析能力
safelist兜底),哪些类名又属于运行时动态注入的内容(这类则需要依赖框架适配、模板扫描补充或额外注入路径)。只要漏掉一个关键前缀,线上页面就可能少掉一整块样式。