混淆 Next.js 构建

@afterpack/next 在 next build 内部混淆客户端 JavaScript,支持 Turbopack 或 webpack、SSR 或静态导出。

@afterpack/next 从 next build 内部混淆你的客户端 JavaScript。withAfterpack 返回一个带有 compiler.runAfterProductionCompile 的配置,这是 Next 自身的构建钩子,无论使用哪种打包工具,Next 都只会调用它一次。

包裹你的配置就是整个集成过程,没有生命周期脚本,也没有单独的命令。

安装

$ npm install -D @afterpack/next
// next.config.ts
import type { NextConfig } from "next";
import { withAfterpack } from "@afterpack/next";

const nextConfig: NextConfig = {
  // your existing config
};

export default withAfterpack(nextConfig);

函数形式的 Next 配置也以同样的方式工作。withAfterpack(async (phase, ctx) => ({ … })) 会返回一个包装后的函数。

构建

现在 next build 会输出混淆后的产物,无论 .next/ 是由哪种打包工具生成的(Turbopack 或 webpack)。next dev 不受影响。每次调用 next build 都会运行该钩子,包括直接调用它的 CI 任务,而且没有生命周期脚本可供安装步骤跳过。如果该钩子抛出异常,构建会以退出码 1 失败。

该钩子只混淆提供给客户端的 JavaScript:.next/static/chunks,以及静态导出时的 out/。.next/server 永远不会到达浏览器,因此不会被混淆。该钩子在编译之后、预渲染和静态导出之前运行,因此静态导出会把混淆后的代码块复制进 out/。

Next 会在钩子运行之前写入代码块,因此在构建过程中,未混淆的代码块会在磁盘上停留大约一秒钟,在此期间任何能读取 .next/ 的进程都能读到它们。管线内置的插件(Vite、Rollup、webpack、Parcel)则会把混淆后的产物直接交回打包工具,因此不会写入任何未混淆的内容。

如果你已经设置了 compiler.runAfterProductionCompile 钩子,它会被组合而不是被替换。你的钩子先运行,AfterPack 的钩子后运行,因此你的钩子看到的字节和未安装 AfterPack 时一样。

传给 withAfterpack 的选项会被该钩子闭包捕获。它们的优先级高于 AFTERPACK_* 环境变量,而环境变量的优先级又高于 afterpack.json。使用 paths.exclude 可以让某个文件保持不变。

合并后的 protectionMap.html 会写入到项目根目录下已被 gitignore 的 .afterpack/ 中。

验证

每次受保护的构建都会向 .next/.afterpack-protection.json 写入一份回执:引擎版本、种子、打包工具、构建 id,以及每个被混淆文件的 sha256。请在部署步骤中校验它:

$ npx afterpack@latest verify .

当回执缺失、属于另一次构建,或任何已记录文件的哈希值与混淆时不一致时,该命令会以非零状态退出。

Protection Map 默认只在打包工具输出了 source map 且构建不在 CI 中(CI=true 或 CI=1)时写出。除非开启 productionBrowserSourceMaps,否则 Next.js 不会输出浏览器 source map,因此 Protection Map 默认关闭。要在本地查看完整产物,请显式启用它:

export default withAfterpack(nextConfig, { protectionMap: { enabled: true } });

该报告会保存在已被 gitignore 的 .afterpack/protectionMap.html 中,其中包含你的完整源码,切勿提交或对外提供该文件。

切勿在生产环境中发布 source map

.map 文件包含你的原始源码。只有当你希望 Protection Map 显示原始源码时,才设置 productionBrowserSourceMaps: true。该钩子会从对外提供的目录中移除这些 map,并在生产环境中保持 //# sourceMappingURL 注释不出现。

选项

每个配置项都可以作为 withAfterpack 的第二个参数传入,例如 withAfterpack(nextConfig, { preset: "hard", complexity: 40 })。

指令

指令由插件从你的源码中读取,需要设置 productionBrowserSourceMaps: true 才能在构建出的代码块中定位到它们。参见指令。

边界情况

  • 不支持 experimental.sri。 Next 会在写入资源的同时计算其完整性哈希,发生在任何构建钩子运行之前,因此之后被重写的代码块会被浏览器拦截。请移除 experimental.sri,或移除 withAfterpack。
  • 指向 .next/,而不是 dist/。 如果你使用通用 CLI 而不是这个包,等价的目标是 npx afterpack@latest .next/static/chunks,但它不会替你移除对外提供的 map。
  • 对未混淆的文件失败即中止,没有覆盖选项。 如果引擎无法安全地混淆某个文件,构建会失败并指出具体文件。如果某个文件永远不应被混淆,请用 paths.exclude 把它完全排除在处理之外。

下一步