# Obfuscate a Vite build

@afterpack/vite obfuscates your build in generateBundle, before Vite writes it. It is the reference integration every other Vite-family package wraps.

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

`@afterpack/vite` is the reference integration. [Astro](https://www.afterpack.dev/docs/frameworks/astro), [Svelte](https://www.afterpack.dev/docs/frameworks/svelte), [SvelteKit](https://www.afterpack.dev/docs/frameworks/sveltekit), [Vue](https://www.afterpack.dev/docs/frameworks/vue), and [Nuxt](https://www.afterpack.dev/docs/frameworks/nuxt) all wrap it, so what this page describes applies to those packages too.

It reads [`@afterpack` directives](https://www.afterpack.dev/docs/directives) from your source, then runs the engine in `generateBundle`, with the finished bundle in memory and before Vite writes any of it.

## Install

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

```ts
// vite.config.ts
import { defineConfig } from "vite";
import { afterpackVite } from "@afterpack/vite";

export default defineConfig({
  plugins: [afterpackVite()],
});
```

## Build

That is the whole setup. `npm run build` now emits obfuscated output; `vite dev` is untouched, because the plugin only acts on a build's `generateBundle`. Vite writes the obfuscated chunks and never the originals, and a failed obfuscation leaves `outDir` as it was.

What it obfuscates is what Vite built: every chunk in the output bundle. Files Vite copies verbatim from `publicDir` into the output directory are not built, are not in the bundle, and are not touched. Put anything that needs protecting through the bundler.

The plugin writes:

- [`protectionMap.html`](https://www.afterpack.dev/docs/protection-map): one combined map for the build, always written into the gitignored `.afterpack/` at your project root. It embeds your original source. Never let it land in a served directory.
- `foo.js.map`: the [source map](https://www.afterpack.dev/docs/config#sourceMap-enabled), when maps are enabled. When maps are off, which is the production default, Vite's own map entry is dropped rather than written beside the obfuscated chunk.

There is no `foo.backup.<hash>.js` here. The obfuscated bytes go back into the bundle, so there is no emitted file for a backup to sit beside, and [`build.backup: true`](https://www.afterpack.dev/docs/config#build-backup) warns instead of writing one.

The plugin also appends `.afterpack/`, `protectionMap.html`, `*.protectionMap.html`, `*.backup.*` and `*.map` to your `.gitignore`, and warns if an artifact lands under a served path.

## Verify

`vite build` sets `NODE_ENV=production`, so the [Protection Map](https://www.afterpack.dev/docs/protection-map) is off by default in a normal [production build](https://www.afterpack.dev/docs/config#build-mode). Ask for it explicitly when you want to inspect one:

```ts
afterpackVite({ protectionMap: { enabled: true } });
```

With `build.sourcemap` unset, that also switches Vite's own source maps on, so the map can render your original source. Without a discoverable map, the plugin has nothing to render original source from and says so.

The report is written to `.afterpack/protectionMap.html`. It contains your full source. Never commit it or serve it, and review it locally only.

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

> **Never ship source maps to production**
>
> A `.map` file reaches your original source. Never deploy one.

## Options

Every [configuration key](https://www.afterpack.dev/docs/config) can be passed here as the options object, for example `afterpackVite({ preset: "hard", complexity: 40 })`. All of them are optional. The named export is `afterpackVite`.

`preset` and `complexity` set different things. A [preset](https://www.afterpack.dev/docs/presets) is a bundle: it sets the complexity target, the total output multiplier, and the [inflation budget](https://www.afterpack.dev/docs/config#inflation-max) together. `complexity` is one number inside that bundle:

```ts
afterpackVite({ preset: "hard", complexity: 40 }); // hard's size limits, target 40
```

## Directives

Directives are read from your source by the plugin, before any minifier strips comments. The [grammar](https://www.afterpack.dev/docs/directives#writing-one) is block comments only:

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

`// @afterpack …` line comments are not scanned and are reported as [diagnostics](https://www.afterpack.dev/docs/diagnostics).

Every key a directive can set is on the [Directives](https://www.afterpack.dev/docs/directives#what-you-can-set) page.

Capture skips `node_modules` and non-JS/TS files. A directive inside a `.vue` or `.svelte` single-file component is not captured. Put it in a plain `.ts`/`.js` module the component imports instead. Set [`directives.enabled: false`](https://www.afterpack.dev/docs/config#directives-enabled) to skip the scan entirely.

## Edge cases

### Library mode

`build.lib` output is a public contract. AfterPack runs on the finished bundle, so exported names survive; internals are still renamed and restructured.

### Multiple outputs

Each Vite build obfuscates its own bundle and nothing else, so separate builds are separate passes even when they write to one directory. Pin the same [`seed`](https://www.afterpack.dev/docs/config#seed) across them if they share a runtime contract.

## Next

- [Frameworks](https://www.afterpack.dev/docs/frameworks): the full matrix.
- [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.
- [Directives](https://www.afterpack.dev/docs/directives): every marker key, and what it does.
- [Presets](https://www.afterpack.dev/docs/presets): the five-rung ladder.
