Parcel

Obfuscate a Parcel build

Parcel needs no config, and neither does this: let parcel build write dist, then protect the JavaScript in it with one Node script or one CLI command. File names, hashes and index.html stay exactly as Parcel made them. The free options run on your own machine with no account.

Tested 2026-10-29

Real build, same result

Parcel 2.16 and javascriptobfuscator-com 0.5.0, no account. The protected bundle printed exactly what Parcel’s own bundle printed, and index.html was byte-for-byte unchanged.

WhenAfter parcel build, on dist
Outputdist-protected, same file names
Source mapsNot copied
Setup

A protect step after parcel build

The helper ships inside javascriptobfuscator-com as javascriptobfuscator-com/parcel. It is not a .parcelrc plugin; it works on the finished dist folder, so it cannot interfere with Parcel’s cache or dev server.

Terminal
npm install --save-dev parcel javascriptobfuscator-com
protect.js
const protectParcelBuild = require("javascriptobfuscator-com/parcel");

protectParcelBuild({
  input: "dist",
  output: "dist-protected",
  preset: "free"  // standard, balanced or maximum with a plan
}).catch((error) => {
  console.error(error.message);
  process.exit(1);
});
package.json
"scripts": {
  "build": "parcel build src/index.html",
  "release": "npm run build && node protect.js"
}

What npm run release does

  1. Parcel builds dist as usual: hashed bundles, index.html, source maps.
  2. The helper protects every .js file in dist in one batch and writes it to dist-protected under the same name.
  3. It copies everything else — HTML, CSS, images — unchanged, so index.html still loads the right hashed file.
  4. It leaves out the .map files and skips anything under a vendor folder.
  5. It writes dist-protected/jso-manifest.json, a record of what was protected, and fails the run if a file grows more than eight times.

Deploy dist-protected, not dist.

Important

Set the preset yourself

The Parcel helper defaults to the balanced preset, which needs a paid plan. Balanced options run only on the hosted engine, so with no API key the run goes there and stops with a plan error, instead of falling back to the free local engine.

So pass preset: "free" until you have a plan; afterwards set preset to standard, balanced or maximum and supply JSO_API_KEY and JSO_API_PASSWORD as environment variables.

No Script At All

Or one CLI command

The same protection from the command line, with no protect.js:

parcel build src/index.html
npx javascriptobfuscator-com \
  --input dist --output dist-protected

With no credentials the CLI uses the free preset on its own. It copies index.html and the other files across and leaves the .map files behind, as the helper does. More in the CLI guide.

Real Output

Parcel’s bundle, before and after

The sample is a pricing table and a licence-key checksum in three modules. Parcel’s production bundle was 542 bytes; the protected one 1,231 bytes, of which 546 are the free-version comment (left out below).

dist/parcel-src.acdd458a.js
let e,t,n=[{name:"starter",seats:5,monthly:29},{name:"team",seats:25,monthly:49},{name:"business",seats:100,monthly:99}];function a(e){let t=0;for(let n of e+"acme-license-v2")t=31*t+n.charCodeAt(0)>>>0;...
dist-protected/parcel-src.acdd458a.js
let e,t,n=[{name:"\x73\x74\x61\x72\x74\x65\x72",seats:5,monthly:29},{name:"\x74\x65\x61\x6d",seats:25,monthly:49},{name:"\x62\x75\x73\x69\x6e\x65\x73\x73",seats:100,monthly:99}];function a(b){let a=0;for(let c of b+"\x61\x63\x6d\x65\x2d\x6c\x69\x63\x65\x6e\x73\x65\x2d\x76\x32")a=31*a+c.charCodeAt(0)>>>0;...

Both printed {"quote":{"tier":"team","total":490,"perSeat":40.83},"valid":true,"forged":false}. The file kept Parcel’s hashed name, so the unchanged index.html loads it. With the free engine, quoted strings are encoded and local names replaced; property names, template-literal strings and the code’s structure stay as they were — the paid presets go further, transform by transform.

Options

Helper defaults, and how to change them

OptionDefault in the Parcel helperChange it when
input / outputdist → dist-protectedYou pass --dist-dir to Parcel.
presetbalanced (needs a plan)Always set it: free without a plan.
exclude**/*.map, **/vendor/**Other files should stay unprotected. Your list replaces the default, vendor rule included; maps stay out either way.
maxGrowthRatio8You want a tighter size budget.
manifestdist-protected/jso-manifest.jsonRelease evidence should go elsewhere, or null for none.
reservedNamesnoneCode is looked up by name, such as a global callback.
Frequently Asked

Parcel obfuscation questions

Is this a Parcel plugin in .parcelrc?

No. It is a post-build step: parcel build writes dist as usual, then a short Node script (or the CLI) protects the JavaScript in dist and writes the result to dist-protected. Nothing changes in .parcelrc.

Will index.html still load the protected bundle?

Yes. Protection keeps every file name, including Parcel’s content hashes, and copies HTML, CSS and images across unchanged, so the script tags in index.html still point at the right files.

Why do the examples set preset: "free"?

The Parcel helper defaults to the balanced preset, which needs a paid plan. Without an API key, pass preset: "free" so the run uses the free local engine; with a plan, use standard, balanced or maximum.

Do I need an account?

No, for the free preset: local variables are renamed and string literals encoded on your own machine, nothing is uploaded, and there is no rate limit. The output starts with a short comment naming the product.

Other Build Tools

Not using Parcel?

The same package protects Rollup, esbuild, Rspack and Gulp builds, or any output folder from the command line.