# 混淆 Angular 应用

Angular v17+ 的混淆是对 dist/<app>/browser 的一次构建后处理:可以通过 CLI,也可以通过 @afterpack/angular 辅助工具。

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

现代 Angular(v17+)使用 `@angular-devkit/build-angular:application` 构建。该构建器基于 esbuild,但不对外暴露插件钩子,因此与 [Vite](https://www.afterpack.dev/zh/docs/frameworks/vite)、[webpack](https://www.afterpack.dev/zh/docs/frameworks/webpack) 和 [esbuild](https://www.afterpack.dev/zh/docs/frameworks/esbuild) 插件不同,AfterPack 无法在这里介入构建过程。

这里的集成是对生成的浏览器端打包产物做一次构建后处理。`@afterpack/angular` 是对该处理过程的便捷封装,它不是一个构建器插件。

## 安装

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

```js
// scripts/obfuscate.mjs — run after `ng build`
import { afterpackAngular } from "@afterpack/angular";

await afterpackAngular({ seed: "git" });
```

或者直接运行 [CLI](https://www.afterpack.dev/zh/docs/cli),无需安装任何东西。

## 构建

该应用构建器会把客户端打包产物写入 `dist/<app>/browser`。

使用辅助工具时,`findAngularBrowserDir(distRoot)` 会定位浏览器端输出。在 monorepo 中请传入 `browserDir`。如果你只需要这个定位函数,`findAngularBrowserDir` 也单独导出。

使用 CLI 时,直接把它指向浏览器端输出:

```jsonc
// package.json
{
  "scripts": {
    "build": "ng build && afterpack dist/my-app/browser --seed=git"
  }
}
```

[把 CLI 指向该目录](https://www.afterpack.dev/zh/docs/cli#usage),每个生成的 `.js` 代码块都会被原地混淆。CLI 的[备份](https://www.afterpack.dev/zh/docs/config#build-backup)会落在项目根目录下的 `.afterpack/backups/` 中,位于 `dist/` 之外,因此不会随部署一起发布;`npx afterpack@latest restore` 可以撤销这次处理。需要特别留意的是 `@afterpack/angular` 辅助工具:在那里设置 `build: { backup: true }` 会在 `dist/` 中写入一个 `.backup.<hash>` 同级文件,而这正是你部署的目录树中的原始源码。

辅助工具还会重命名每个内容哈希文件,使其文件名[跟随其混淆后的字节](https://www.afterpack.dev/zh/docs/builds#file-names-and-caching),并重新计算 Angular 为未混淆文件写下的哈希:`subresourceIntegrity` 选项生成的 `integrity` 属性,以及 service worker 的 `ngsw.json` 哈希表。把 [CLI](https://www.afterpack.dev/zh/docs/cli#content-hashed-file-names) 指向 `dist/<app>/browser/` 时,它也会做这两件事。如果 SSR 构建的 `angular-app-manifest.mjs` 内嵌了资源哈希,处理流程会失败,CLI 也会拒绝它。

构建器会在处理阶段运行之前写入打包产物,因此在处理完成之前,未混淆的产物会一直留在磁盘上,在此期间任何能读取 `dist/` 的进程都能读到它。处理失败会让该输出原样留在原地:构建失败,因此不会有任何内容发布,但这些文件会一直保留,直到下一次构建将其覆盖。

[失败即中止](https://www.afterpack.dev/zh/docs/diagnostics#fail-closed-no-exceptions):如果浏览器目录中没有任何 `.js`/`.mjs`/`.cjs` 文件,或引擎报告了失败的[诊断信息](https://www.afterpack.dev/zh/docs/diagnostics),`afterpackAngular` 会抛出异常。

## 验证

为你想查看的构建开启 Angular 自身在 `angular.json` 中的 `sourceMap` 构建选项,这样 [Protection Map](https://www.afterpack.dev/zh/docs/protection-map) 才能渲染出你的原始源码。它会写入已被 gitignore 的 `.afterpack/protectionMap.html`,其中包含你的完整源码,切勿提交或对外提供该文件。

CLI 和辅助工具都只在存在 source map 且构建不在 CI 中(`CI=true` 或 `CI=1`)时默认写出 Protection Map;[生产](https://www.afterpack.dev/zh/docs/config#build-mode)模式不会关闭它。`ng build` 的生产配置不输出 source map,因此在那里 Protection Map 默认关闭。`protectionMap: { enabled: true }` 会强制开启它。

> **切勿把 source map 发布到你的 CDN**
>
> 生产环境的 source map 可以还原出你的原始源码。为这次构建生成它,然后不要把它包含在你部署的 `dist/` 中。

## 选项

每个[配置项](https://www.afterpack.dev/zh/docs/config)都可以作为选项对象在这里传入,例如 `afterpackAngular({ preset: "hard", complexity: 40 })`。以下选项仅存在于这个包中,用于定位构建输出:

| 选项 | 类型 | 默认值 |
|---|---|---|
| `cwd` | `string` | `process.cwd()`;项目根目录，`afterpack.json` 的查找和 `distRoot` 都从这里开始 |
| `distRoot` | `string` | `<cwd>/dist`;查找 `browser` 子目录的位置 |
| `browserDir` | `string` | 未设置,完全跳过自动定位 |

这里没有 `build.autorun` 选项。

## 指令

`directives.enabled` 在这里**任何层级都会被拒绝**:选项对象、`AFTERPACK_directives_enabled` 和
`afterpack.json` 皆是如此。Angular 的应用构建器是封闭的,因此这个处理阶段看到的只是已经压缩过的输出,没有任何钩子可以从中捕获 `/* @afterpack ... */` 注释。设置它会导致构建失败,而不是被静默忽略。手写的
[`regions`](https://www.afterpack.dev/zh/docs/config#regions) 仍然有效,因为它们指定的是字节范围,而不是注释。

## 边界情况

**旧版 Angular(webpack)。** 仍在使用较旧的基于 webpack 的构建器的项目,可以通过自定义构建器,用 [`@afterpack/webpack`](https://www.afterpack.dev/zh/docs/frameworks/webpack) 在构建过程中完成混淆。上面介绍的构建后处理方式在两种构建器上都能统一工作,是推荐的方式。

## 下一步

- [框架](https://www.afterpack.dev/zh/docs/frameworks):完整矩阵。
- [CLI 参考](https://www.afterpack.dev/zh/docs/cli):上面用到的参数。
- [构建与 CI](https://www.afterpack.dev/zh/docs/builds):种子、开发与生产的区别、发布同一份字节。
- [Protection Map](https://www.afterpack.dev/zh/docs/protection-map):如何阅读该报告。
- [配置](https://www.afterpack.dev/zh/docs/config):引擎的配置面。
- [诊断信息](https://www.afterpack.dev/zh/docs/diagnostics#exit-codes):失败的处理会返回什么。
