purgecss详解

380 阅读4分钟

什么是 PurgeCSS?

简单来说,PurgeCSS 就是 CSS 的 Tree Shaking 方案。它的主要功能是通过正则匹配的方式,剔除项目中未使用的 CSS 代码,从而减小最终打包后的体积。

不过需要注意,PurgeCSS 只能识别静态类名,也就是说类名必须在编译阶段就能确定。例如:

<div className="name"></div> // ✅ PurgeCSS 可以识别

但如果类名是通过变量或拼接方式动态生成的:

const name = 'name';
<div className={name}></div> // ❌ 无法识别

由于变量 name 的值要到运行时才能确定,PurgeCSS 无法识别这类动态类名,可能会将对应样式误删。因此,在项目中使用 PurgeCSS 时需要特别注意这些限制。


工作原理

PurgeCSS 的源码结构是一个典型的多包(monorepo)项目,由 pnpm + lerna 管理。其中我们重点关注的是 purge-from-tsx 这个子包,因为它适用于 TSX 项目。

在主文件中引入了以下几个关键依赖:

  • acorn:将 JavaScript 代码转换为抽象语法树(AST)
  • acorn-jsx:用于解析 JSX
  • acorn-walkacorn-jsx-walk:用于遍历 AST,提取关键信息(如类名、ID 等)

主函数逻辑

主函数返回一个接收 JSX 字符串的函数,它的作用是从 JSX 代码中提取出用于匹配的 CSS 选择器

核心逻辑使用 walk.recursive 递归遍历 AST,其中重点处理以下节点:

  • JSXOpeningElement:提取 JSX 元素的标签名作为选择器
  • JSXAttribute:提取 idclassName
  • JSXIdentifier:提取 <div> 等基本标签名
  • JSXNamespacedName:提取如 <svg:path> 这类带命名空间的标签
  • Literal:提取字面量形式的类名,例如 className="container"

这些信息就是最终提取出来的“有效选择器”。


CSS 对比与剔除过程

提取完 JSX 中的选择器后,下一步就是分析 CSS 文件。PurgeCSS 使用 postcss 插件解析 CSS,将其转换成选择器集合,然后与 JSX 中提取出的选择器做比对,判断哪些 CSS 是未使用的,最后将其移除

另外,PurgeCSS 还支持配置“白名单”,防止特定的类名被误删,提升可控性和安全性

总结

因此,我们可以将 PurgeCSS 的核心流程概括为以下四步:

  1. 扫描 JSX 文件:提取标签名、idclassName 等属性,构建出一组实际使用的选择器集合
  2. 解析 CSS 文件:通过 PostCSS 插件分析样式文件,将其中的选择器按类型(标签、类名、ID)进行分类
  3. 比对选择器:将提取的实际使用选择器与 CSS 中的选择器进行对比,识别出未被使用的样式
  4. 移除冗余 CSS:将未使用的 CSS 规则从最终样式文件中剔除,优化体积

与 Tailwind 的关系

Tailwind 旨在通过仅生成您在项目中实际使用的 CSS 来生成尽可能小的 CSS 文件。借鉴 Tailwind 实现 tree shaking 的方案。在开发的时候,为了避免每次保存文件时重新生成样式,tailwind 会先生成一份全量的 CSS 文件,包含了所有的类名选择器。如果不做任何处理,全量的 CSS 文件必然会生成许多无关的 CSS,使得代码打包体积增大。因此,tailwind 其实提供了一个 purge 的选项,用于开启清除无关样式的功能,在 purge 内配置的文件都会进行扫描,从而剔除无关的 CSS 。而其 purge CSS 功能的底层,实际上依靠的就是 purgeCss 这个库


使用场景

PurgeCSS 的应用主要集中在两个方面:

  1. 业务代码冗余清理
    随着需求迭代,很多 CSS 可能已经不再使用,但仍然留在项目中,占据体积
  2. 组件库的 CSS 剔除(重点)
    比如我们使用某个 UI 组件库,只使用了其中的 primary 样式:
import Button from 'xx-ui';
<Button type="primary">点击</Button>

默认情况下,打包结果可能包含以下样式:

.primary {}
.alert {}
.large {}
.btn-hover {}

实际上我们只用了 .primary.btn-hover,其余的 .alert.large 是多余的。

使用 PurgeCSS 就能把这些未使用的样式剔除掉。但前提依然是类名是静态的,如果组件库中的类名是动态生成的,PurgeCSS 同样无法识别,容易误删。