# Obfuscate an Angular app

Angular v17+ obfuscation is a postbuild pass over dist/<app>/browser: via the CLI, or the @afterpack/angular helper.

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

Modern Angular (v17+) builds with `@angular-devkit/build-angular:application`. That builder is esbuild-based but exposes no plugin hook, so, unlike the [Vite](https://www.afterpack.dev/docs/frameworks/vite), [webpack](https://www.afterpack.dev/docs/frameworks/webpack) and [esbuild](https://www.afterpack.dev/docs/frameworks/esbuild) plugins, AfterPack cannot hook into the build here.

The integration is a postbuild pass over the emitted browser bundle. `@afterpack/angular` is a convenience wrapper over that pass. It is not a builder plugin.

## Install

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

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

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

Or run [the CLI](https://www.afterpack.dev/docs/cli) directly instead, with nothing to install.

## Build

The application builder writes the client bundle to `dist/<app>/browser`.

With the helper, `findAngularBrowserDir(distRoot)` finds the browser output. Pass `browserDir` in a monorepo. `findAngularBrowserDir` is also exported separately if you want just the locator.

With the CLI, point it at the browser output directly:

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

[Point the CLI at it](https://www.afterpack.dev/docs/cli#usage) and every emitted `.js` chunk is obfuscated in place. The CLI's [backup](https://www.afterpack.dev/docs/config#build-backup) lands in `.afterpack/backup/` at the project root, outside `dist/`, so it does not ship with the deploy; `npx afterpack@latest restore` undoes the run. The `@afterpack/angular` helper is the case to watch: `build: { backup: true }` there writes a `.backup.<hash>` sibling into `dist/`, which is your original source inside the tree you deploy.

The builder writes the bundle before the pass can run, so your unobfuscated bundle is on disk until the pass completes, and anything that can read `dist/` in that window can read it. A failed pass leaves that output in place: the build fails, so nothing ships, but the files stay until the next build overwrites them.

[Fail-closed](https://www.afterpack.dev/docs/diagnostics#fail-closed-no-exceptions): `afterpackAngular` throws if the browser directory holds no `.js`/`.mjs`/`.cjs`, or if the engine reports a failing [diagnostic](https://www.afterpack.dev/docs/diagnostics).

## Verify

Enable `sourceMap: { enabled: true }` for the build you want to inspect, so the [Protection Map](https://www.afterpack.dev/docs/protection-map) can render your original source. It writes to the gitignored `.afterpack/protectionMap.html`. It contains your full source. Never commit it or serve it.

Both the CLI and the helper flip the Protection Map off under [`NODE_ENV=production` / `CI=true`](https://www.afterpack.dev/docs/config#build-mode); the helper accepts `protectionMap: { enabled: true }` to force it back on, with a warning.

> **Never ship source maps to your CDN**
>
> A production source map reaches your original source. Generate it for the build, then withhold it from your deployed `dist/`.

## Options

Every [configuration key](https://www.afterpack.dev/docs/config) can be passed here as the options object, for example `afterpackAngular({ preset: "hard", complexity: 40 })`. These options exist only on this package, for locating the build output:

| Option | Type | Default |
|---|---|---|
| `cwd` | `string` | `process.cwd()`; the project root that owns `.gitignore` and `.afterpack/` |
| `distRoot` | `string` | `<cwd>/dist`; where to look for the `browser` subdirectory |
| `browserDir` | `string` | unset. Skips auto-location entirely |

There is no `build.autorun` option here.

## Directives

`directives` is **refused here, in every layer** — the options object, `AFTERPACK_directives` and
`afterpack.json` alike. Angular's application builder is sealed, so this pass only ever sees
already-minified output and has no hook to capture `/* @afterpack ... */` comments from. Setting it
fails the build rather than being silently ignored. Hand-authored
[`regions`](https://www.afterpack.dev/docs/config#regions) still work, because they name byte ranges instead of comments.

## Edge cases

**Legacy Angular (webpack).** Projects still on the older webpack-based builder can obfuscate in-build with [`@afterpack/webpack`](https://www.afterpack.dev/docs/frameworks/webpack) via a custom builder. The postbuild path above works uniformly across both builders and is the recommended one.

## Next

- [Frameworks](https://www.afterpack.dev/docs/frameworks): the full matrix.
- [CLI reference](https://www.afterpack.dev/docs/cli): the flags used above.
- [Builds & CI](https://www.afterpack.dev/docs/builds): seeds, dev-vs-prod, promoting the same bytes.
- [Protection Map](https://www.afterpack.dev/docs/protection-map): reading the report.
- [Configuration](https://www.afterpack.dev/docs/config): the engine config surface.
- [Diagnostics](https://www.afterpack.dev/docs/diagnostics#exit-codes): what a failing pass returns.
