@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.
The package is @afterpack/parcel-optimizer.
Install
$ npm install -D @afterpack/parcel-optimizer// .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:
{
"seed": "git",
"preset": "medium"
}Only project-root artifacts land; nothing goes beside your bundles:
- One
protectionMap.htmlper bundle, at.afterpack/<bundle>.<id>.protectionMap.html. - A source map, when the sourceMap policy 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 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 can be set there, for example { "preset": "hard", "complexity": 40 }. protectionMap.enabled depends on whether the bundle carries a source map rather than on 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
seedexplicitly or exportAFTERPACK_seed. - Parcel caches optimizer output. A rebuild with a warm
.parcel-cacheand unchanged input does not re-run AfterPack, and does not rotate an unpinned seed. Clear.parcel-cachefor 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 plugincontains 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:
$ npx parcel build$ npx afterpack@latest dist/ --preset=medium --seed=gitThe path is a required argument; there's no auto-detection. AfterPack walks the directory, collecting every .js/.mjs/.cjs and obfuscating them in place. A CLI run gets no content-hash guard, and one combined protectionMap.html for the whole directory instead of one per bundle. Fail-closed still holds: a missing build dir, no JS files, an error/critical diagnostic, or a file the engine couldn't obfuscate all exit 1.
Next
- Frameworks: the full matrix.
- esbuild: another plugin that recovers directives from a source map after minification.
- CLI reference: the fallback path's flags in full.
- Builds & CI: seeds, dev-vs-prod, promoting the same bytes.
- Protection Map: reading the report.
- Configuration: every option, and which surface can set it.
- Presets: the five-rung ladder.