预设是一组有名称的变换,具有已知的体积和运行时代价。更重的预设施加更多变换,但这并不是经过测量的保护提升。一个名称设定一个复杂度目标;输出体积由该目标决定。显式设置 complexity 会覆盖该目标,而直接设置的值总是作用于每一个 token,即使在 light 下也是如此。
阶梯
| 预设 | complexity | 典型 gzip 后体积 | 字符串下限 | 适用场景 |
|---|---|---|---|---|
minify | 0 | 1.09x 至 1.17x | 关闭 | 不做混淆。标识符会被重命名,代码会被压缩;字符串字面量保持可读。 |
light | 5 | 1.29x 至 1.60x | 开启 | **默认。**在 minify 的基础上加入字符串编码并重写语法,不添加结构层。适合整个应用 bundle 的预设。 |
medium | 7 | 6.04x 至 8.33x | 开启 | 希望明显更难跟读的代码。用于整个应用 bundle 时,它会使启动时间成倍增加(加载代价),因此请先实测你的页面。 |
hard | 12 | 6.62x 至 9.23x | 开启 | 关键代码:定价、功能门控、许可证校验、客户端业务逻辑。用指令把它用在这些代码上;用于整个 bundle 时,它的代价比 medium 更高。 |
extreme | 25 | 8.25x 至 11.19x | 开启 | 实际应用的最高复杂度。将它用于单个文件或区域,而不是整个 bundle。 |
以上是针对同一份已压缩文件的 gzip 后输出体积,在五个真实的、已压缩的生产 bundle 上用 AfterPack 0.2.6 于 2026-10-16 测得;方法见性能。
任何正数目标都会开启字符串下限。
加载代价
于 2026-10-24 使用 AfterPack 1.0.0 测得,测试对象是五个分别用 Vite 和 React、Vue、Astro、SvelteKit 以及 Next.js 构建的小型应用。每个应用的整个 bundle 都使用同一个预设,并与不使用 AfterPack 构建的同一应用比较;方法和每个应用的耗时见性能。
| 预设 | 页面加载的 JavaScript(未压缩) | 首次有效点击的时间 |
|---|---|---|
light | 1.12x 至 1.42x | +4 ms 至 +32 ms |
medium | 10.2x 至 11.9x | 3.1x 至 11x |
hard | 14.3x 至 15.8x | 3.2x 至 13.7x |
extreme | 27.1x 至 30.7x | 5.9x 至 24x |
体积在每个框架上的增长倍数大致相同。启动时间的增长取决于页面响应之前有多少这类代码要运行,因此 Next.js 应用的代价最高。框架自身的运行时也是 bundle 的一部分,每个预设都会同样保护它。
在整个应用 bundle 上,light 最多增加几十毫秒。从 medium 起,在把该预设用于整个 bundle 之前,请先实测你自己的页面。
默认为 light
不带 --preset 运行 npx afterpack@latest dist/,或者安装一个插件而不做任何配置,得到的就是 light。
$ npx afterpack@latest dist/npx afterpack@latest dist/ --preset=hard # step up命令行参数用 = 连接取值。用空格分隔的取值会被当作未知参数而拒绝。
light 是零配置的下限。它能重写的每一个字符串字面量都会经过运行时解码器。它会重命名标识符并重写语法,且不添加结构层。从引擎 0.2.3 起,minify 和 light 不再把循环改写成回调辅助函数,从而消除了这些辅助函数在热点循环中造成的 2-5 倍减速。在 light 下,属性名和全局名仍然经由编码常量访问,这在非常热的代码中会有一定开销。有些字符串在任何预设下都保持可读:指令序言、typeof 的比较对象、运行时需要的属性名,以及导出名。Protection Map 会列出它们,引擎也会在每次构建时报告其数量和字节总数。把预设提高到 medium 或以上,会在每一个词元上增加结构性工作:膨胀、控制流重塑,以及以一个共享的运行时值为密钥的字符串和整数。它不会改变字符串的可读性,因为可读性已经处于下限。
minify 是唯一关闭这一下限的级别。从引擎 0.2.3 起,它只重命名标识符并压缩代码,不应用任何混淆。引擎 0.2.2 在 minify 下还会重写语法。
体积预算
每份输出都带有引擎固定的运行时开销(解码器和整数表达式解释器),因此几百字节的小文件产出会比上表的比例还要大得多。请在真实的 bundle 上读取比值,而不是小文件。
inflation.max 默认未设置,因此复杂度目标独自决定输出体积。只有当你有一个宁可失败也不愿超出的真实体积预算时,才自己设置它。触及显式设置的值是一个错误:DIAG_SIZE_CAP_REACHED,没有输出,CLI 以退出码 3退出,插件则让构建失败。从引擎 0.2.3 起,绝不会写出保护不足的输出来替代:最终生成的文件会按上限检查,并且字符串编码总是先完成。在引擎 0.2.2 中,过紧的上限可能让字符串编码提前停止,使部分字符串保持可读。
混淆后的字节,以及 gzip 后的体积,按设计会随每次构建而变化。默认情况下每次构建都会抽取一个新种子,seed: "git" 则在每次提交时抽取一个新种子,因此每个版本发布的都是一个不同的程序。固定种子只有在输入相同、引擎版本相同时才能复现相同的字节。请把包体积预算设在打包工具写出的代码上(AfterPack 之前),或者为受保护输出的预算留出余量。
直接设置目标
complexity 是一个无上限的非负数。--complexity=200 是 Free 层级下有效的覆盖值,远超 extreme 所设定的数值。
npx afterpack@latest dist/ --preset=hard --complexity=40这相当于把 hard 的复杂度目标覆盖为 40;预设的其他设置都不变。向 --complexity 传入预设名称是一个错误。
将预设应用到单个区域
在 Free 上,一个预设覆盖整个构建:在 CLI 上用 --preset,或在配置中用 preset。唯一更细的控制是 paths.exclude,它把整个文件排除在外,并原样复制到输出中而不加保护。其余每个文件都要付出该预设的加载代价,因此除非你自己的实测结果允许更高的预设,否则请把整个应用 bundle 保持在 light。
在 Pro 上,源代码指令把更重的预设用在某个函数或文件上,bundle 的其余部分保持你的全局预设:
/* @afterpack preset=extreme */
function pricingEngine(plan, region) {
return resolve(plan, region);
}
/* @afterpack end */要覆盖一个文件,就用一个指令包住它的代码。指令只能写成块注释,由框架插件从你的源代码中读取;CLI 不读取任何指令。语法和层级规则参见指令。
下一步
- 配置:预设涉及的每一个键。
- CLI 参考:用于选择预设的参数。
- 指令:对单个片段使用
skip或preset=<name>。 - AfterPack 的工作原理:更高的目标面对静态阅读者究竟能带来什么。
- 最佳实践:什么该加强、什么该放过。