Performance

How much bigger does obfuscation make your bundle? We measured 42 libraries

“Obfuscation bloats your bundle” is one of those claims everybody repeats and nobody quantifies. So we quantified it. Below is the measurement across 42 real production libraries — jQuery, React DOM, three.js, Babel, D3, Chart.js and the rest — run through our own protection pipeline, with raw and gzipped sizes for every one.

The headline: raw size roughly doubles, gzipped size grows about a quarter. Those two numbers are far apart, and the gap is the whole story.

Method, so you can discount it appropriately

The corpus is the same 64-library set our engine uses as a release gate — real, shipped, mostly-minified library code rather than snippets. We measured the 42 files over 20 KB (smaller files are dominated by fixed overhead and tell you nothing useful).

  • Settings: identifier renaming including globals, string extraction into a lookup table, string encoding. This is a deliberately aggressive configuration — it exercises as much of the pipeline as possible.
  • Not included: self-compression, VM bytecode, or control-flow flattening. Those cost more. Numbers here are a floor for heavy modes, not a ceiling.
  • Gzip is level 9. Brotli will differ somewhat; the direction is the same.
  • Every output was checked to parse and to be free of dangling internals. These are sizes for working output, not for garbage.

The result

Split by whether the input was already minified, because the two groups behave nothing alike.

Input n Raw, median Gzip, median Gzip, range
Already minified301.97×1.29×0.90× – 1.38×
Not minified120.95×0.99×0.49× – 1.38×

Protection includes minification, so unminified input comes out roughly the size it went in: the shortening and the obfuscation overhead cancel. If you are protecting source that has never been through a minifier, your bundle does not grow at all, and sometimes it shrinks noticeably — acorn went from 54 KB gzipped to 38 KB.

If you are protecting an already-minified bundle, which is the common case, the honest number is 1.25× to 1.30× over the wire. Concretely:

Library Raw before → after Gzip before → after Wire cost
jquery.min.js85 → 173 KB30 → 38 KB+8 KB
react-dom.production.min.js129 → 260 KB42 → 53 KB+11 KB
axios.min.js41 → 80 KB15 → 19 KB+4 KB
three.min.js589 → 1379 KB146 → 190 KB+44 KB
echarts.min.js1005 → 1868 KB326 → 410 KB+84 KB
zod.min.js165 → 124 KB23 → 21 KB−2 KB

Why raw doubles but gzip barely moves

Because gzip is very good at exactly the thing obfuscation produces.

The two transforms that add the most bytes are identifier renaming (e becomes _0x1F4A2, seven characters where there was one) and string extraction (every literal moves into one array and each use becomes an indexed read). Both replace varied text with highly repetitive text. A file full of _0x1F4A2, _0x1F4B7, _0x1F4C1 compresses far better than a file full of genuinely different short names, and a string table is the most compressible structure in the output — it is one long run of similar-looking entries.

So the raw multiplier tells you about disk and parse work; the gzip multiplier tells you about the network. Only one of those is what your users wait for.

The outliers, in both directions

The spread matters more than the median if you are budgeting.

  • Best case, 0.90×: zod, whose bundle is dominated by long unique strings that the string table deduplicates.
  • Worst case, 1.38×: bootstrap.bundle.min.js and effector, both small relative to the fixed cost of the string table and both already very tightly minified — there was nothing left to squeeze.
  • Largest raw multiplier, 2.51×: effector again, at 28 KB → 70 KB. Over the wire that is 12 KB → 16 KB. The scary-looking number and the number that matters are four kilobytes apart.

There is a pattern worth internalising: the raw multiplier is worst on small, dense files, and the gzip multiplier follows it only weakly. If you are protecting one small utility module, expect the raw file to look alarming and the transfer cost to be trivial.

What to actually do about it

  1. Measure gzipped, not raw. If your performance budget is written in raw bytes it is measuring the wrong thing for every asset, not just protected ones.
  2. Protect before compression, and let the server compress. Do not ship a pre-gzipped protected file and then let a CDN re-compress it.
  3. Protect selectively. Your licence check, pricing logic, and proprietary algorithms are worth the bytes. Your date formatter is not. Most build integrations let you scope protection to specific entry points, and a 30% increase on 15% of your code is a 4% increase.
  4. Re-measure if you enable heavier modes. Control-flow flattening and VM bytecode are a different order of cost. The numbers above do not cover them, and we would rather say so than let you find out in production.

The honest caveat

This is one engine, one configuration, one corpus, one compressor setting. Another tool with different defaults will land somewhere else, and the same tool with control-flow flattening turned on certainly will. What we would encourage is not to take our number but to run the same measurement on your bundle, which takes about five minutes: protect it, gzip both versions, compare. Any vendor unwilling to have that done to their output is telling you something.

And keep the size question separate from the strength question. A bigger output is not a better-protected one, and obfuscation raises the cost of reverse engineering rather than preventing it. Size is a budget line; protection strength is a different conversation.

Related reading