# 混淆一次 webpack 构建

@afterpack/webpack 挂钩 compilation.hooks.processAssets，在 webpack 写入资产之前对其进行混淆。

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

`@afterpack/webpack` 是一个 webpack 5 插件:一个带有 `apply(compiler)` 方法的类。它会在压缩之前从你的源代码中读取 [`@afterpack` 指令](https://www.afterpack.dev/zh/docs/directives),然后在 webpack 分配内容哈希文件名之后、写入之前,立即在 `compilation.hooks.processAssets` 上运行引擎。

每个资产的字节都会从编译结果中读出,混淆后的替代内容再交还给它,因此 webpack 永远不会写出未混淆的打包产物。一次失败的混淆会让输出目录保持原样。

## 安装

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

```js
// webpack.config.js
import { AfterpackWebpackPlugin } from "@afterpack/webpack";

export default {
  mode: "production",
  output: { path: "dist" },
  plugins: [new AfterpackWebpackPlugin()],
};
```

导出的是 `AfterpackWebpackPlugin` 这个类。

## 构建

该插件紧接在 webpack 的内容哈希阶段之后挂钩 `compilation.hooks.processAssets`。每个 chunk 的文件名都[跟随其混淆后的字节](https://www.afterpack.dev/zh/docs/builds#file-names-and-caching)。只有某个 chunk 声明的 `.js`、`.mjs` 和 `.cjs` 资产会被重写;`.hot-update.` chunk 会被跳过。编译器携带但并非自己构建出来的资产,比如另一个插件生成的清单文件或复制过来的文件,会被原样保留。

压缩插件在更晚的阶段运行,因此它们压缩的是混淆后的字节。

`processAssets` 在每次编译时都会运行,包括监听模式下的重新构建,每次重新构建都要付出引擎的开销。在开发循环中设置 [`build.autorun: false`](https://www.afterpack.dev/zh/docs/config#build-autorun)(或 `AFTERPACK_build_autorun=false`),生产构建则保持开启。

## 验证

[Protection Map](https://www.afterpack.dev/zh/docs/protection-map) 默认会在 `devtool` 生成 source map 且构建不在 CI 中(`CI=true` 或 `CI=1`)时写出;[生产构建模式](https://www.afterpack.dev/zh/docs/config#build-mode)不会关闭它。生产环境的 webpack 构建默认没有 `devtool`,因此 Protection Map 是关闭的。要显式请求它:

```js
new AfterpackWebpackPlugin({ protectionMap: { enabled: true } });
```

[map 的默认值](https://www.afterpack.dev/zh/docs/config#protectionMap-enabled)与打包工具是否存在 source map 相耦合,因此如果你想让它渲染出你的原始源码,就设置 `devtool: "source-map"`。如果没有,插件会警告说没有找到打包工具的 source map。该文件总是写入已被 gitignore 的 `.afterpack/protectionMap.html`,绝不会写进 `dist/`。它包含你的完整源码。绝不要提交它或对外提供服务。

*(live Protection Map demo embed — see https://www.afterpack.dev/protection-map-demo)*

## 选项

每一个[配置键](https://www.afterpack.dev/zh/docs/config)都可以作为选项对象在这里传入,例如 `new AfterpackWebpackPlugin({ preset: "hard", complexity: 40 })`。这些名称与 [`@afterpack/vite`](https://www.afterpack.dev/zh/docs/frameworks/vite#options) 和 [`@afterpack/next`](https://www.afterpack.dev/zh/docs/frameworks/nextjs#options) 完全一致,因此配置可以在各个包之间直接移植。

## 指令

插件会从你的源代码中读取指令。

```js
/* @afterpack preset=hard */
function verifyLicense(token) {
  // ...
}
/* @afterpack end */
```

指令只能是块注释:`// @afterpack …` 这样的行注释不会被扫描,并会作为[诊断信息](https://www.afterpack.dev/zh/docs/diagnostics)出现。完整语法见[指令](https://www.afterpack.dev/zh/docs/directives#writing-one)页面。

捕获会跳过 `node_modules` 和非 JS/TS 模块。设置 [`directives.enabled: false`](https://www.afterpack.dev/zh/docs/config#directives-enabled) 可以完全跳过扫描;手写的 [`regions`](https://www.afterpack.dev/zh/docs/config#regions) 在两种情况下都照常生效。

## 边界情况

- **第三方依赖代码。** webpack 会把 `node_modules` 一起打包进你的 chunk,因此不存在按输出文件名区分它们的规律。AfterPack 会整体处理生成的文件;需要时,[`paths.exclude`](https://www.afterpack.dev/zh/docs/config#paths-exclude) 是按文件粒度排除的方式。
- **不支持 `webpack-subresource-integrity`。** 它的 `integrity` 属性将与混淆后的 chunk 不匹配。请移除它,并在构建之后根据生成的文件计算完整性。
- **失败即中止,没有覆盖选项。** 某个文件处理失败或结果为空会让构建失败,而不是发布未混淆的代码。

## 下一步

- [框架](https://www.afterpack.dev/zh/docs/frameworks):完整对照表。
- [Vite](https://www.afterpack.dev/zh/docs/frameworks/vite):参考实现以及完整的选项语义。
- [配置](https://www.afterpack.dev/zh/docs/config):上面每一个选项,以及哪个入口能设置它。
- [指令](https://www.afterpack.dev/zh/docs/directives):完整的标记语法。
- [预设](https://www.afterpack.dev/zh/docs/presets):`preset` 所对应的阶梯。
- [构建与 CI](https://www.afterpack.dev/zh/docs/builds):种子、开发环境与生产环境的区别、提升同一份字节。
- [Protection Map](https://www.afterpack.dev/zh/docs/protection-map):如何阅读这份报告。
