Compress JavaScript with Terser, the minifier most bundlers use. Local variables get short names and dead branches are dropped; the example below goes from 356 to 218 bytes. Terser loads from a CDN the first time you click Minify, and your code never leaves the tab.
// Debounce a function call
function debounce(fn, wait) {
let timeout;
return function (...args) {
clearTimeout(timeout);
timeout = setTimeout(() => fn.apply(this, args), wait);
};
}
const onResize = debounce(() => {
console.log("resized", window.innerWidth);
}, 200);
window.addEventListener("resize", onResize); function debounce(n,t){let e;return function(...o){clearTimeout(e),e=setTimeout(()=>n.apply(this,o),t)}}const onResize=debounce(()=>{console.log("resized",window.innerWidth)},200);window.addEventListener("resize",onResize); Variables and parameters inside a function get one-letter names. Most of the saving comes from this, and it is also why the output is hard to read. Untick Rename locals to skip it.
An if (false) branch, a statement after a return, a variable that is assigned and never read: Terser removes all of them.
Terser joins statements with commas, turns if/else into a ternary where that is shorter, and writes true as !0. The behavior stays the same.
Top-level names are not renamed, and neither are exports or properties on window, so other scripts and inline handlers still find them.
Use a build tool for anything bigger than one file. This page suits a snippet for a CMS, a tracking widget or a small embed you paste into someone else's page. In a project, run Terser in your bundler: you get source maps, so a production stack trace points at a real line, and results are cached between builds. This page also strips every comment, /*! license */ blocks included, so add a required header back by hand.
Minifying is not obfuscation. The logic is all still there, and any pretty-printer turns it back into readable code in a second. Do not put a secret in client-side code and expect the minifier to hide it.
# Terser directly
npm i -D terser
npx terser src/app.js -c -m -o dist/app.min.js
# With a source map, so production stack traces still mean something
npx terser src/app.js -c -m --source-map "url='app.min.js.map'" -o dist/app.min.js
# esbuild is much faster and nearly as small
npx esbuild src/app.js --minify --sourcemap --outfile=dist/app.min.js
# Check the real cost, which is the compressed size
gzip -c dist/app.min.js | wc -c const { minify } = require("terser");
const fs = require("node:fs");
const code = fs.readFileSync("src/app.js", "utf8");
const result = await minify(code, {
compress: { drop_console: true, passes: 2 },
mangle: { toplevel: false }, // keep top-level names for other scripts
format: { comments: /^!/ }, // keep /*! licence */ comments
sourceMap: { filename: "app.min.js", url: "app.min.js.map" },
});
fs.writeFileSync("dist/app.min.js", result.code);
fs.writeFileSync("dist/app.min.js.map", result.map); drop_console removes every console call. Use it for production builds, and expect to miss those logs when you debug one.
// vite.config.js
export default {
build: {
minify: "terser", // "esbuild" is the default and faster
sourcemap: true,
terserOptions: {
compress: { drop_console: true },
},
},
};
// Most projects should stop here. A bundler minifies, splits chunks,
// tree-shakes and writes source maps in one pass, and caches the result. Measure the gzipped size, not the minified size on disk. Minifying and gzip work on different things: the minifier removes characters, and gzip removes repetition. Say a 100 KB file minifies to 60 KB and then gzips to 18 KB, while the unminified file gzips to 22 KB. Measure both before deciding a build step is worth the complexity.
This tool follows the specifications below. Read them when you need the exact normative wording, for example to settle an edge case between two implementations.
Terser, the compressor most build tools use. It loads from a CDN the first time you click Minify, so the page stays light for anyone who does not need it.
Local ones, yes, which is where most of the saving comes from. Top-level names, exports and properties on window keep their names, so external callers still work. Untick Rename locals to keep every name.
Modern JavaScript: classes, optional chaining, nullish coalescing, private fields. It compresses without transpiling, so the output targets the same engines the input did. TypeScript and JSX need compiling first, and the input is read as a script, so top-level await fails to parse.
For a file or two, yes. For a real project, run Terser in your build so you get source maps and caching. This page is for the quick case: a snippet, an embed, a widget you are pasting into a CMS.
No. Terser is plain JavaScript and runs inside your tab. The library itself comes from a CDN, but your code never leaves the browser.
Stylesheets go through the CSS minifier, and data through the JSON minifier. To read minified code again, the JSON formatter handles data and your browser's devtools have a pretty-print button for scripts.