A bug report comes in from production. You open the stack trace and it points to line 1, column 48,213 of main.3f9a1c.js. Minified code is great for your users and miserable for you. This post covers both sides: making JavaScript small, and making it readable again.
Why minify JavaScript
JavaScript is expensive per byte. The browser has to download, parse, compile and run it. A classic <script> tag without async or defer even stops the page from loading further until all that is done.
Parse and compile time grows with the size of the script. So a smaller file saves time at every step, not just on the network.
According to the HTTP Archive, the median page ships roughly 500 KB of compressed JavaScript, and much more once it is decompressed. On mid-range phones, that is a noticeable part of load time.
What a minifier does
A minifier rewrites code to be smaller without changing what it does. Your build tool usually runs one for you: Terser, esbuild or SWC. They apply these steps:
Whitespace and comment removal
Spaces, tabs, newlines and comments (// and /* */) are removed. Strings and template literals are left exactly as they are. Most minifiers can keep license comments, such as those starting with /*!.
Name mangling
This is where most of the saving comes from. Local variables and parameters get the shortest names available, so the lovingly named userAccountBalance becomes a. The minifier tracks scope, so renaming never changes behaviour. Globals and property names such as document.getElementById are left alone by default, because other code may refer to them.
Dead code removal
Code that can never run, such as an if (false) block or statements after a return, is dropped. A related trick, tree-shaking, removes exported functions that nothing imports. That one is done by the bundler (webpack, Rollup, esbuild) before the minifier runs.
Shorter expressions
true becomes !0, undefined becomes void 0, and consecutive declarations are merged.
var a = 1;
var b = 2;
if (flag === true) return undefined;var a=1,b=2;if(flag===!0)return void 0;How much it saves
Approximate sizes for some well-known libraries:
| Library | Unminified | Minified | Minified and gzipped |
|---|---|---|---|
| React DOM 18 (development vs production build) | About 1,000 KB | About 130 KB | About 42 KB |
| Lodash (full) | About 540 KB | About 72 KB | About 25 KB |
| jQuery 3.7 | About 285 KB | About 87 KB | About 30 KB |
Typical application code shrinks by 40 to 60 percent before compression. Minification and compression (gzip or Brotli, applied by your server) stack. The minifier removes what the code doesn’t need, then compression squeezes what’s left.
In time terms, cutting 60 KB saves about 300 ms on a 1.6 Mbps connection and about 40 ms at 12 Mbps. Small per file, but it adds up across every script and every page view.
Beautifying JavaScript
Beautifying, or pretty-printing, puts the indentation and line breaks back. It won’t make the code good, but it will make it readable. It helps when you need to:
- Debug a problem that only shows up in the production bundle
- See what an analytics, ad or widget script is doing on your page
- Audit a third-party script for unexpected behaviour such as sending data elsewhere
- Learn how a feature works when no source is published
function d(a,b){var c=a.length,e=[];for(var f=0;f<c;f++){if(a[f]!==b){e.push(a[f])}}return e}var g=d([1,2,3,4,5],3);console.log(g);function d(a, b) {
var c = a.length,
e = [];
for (var f = 0; f < c; f++) {
if (a[f] !== b) {
e.push(a[f]);
}
}
return e;
}
var g = d([1, 2, 3, 4, 5], 3);
console.log(g);The structure is clear again, but the names are still d, a, b and so on. A beautifier restores layout, not the original names. For those you need a source map.
Using the online minifier and beautifier
- Open the JavaScript minifier and beautifier and paste your code.
- Choose Beautify (with a 2 or 4 space indent) or Minify, then run it.
- The output panel shows the size before and after, for example
4.2 KB → 2.9 KB (-31%). Copy the result.
It runs in your browser, so the code stays on your machine. It’s a quick, lightweight tool rather than a full compiler, so know what it does and doesn’t do:
- Minify removes comments and whitespace and leaves strings, template literals and regular expressions intact. It does not rename variables or remove dead code, so savings are smaller than with Terser or esbuild.
- Beautify starts a new line after each opening bracket and semicolon and indents by nesting depth. That is enough to read minified code, but for code you commit, use a formatter such as Prettier.
Source maps
Remember that stack trace at line 1, column 48,213? A source map is how you turn it back into checkout.ts, line 87. It’s a JSON file that maps each position in the minified file to a position in the original source. It lists the original file names and usually includes the original source code too.
- Your build tool (webpack, Vite, esbuild, Rollup) writes the map next to the minified file.
- The minified file ends with a comment pointing to it:
//# sourceMappingURL=app.js.map. - When DevTools is open, the browser fetches the map and shows the original files and names, even though it runs the minified code.
- Breakpoints you set in the original source are mapped to the right spot in the minified code.
Source maps in production
Because a map usually contains your full source, decide how to handle it in production:
- Do not deploy maps. Simplest and private, but debugging production means reading beautified code.
- Hidden source maps. Generate the maps without the
sourceMappingURLcomment and upload them to your error tracker (Sentry, Bugsnag, Datadog), which uses them to show readable stack traces. - Restricted maps. Deploy them but allow access only from your network or to signed-in developers.
Beautifying obfuscated code
Minified code is hard to read by accident. Obfuscated code is hard to read on purpose: it’s transformed so people can’t easily follow it. Common techniques:
- String encoding: strings are written as hex or Unicode escapes (
"\x48\x65\x6c\x6c\x6f") or split into arrays and joined at runtime. - Control flow flattening: the logic is turned into a state machine, a
switchinside awhileloop. - Dead code injection: unreachable branches are added to mislead readers.
- Self-defending code: the script checks whether it has been reformatted and stops working if so.
A beautifier will not undo these, but restoring the indentation is still a useful first step: it makes patterns such as the big state-machine switch visible. Undoing the rest needs a deobfuscator built for the specific obfuscator.
Minifiers compared
Terser
The successor to UglifyJS and still the most widely used minifier. Webpack uses it by default through terser-webpack-plugin. It has many options and compresses well, but being written in JavaScript it is slower than native tools.
esbuild
A bundler and minifier written in Go, often 10 to 100 times faster than JavaScript-based tools. Vite has used it as its default minifier. Its output is usually a little larger than Terser’s.
SWC
A compiler written in Rust that can replace both Babel and Terser. Next.js uses it for compiling and minifying. It is much faster than Terser with similar output size.
Google Closure Compiler
The most aggressive option. Its advanced mode renames properties, inlines functions and removes unused code across files, which gives the smallest output but requires code written for it, often with JSDoc type annotations. Outside Google it is used far less than the others.
Online tools
For a one-off job without a build setup, such as reading a minified file or checking how much whitespace a snippet carries, an online tool is quickest.
Good practice
- Always minify production builds. Every modern build tool does it out of the box.
- Generate source maps even if you do not serve them, and upload them to your error tracker.
- Measure. Compare sizes in the DevTools Network tab and check the effect with Lighthouse.
- Use ES modules (
importandexport) so the bundler can tree-shake unused code. - Split code by route so visitors only download what the page they are on needs.
- Review third-party scripts from time to time for size and unexpected behaviour.
