混淆预设

从 minify 到 extreme 的五个保护级别,每个级别设定的复杂度目标,以及零配置默认值(light)已经保证的内容。

预设是一组有名称的变换,具有已知的体积和运行时代价。更重的预设施加更多变换,但这并不是经过测量的保护提升。一个名称设定一个复杂度目标;输出体积由该目标决定。显式设置 complexity 会覆盖该目标,而直接设置的值总是作用于每一个 token,即使在 light 下也是如此。

The five obfuscation presets ranked by complexity target, with light marked as the defaultextremetarget 25hardtarget 12mediumtarget 7lighttarget 5DEFAULTminifytarget 0Zero config gives you light: string literals encoded. Step up per project.

阶梯

预设complexity典型 gzip 后体积字符串下限适用场景
minify01.09x 至 1.17x关闭不做混淆。标识符会被重命名,代码会被压缩;字符串字面量保持可读。
light51.29x 至 1.60x开启**默认。**在 minify 的基础上加入字符串编码并重写语法,不添加结构层。适合整个应用 bundle 的预设。
medium76.04x 至 8.33x开启希望明显更难跟读的代码。用于整个应用 bundle 时,它会使启动时间成倍增加(加载代价),因此请先实测你的页面。
hard126.62x 至 9.23x开启关键代码:定价、功能门控、许可证校验、客户端业务逻辑。用指令把它用在这些代码上;用于整个 bundle 时,它的代价比 medium 更高。
extreme258.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(未压缩)首次有效点击的时间
light1.12x 至 1.42x+4 ms 至 +32 ms
medium10.2x 至 11.9x3.1x 至 11x
hard14.3x 至 15.8x3.2x 至 13.7x
extreme27.1x 至 30.7x5.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 不读取任何指令。语法和层级规则参见指令。

下一步