# Obfuscate an esbuild build

@afterpack/esbuild hooks build.onEnd and obfuscates the emitted output in place.

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

`@afterpack/esbuild` is a standard esbuild plugin (`{ name, setup(build) }`). It hooks `build.onEnd`, which fires once esbuild has finished writing output to disk (`write !== false`, the default).

It handles both output shapes: `outdir` (many files) and `outfile` (a single file). It skips a build that already errored.

## Install

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

```js
// build.mjs
import { build } from "esbuild";
import { afterpackEsbuild } from "@afterpack/esbuild";

await build({
  entryPoints: ["src/main.js"],
  bundle: true,
  format: "esm",
  outfile: "dist/app.js",
  plugins: [afterpackEsbuild()],
});
```

The named export is `afterpackEsbuild`.

## Build

The plugin runs on `build.onEnd`, after esbuild has finished writing output to disk, for `outdir` and `outfile` alike. There is no separate dev command for it to skip: every `build()` call that writes files runs the plugin.

esbuild exposes no in-pipeline hook, so your unobfuscated bundle is on disk until the pass completes — tens of milliseconds on a real build — and anything that can read the output directory in that window can read it. A failed obfuscation leaves that output in place: the build fails, so nothing ships, but the files stay until the next build overwrites them.

## Verify

The [Protection Map](https://www.afterpack.dev/docs/protection-map) default depends on whether the bundler emits a [source map](https://www.afterpack.dev/docs/config#sourceMap-enabled), and flips off in [production](https://www.afterpack.dev/docs/config#build-mode). Ask for it explicitly:

```js
afterpackEsbuild({ protectionMap: { enabled: true } });
```

It lands in the gitignored `.afterpack/protectionMap.html`. Without `sourcemap: true` the plugin warns that it found no bundler map to render original source from. The report contains your full source. Never commit it or serve it.

## Options

Every [configuration key](https://www.afterpack.dev/docs/config) can be passed here as the options object, for example `afterpackEsbuild({ preset: "hard", complexity: 40 })`.

## Directives

Directives are recovered from the bundler's source map, so build with `sourcemap: true` if you use them. See [Directives](https://www.afterpack.dev/docs/directives).

## Edge cases

- **[Fail-closed](https://www.afterpack.dev/docs/diagnostics#fail-closed-no-exceptions).** A failed file or an empty output rejects the build.
- **`write: false`.** Nothing is on disk, so the plugin does nothing. AfterPack needs written files.
- **TypeScript.** esbuild has already stripped types by the time AfterPack runs; maps chain back to your `.ts` lines.

## Next

- [Frameworks](https://www.afterpack.dev/docs/frameworks): the full matrix.
- [Vite](https://www.afterpack.dev/docs/frameworks/vite): the reference integration and full option semantics.
- [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.
- [Directives](https://www.afterpack.dev/docs/directives): every marker key, and what it does.
- [Configuration](https://www.afterpack.dev/docs/config): the engine config surface.
