# Obfuscate a Parcel build

@afterpack/parcel-optimizer obfuscates each packaged bundle in memory as a Parcel 2 Optimizer, and fails the build closed rather than ship a chunk that 404s in production.

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

`@afterpack/parcel-optimizer` is a Parcel 2 **Optimizer**, registered in `.parcelrc`. It obfuscates each packaged bundle **in memory**, during Parcel's own optimize phase, so the bundle never touches disk in cleartext.

> **Not @afterpack/parcel**
>
> The package is `@afterpack/parcel-optimizer`.

## Install

```bash
npm install -D @afterpack/parcel-optimizer
```

```jsonc
// .parcelrc
{
  "extends": "@parcel/config-default",
  "optimizers": {
    "*.{js,mjs,cjs}": ["...", "@afterpack/parcel-optimizer"]
  }
}
```

The `"..."` spread matters: it keeps Parcel's default optimizers (`@parcel/optimizer-swc`) and puts AfterPack **last**. AfterPack runs after minification. Peer dependency: Parcel `>=2.9.0`.

## Build

`.parcelrc` entries are bare package names with no inline options, so options come from `afterpack.json` in your project root, the one config file every AfterPack integration reads:

```json
{
  "seed": "git",
  "preset": "medium"
}
```

Only project-root artifacts land; nothing goes beside your bundles:

- **One [`protectionMap.html`](https://www.afterpack.dev/docs/protection-map) per bundle**, at `.afterpack/<bundle>.<id>.protectionMap.html`.
- A source map, when the [sourceMap policy](https://www.afterpack.dev/docs/config#sourceMap-enabled) enables one. It is returned to Parcel rather than written directly, so Parcel names and emits it with its own content hash.

The plugin also appends its guard globs (`.afterpack/`, `*.protectionMap.html`, `*.backup.*`, `*.map`) to your project `.gitignore`.

## Verify

Because there's no combined report, check the bundle you care about directly: `.afterpack/<bundle-name>.<id>.protectionMap.html` for each entry or lazy chunk Parcel packaged. [Maps are off in production](https://www.afterpack.dev/docs/config#sourceMap-enabled) by default; set `"sourceMap": { "enabled": true }` in `afterpack.json` to override. Each report contains your full source. Never commit it or serve it.

## Options

Every [configuration key](https://www.afterpack.dev/docs/config) can be set there, for example `{ "preset": "hard", "complexity": 40 }`. [`protectionMap.enabled`](https://www.afterpack.dev/docs/config#protectionMap-enabled) depends on whether the bundle carries a source map rather than on [build mode](https://www.afterpack.dev/docs/config#build-mode), and always lands in the gitignored `.afterpack/`. There is no `build.backup` option on this plugin. Setting it warns. Explicit options always win over the production defaults.

## Directives

Directives are read from your source by the plugin. With no usable source map, the pass has nothing to recover and says so.

**A directive in your entry module is not recovered.** The pass reports `DIAG_DIRECTIVE_SOURCE_UNRESOLVED` and names the module. Directives in every other, non-entry module work normally once source maps are on. Move the guarded code into an imported module if you need an entry-module directive.

## Edge cases

### Content-hash placeholders can fail your build

With content hashing on (the default for `parcel build`) and a target without native ESM, Parcel's content-hash placeholders end up inside obfuscated strings, and every code-split chunk 404s in production.

This plugin detects the collision and fails the build instead of shipping it:

```
AfterPack obfuscated 1 Parcel content-hash placeholder(s) in app.[hash].js (HASH_REF_...).
  hint: Run `parcel build --no-content-hash` (chunk URLs stop being content-addressed).
  hint: Or set "complexity": 0 in afterpack.json to ship minify-only.
```

Parcel's default ESM output is unaffected.

### Worth knowing

- **One pass per bundle, in worker processes.** Parcel runs optimizers per bundle, and bundles packaged in different worker processes cannot share state. Bundles packaged in the same process share one seed. To pin one seed across every worker, set [`seed`](https://www.afterpack.dev/docs/config#seed) explicitly or export `AFTERPACK_seed`.
- **Parcel caches optimizer output.** A rebuild with a warm `.parcel-cache` and unchanged input does not re-run AfterPack, and does not rotate an unpinned seed. Clear `.parcel-cache` for a fresh pass; CI builds from a cold cache anyway.
- **Two harmless warnings.** Parcel prints `ES module dependencies are experimental` (every AfterPack package is ESM) and reports that the plugin `contains non-statically analyzable dependencies` (the native engine's runtime binary resolution). Neither affects the build.

### Without the plugin: the CLI

Build with Parcel as usual, then point AfterPack at the output:

```bash
npx parcel build
```

```bash
npx afterpack@latest dist/ --preset=medium --seed=git
```

The [path is a required argument](https://www.afterpack.dev/docs/cli#usage); there's no auto-detection. AfterPack walks the directory, collecting every `.js`/`.mjs`/`.cjs` and obfuscating them [in place](https://www.afterpack.dev/docs/cli#what-a-run-does). A CLI run gets no content-hash guard, and one combined [`protectionMap.html`](https://www.afterpack.dev/docs/protection-map) for the whole directory instead of one per bundle. [Fail-closed](https://www.afterpack.dev/docs/diagnostics#fail-closed-no-exceptions) still holds: a missing build dir, no JS files, an `error`/`critical` [diagnostic](https://www.afterpack.dev/docs/diagnostics), or a file the engine couldn't obfuscate all exit `1`.

## Next

- [Frameworks](https://www.afterpack.dev/docs/frameworks): the full matrix.
- [esbuild](https://www.afterpack.dev/docs/frameworks/esbuild#directives): another plugin that recovers directives from a source map after minification.
- [CLI reference](https://www.afterpack.dev/docs/cli): the fallback path's flags in full.
- [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): every option, and which surface can set it.
- [Presets](https://www.afterpack.dev/docs/presets): the five-rung ladder.
