# 混淆 Next.js 构建

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

Source: https://www.afterpack.dev/zh/docs/frameworks/nextjs

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

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

## 安装

```bash
npm install -D @afterpack/next
```

```ts
// 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` 永远不会到达浏览器,因此不会被混淆。该钩子会重命名每个内容哈希代码块,使其文件名[跟随其混淆后的字节](https://www.afterpack.dev/zh/docs/builds#file-names-and-caching),并改写对它的引用。该钩子在编译之后、预渲染和静态导出之前运行,因此静态导出会把混淆后的代码块复制进 `out/`。

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

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

传给 `withAfterpack` 的选项会被该钩子闭包捕获。它们的优先级高于 `AFTERPACK_*` 环境变量,而环境变量的优先级又高于 `afterpack.json`。使用 [`paths.exclude`](https://www.afterpack.dev/zh/docs/config#paths-exclude) 可以让某个文件保持不变。

合并后的 [`protectionMap.html`](https://www.afterpack.dev/zh/docs/protection-map) 会写入到项目根目录下已被 gitignore 的 `.afterpack/` 中。

## 验证

每次受保护的构建都会向[项目根目录](https://www.afterpack.dev/zh/docs/cli#files)中、`.next/` 之外的 `.afterpack/receipts/.next.json` 写入一份回执:引擎版本、种子、打包工具、构建 id,以及每个被混淆文件的 sha256。请在部署步骤中校验它:

```bash
npx afterpack@latest verify .
```

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

静态导出的 `out/` 是这些 chunk 的副本，因此 `npx afterpack@latest verify out` 会按同一份回执检查它，并点名任何被改动的文件。

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

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

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

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

## 选项

每个[配置项](https://www.afterpack.dev/zh/docs/config)都可以作为 `withAfterpack` 的第二个参数传入,例如 `withAfterpack(nextConfig, { preset: "hard", complexity: 40 })`。

## 指令

指令由插件从你的源码中读取,需要设置 `productionBrowserSourceMaps: true` 才能在构建出的代码块中定位到它们。参见[指令](https://www.afterpack.dev/zh/docs/directives)。

## 边界情况

- **不支持 `experimental.sri`。** Next 会在写入资源的同时计算其完整性哈希,发生在任何构建钩子运行之前,因此之后被重写的代码块会被浏览器拦截。请移除 `experimental.sri`,或移除 `withAfterpack`。
- **固定的 `generateBuildId` 会让内容已变的文件留在同一个 URL 上。** 如果构建 ID 在多次发布之间保持不变,`static/<buildId>/_buildManifest.js` 和 `_ssgManifest.js` 会保持原有 URL,而它们的字节却变了,构建会对此发出警告。请不要设置 `generateBuildId`,让 Next 为每次构建生成随机 ID,或者每次发布都返回一个新的 ID。
- **指向 `.next/`,而不是 `dist/`。** 如果你使用[通用 CLI](https://www.afterpack.dev/zh/docs/cli#usage) 而不是这个包,等价的目标是 `npx afterpack@latest .next/static/chunks`,但它不会替你移除对外提供的 map。
- **对未混淆的文件[失败即中止](https://www.afterpack.dev/zh/docs/diagnostics#fail-closed-no-exceptions),没有覆盖选项。** 如果引擎无法安全地混淆某个文件,构建会失败并指出具体文件。如果某个文件永远不应被混淆,请用 [`paths.exclude`](https://www.afterpack.dev/zh/docs/config#paths-exclude) 把它完全排除在处理之外。

## 下一步

- [框架](https://www.afterpack.dev/zh/docs/frameworks):完整矩阵。
- [快速开始](https://www.afterpack.dev/zh/docs/quickstart):安装、构建与验证。
- [构建与 CI](https://www.afterpack.dev/zh/docs/builds):种子、开发与生产的区别、发布同一份字节。
- [Protection Map](https://www.afterpack.dev/zh/docs/protection-map):如何阅读该报告。
- [指令](https://www.afterpack.dev/zh/docs/directives):每个标记键及其作用。
- [配置](https://www.afterpack.dev/zh/docs/config):引擎的配置面。
