混淆 Parcel 构建

@afterpack/parcel-optimizer 作为 Parcel 2 Optimizer,在内存中混淆每个打包产物,并在可能导致分块在生产环境 404 时使构建失败,而不是继续发布。

@afterpack/parcel-optimizer 是一个 Parcel 2 Optimizer,注册在 .parcelrc 中。它在 Parcel 自身的优化阶段在内存中混淆每个打包产物,因此该产物不会以明文形式落盘。

不是 @afterpack/parcel

该软件包是 @afterpack/parcel-optimizer。

安装

$ npm install -D @afterpack/parcel-optimizer
// .parcelrc
{
  "extends": "@parcel/config-default",
  "optimizers": {
    "*.{js,mjs,cjs}": ["...", "@afterpack/parcel-optimizer"]
  }
}

"..." 展开很重要:它保留了 Parcel 的默认优化器(@parcel/optimizer-swc),并把 AfterPack 放在最后。AfterPack 在压缩之后运行。对等依赖:Parcel >=2.9.0。

构建

.parcelrc 中的条目是不带内联选项的裸包名,因此选项来自项目根目录下的 afterpack.json,这是每个 AfterPack 集成都会读取的唯一配置文件:

{
  "seed": "git",
  "preset": "medium"
}

只有项目根目录会产生构建产物;不会在你的打包产物旁边生成任何文件:

  • 每个打包产物一个 protectionMap.html,位于 .afterpack/<bundle>.<id>.protectionMap.html。
  • 当 sourceMap 策略 启用时生成一个 source map。它会返回给 Parcel 而不是直接写入磁盘,因此由 Parcel 用自己的内容哈希来命名并输出它。

.afterpack/ 自带一个 .gitignore;该插件从不修改你项目的 .gitignore。

验证

由于没有合并报告,请直接检查你关心的打包产物:对 Parcel 打包的每个入口或延迟加载的代码块,查看 .afterpack/<bundle-name>.<id>.protectionMap.html。生产环境下默认关闭 map;要覆盖此行为,请在 afterpack.json 中设置 "sourceMap": { "enabled": true }。每份报告都包含你的完整源码,切勿提交或对外提供该文件。

选项

每个配置项都可以在这里设置,例如 { "preset": "hard", "complexity": 40 }。protectionMap.enabled 取决于打包产物是否带有 source map,而不是取决于构建模式,并且始终写入已被 gitignore 的 .afterpack/ 中。该插件没有 build.backup 选项,设置它会发出警告。显式选项始终优先于生产环境默认值。

指令

指令由插件从你的源码中读取。如果没有可用的 source map,该处理阶段就无法恢复任何内容,并会明确说明这一点。

入口模块中的指令不会被恢复。 该处理阶段会报告 DIAG_DIRECTIVE_SOURCE_UNRESOLVED 并指出具体模块。一旦启用 source map,其他所有非入口模块中的指令都能正常工作。如果你需要在入口模块中使用指令,请把受保护的代码移到一个被导入的模块中。

边界情况

内容哈希占位符可能导致构建失败

启用内容哈希时(parcel build 的默认行为),如果目标环境不支持原生 ESM,Parcel 的内容哈希占位符会落入被混淆的字符串内部,导致每个代码分割出的分块在生产环境中都返回 404。

该插件会检测这种冲突,并使构建失败,而不是继续发布:

AfterPack obfuscated 1 Parcel content-hash placeholder(s) in app.[hash].js (HASH_REF_...).
  hint: Run `parcel build --no-content-hash` (chunk URLs stop being content-addressed).
  hint: Or set "complexity": 0 in afterpack.json to ship minify-only.

Parcel 的默认 ESM 输出不受影响。

需要了解的情况

  • 每个打包产物在工作进程中处理一次。 Parcel 按打包产物运行优化器,不同工作进程中打包的产物无法共享状态。在同一进程中打包的产物共享同一个种子。要在所有工作进程中固定同一个种子,请显式设置 seed 或导出 AFTERPACK_seed。
  • Parcel 会缓存优化器输出。 如果 .parcel-cache 是热缓存且输入未变化,重新构建不会重新运行 AfterPack,也不会轮换未固定的种子。要获得全新的处理结果,请清除 .parcel-cache;CI 构建反正都是从冷缓存开始的。
  • 两条无害的警告。 Parcel 会打印 ES module dependencies are experimental(每个 AfterPack 包都是 ESM),并报告该插件 contains non-statically analyzable dependencies(原生引擎的运行时二进制文件解析)。二者都不影响构建。

不使用插件:改用 CLI

照常使用 Parcel 构建,然后让 AfterPack 处理其输出:

$ npx parcel build
$ npx afterpack@latest dist/ --preset=medium --seed=git

不带路径参数时,CLI 会检测 dist/,即 Parcel 的默认输出目录;如果你的目录不同,请指定它。AfterPack 会遍历该目录,收集所有 .js/.mjs/.cjs 文件并原地混淆它们。CLI 运行没有内容哈希防护,并且会为整个目录生成一份合并的 protectionMap.html,而不是每个打包产物一份。失败即中止依然有效:构建目录缺失、没有 JS 文件、error/critical 诊断信息,或者某个文件引擎无法混淆,都会以 1 退出。

下一步

  • 框架:完整矩阵。
  • esbuild:另一个在压缩后从 source map 中恢复指令的插件。
  • CLI 参考:兜底路径的完整参数列表。
  • 构建与 CI:种子、开发与生产的区别、发布同一份字节。
  • Protection Map:如何阅读该报告。
  • 配置:每个选项,以及哪个入口可以设置它。
  • 预设:五级阶梯。