How Terser shrinks code
Whitespace and comments are the obvious savings, but the bigger gains come from understanding the code. Terser parses the program, runs two compression passes and then mangles names:
- Mangling renames function parameters and local variables to one or two letters:
formatPrice(amount, currency)becomesformatPrice(e,r). Private class fields are shortened too, so#itemscan become#s. - Dead code is removed. With
const DEBUG = false, anif (DEBUG) { ... }block disappears entirely, and so dodebuggerstatements.console.logcalls are kept, since they may be intentional. - Constants used once or known at compile time are inlined. In a module,
(1 + TAX_RATE)withTAX_RATE = 0.09becomes1.09and the constant declaration vanishes. - Shorter equivalents replace verbose syntax:
falsebecomes!1, consecutiveconstdeclarations merge, and statements are joined with commas where that is valid. - All comments are removed, including
/*! ... */licence banners. Re-add a required banner in your build.
The savings are large: the widget script in the first example goes from 430 to 240 bytes, and the module in the third example from 210 to 64.
Scripts and ES modules are treated differently
The minifier checks whether the code has a top-level import or export statement. If it does, the input is minified as an ES module, which has its own scope: top-level names are private to the file, so they can be mangled or removed when unused, and exported bindings keep their public names. An exported constant that is used internally may come out as const e=...;export{e as total}, which still exports total to importers.
Without import or export, the code is treated as a classic script. Its top-level functions and variables are globals that other scripts or inline onclick handlers might call, so their names are left alone while everything inside them is still mangled. That is why formatPrice keeps its name in the first example. If you know a script’s globals are private, wrap it in a function or convert it to a module to get the extra savings.
Using it, and what it cannot take
Paste the code and the minified result appears as you type; Ctrl/Cmd+Shift+M flips to the Prettier-formatted view, useful for reading a vendor bundle, and Ctrl/Cmd+Shift+C copies the output. The formatting options on the page (Semicolons, Single quotes, Trailing commas and the rest) only shape the beautified view; Terser decides the minified spelling itself. Code is minified in the browser, so proprietary source does not leave your machine.
Before Terser runs, Prettier parses the input, so a syntax error gives the same message and position you would see when formatting. JSX cannot be minified: compile it with Babel, esbuild or your framework first. TypeScript type annotations are not JavaScript either and fail to parse here; strip them with the TypeScript compiler before minifying.
Is minified output safe to ship?
Terser’s transforms preserve behaviour for code that does not depend on its own source text. The known exceptions are code that reads Function.prototype.toString(), relies on function or class .name values for logic, or uses eval to reach local variables by name. Keep a source map or the original file for debugging production errors, since stack traces will show mangled names.
Examples
Classic script with a licence banner and a debug flag
The debug branch and both comments are removed and the parameters are mangled, but the global formatPrice keeps its name.
/*! orders-widget v1.4.0 | MIT */
// Format a price for display
function formatPrice(amount, currency = 'SGD') {
const formatter = new Intl.NumberFormat('en-SG', { style: 'currency', currency });
return formatter.format(amount);
}
const DEBUG = false;
if (DEBUG) {
console.log('debug mode');
}
document.querySelectorAll('[data-price]').forEach(function (el) {
el.textContent = formatPrice(Number(el.dataset.price));
});
function formatPrice(e,r="SGD"){return new Intl.NumberFormat("en-SG",{style:"currency",currency:r}).format(e)}const DEBUG=!1;document.querySelectorAll("[data-price]").forEach(function(e){e.textContent=formatPrice(Number(e.dataset.price))});ES module with an import
Because the file is a module, the imported fetchOrders is renamed locally while the exported loadDashboard keeps its public name.
import { fetchOrders } from './api.js';
export async function loadDashboard(userId) {
const orders = await fetchOrders(userId);
const pending = orders.filter((order) => order.status === 'pending');
return { total: orders.length, pending: pending.length };
}
import{fetchOrders as t}from"./api.js";export async function loadDashboard(n){const a=await t(n),o=a.filter(t=>"pending"===t.status);return{total:a.length,pending:o.length}}Module constants folded into the code
Both constants are inlined and removed, leaving a single return expression that multiplies by 1.09.
const TAX_RATE = 0.09;
const FREE_SHIPPING_FROM = 50;
export function checkoutTotal(subtotal) {
const shipping = subtotal >= FREE_SHIPPING_FROM ? 0 : 4.99;
return (subtotal + shipping) * (1 + TAX_RATE);
}
export function checkoutTotal(t){return 1.09*(t+(t>=50?0:4.99))}Common errors and how to fix them
| Error | Cause | Fix |
|---|---|---|
Minifying JSX is not supported — compile JSX to JavaScript first | The code contains JSX tags such as <div>, which Terser does not parse. | Compile with Babel, esbuild, SWC or your framework build, then minify the generated JavaScript. |
Unexpected token, expected ","Explained | A bracket or parenthesis is not closed, or a comma is missing in an argument list or object literal. | Go to the reported line and column and check the bracket just before it. |
Unexpected tokenExplained | The parser met something that cannot appear at that point, such as an assignment with no value or a TypeScript type annotation. | Complete the expression, or compile TypeScript to JavaScript before minifying. |
Frequently asked questions
Which minifier does this page use?
Terser, with two compression passes and name mangling enabled. It is the same minifier webpack, Rollup and Vite use by default or as an option.
Why are my top-level function names not shortened?
Without import or export the code is a classic script, and its top-level names are globals other code may call. Make it an ES module to let those names be mangled too.
Does it remove console.log?
No. Calls to console are kept because they may be intended output. debugger statements are removed.
Can I un-minify JavaScript?
Switch to Beautify to re-indent it with Prettier. Formatting restores structure, but mangled names and removed comments cannot be recovered without the original source or a source map.
Can it minify TypeScript or JSX?
No. Both need compiling first, because type annotations and JSX tags are not plain JavaScript.