This page is available in English only.

Obfuscate a webpack build

@afterpack/webpack taps compilation.hooks.processAssets and obfuscates your assets before webpack writes them.

@afterpack/webpack is a webpack 5 plugin: a class with apply(compiler). It reads @afterpack directives from your source before minification, then runs the engine on compilation.hooks.processAssets, at the last stage before webpack writes.

Each asset's bytes are read out of the compilation and the obfuscated replacement is handed back to it, so webpack never writes your unobfuscated bundle. A failed obfuscation leaves the output directory as it was.

Install

$ npm install -D @afterpack/webpack
// webpack.config.js
import { AfterpackWebpackPlugin } from "@afterpack/webpack";

export default {
  mode: "production",
  output: { path: "dist" },
  plugins: [new AfterpackWebpackPlugin()],
};

The export is the class AfterpackWebpackPlugin.

Build

The plugin taps compilation.hooks.processAssets at the last stage before webpack writes. Only the .js, .mjs and .cjs assets a chunk of the compilation claims are rewritten; .hot-update. chunks are skipped. An asset the compiler carries but did not build, such as a manifest another plugin emitted or a copied file, is left alone.

An integrity plugin that taps afterProcessAssets still runs after this pipeline, so its hashes are computed over the obfuscated bytes.

processAssets runs on every compilation, including rebuilds in watch mode, and every rebuild pays the engine's cost. Set build.autorun: false (or AFTERPACK_build_autorun=false) for the dev loop and keep it on for production builds.

Verify

A production webpack build sets the production build mode, so the Protection Map is off by default. Ask for it explicitly:

new AfterpackWebpackPlugin({ protectionMap: { enabled: true } });

The map's default couples to bundler-sourcemap presence, so set devtool: "source-map" if you want it to render your original source. Without one, the plugin warns that it found no bundler source map. The file is always written to the gitignored .afterpack/protectionMap.html, never into dist/. It contains your full source. Never commit it or serve it.

entitlements.jsmedium · complexity 8Open full size
entitlements.js, 54 lines, built at the medium preset with a directive around the signature check. Click any token to see what was applied to it.

Options

Every configuration key can be passed here as the options object, for example new AfterpackWebpackPlugin({ preset: "hard", complexity: 40 }). The names mirror @afterpack/vite and @afterpack/next exactly, so a config is portable between packages.

Directives

Directives are read from your source by the plugin.

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

Directives are block comments only: a // @afterpack … line comment is not scanned and surfaces as a diagnostic. The full grammar is on the Directives page.

Capture skips node_modules and non-JS/TS modules. Set directives.enabled: false to skip the scan entirely; hand-authored regions still work either way.

Edge cases

  • Vendored code. webpack co-bundles node_modules into your chunks, so there is no output-filename pattern that separates them. AfterPack works on the emitted files as a whole; paths.exclude is the file-level carve-out when you need one.
  • Fail-closed, with no override. A failed file or an empty result fails the build rather than shipping unobfuscated code.

Next