Chrome Coverage面板能直接标出未匹配任何元素的CSS规则,但仅反映当前路由和已触发交互状态;SPA需逐路由重录,伪类、JS动态类名及CSS-in-JS均无法检测,须结合PurgeCSS等工具交叉验证。

Chrome Coverage 面板能直接看到哪些 CSS 没用,但只限当前页面状态
打开 DevTools → More Tools → Coverage → 刷新页面,它会标出每行 CSS 的使用率:红色 = 0%,绿色 = 100%。这个数据真实、无误,但有严重局限:
- 只覆盖你当前访问的路由和已触发的交互(比如没点开的下拉菜单、没提交的表单错误态,里面的样式全算“未使用”)
- 单页应用(SPA)必须手动跳转到每个路由,再点
Reload coverage,否则首页之外的 CSS 全部被误判 - 动态类名(如
class="btn btn--${type}")在 JS 中拼接的,Coverage 看不到运行时结果,会把btn--danger当作死代码
PurgeCSS 是静态分析主力,但 content 配置漏一项就删崩页面
PurgeCSS 不执行 JS,只扫描字符串。它靠 content 字段指定要检查的文件路径,一旦漏掉某个 .html 或 .vue,对应页面里用到的类就会被误删。
- 必须显式列出所有模板文件:
content: ["./src/**/*.html", "./src/**/*.vue", "./src/**/*.jsx"] - JS 中动态拼类名(
element.classList.add('text-' + color))必须加safelist,例如/^text-.*$/,否则text-red、text-blue全进回收站 - Tailwind 项目别绕过
tailwind.config.js的content字段——它内置 AST 解析,比裸用 PurgeCSS 更稳 - 删完发现样式异常?立刻查
purgecss --info输出的removedSelectors日志,重点盯那些你明明在用却被列进去的类名
uncss 适合传统多页站点,但对伪类和 JS 动态 DOM 支持弱
uncss 的核心是启动一个真实浏览器环境(基于 jsdom 或 Puppeteer),加载 HTML 后提取实际渲染的 DOM 节点,再反向匹配 CSS 选择器。它天然支持 :hover、::before 这类静态分析无法处理的伪类,但代价明显:
- 不支持 JS 动态挂载的 DOM(比如 Vue 的
v-if、React 的条件渲染),除非你额外写脚本驱动交互 -
dePseudify函数会主动忽略:link、:visited、::selection等无法静态判定的伪类,相关样式大概率被误删 - 运行慢,每次都要解析 HTML + 构建虚拟 DOM,不适合 CI 频繁校验;更适合上线前一次性瘦身
- 对内联
style、styled-components、CSS-in-JS 完全无感知,这些地方的类名它根本看不到
真正可靠的方案是运行时 + 静态双路验证
单靠一种工具永远有盲区。生产环境要落地,必须组合使用:
立即学习“前端免费学习笔记(深入)”;
- 用
Puppeteer或Playwright驱动真实浏览器,覆盖所有路由 + 所有用户可触发状态(hover、focus、error、loading),抓取最终生效的 class 列表 - 用
PurgeCSS做静态扫描,输出usedSelectors和removedSelectors - 两者取交集作为“可信已用集合”,再人工核对差异项:比如 Playwright 抓到了
is-active,但 PurgeCSS 没扫到,说明 JS 里有隐藏逻辑,得补进safelist - CI 中加 diff 检查:对比本次构建与上一次的
removedSelectors,若新增大量高频类(如hidden、flex),自动拦截发布
最易被忽略的一点:所有工具都默认忽略 @keyframes 和 @font-face,哪怕它们真没被调用。如果你的动画或字体是 JS 动态注入名称的,得单独写逻辑保活。



















