What minification removes and what it cannot touch
Minifying JavaScript means deleting the bytes that only exist for human readers: comments, indentation, line breaks and the spaces around operators and punctuation. The result is semantically identical code in fewer characters. This tool performs exactly that class of transformation. It does not rename variables, inline functions, fold constants, drop unreachable branches or tree-shake unused exports, all of which are the province of a full compiler such as terser, esbuild or SWC.
The practical consequence is a modest size win. Whitespace removal typically takes twenty to forty percent off a readable source file, while identifier mangling and dead-code elimination in a real bundler often take considerably more. If you are shipping a production bundle, use a build tool. If you need to shrink one file, paste a snippet into an inline script tag, or see where the bytes in a function actually go, a whitespace minifier is the right size of tool.
It is also worth noting that gzip and brotli already compress repeated whitespace very effectively, so the difference between minified and unminified code after transport compression is smaller than the raw character count suggests. The uncompressed size still matters for parse time, which is why minification remains worthwhile.
Why the minifier tokenizes instead of using regular expressions
The naive approach to stripping comments is to delete everything from // to the end of the line, and everything between /* and */. This corrupts real code the first time those characters appear inside a string, a template literal or a regular expression. The URL in const u = "https://example.com" contains //, and deleting from there to the end of the line destroys the statement. A regex literal like /\/\*/ contains a comment opener.
This tool therefore runs a proper tokenizer first. It walks the source character by character and classifies each run as a string, a template literal, a regular expression, a comment, a whitespace run, or plain code. Strings track backslash escapes so an escaped quote does not end them early. Template literals track nesting depth so a ${ } expression containing a backtick or a brace is handled correctly. Only whitespace and comment tokens are ever modified; everything else is copied through byte for byte.
Distinguishing a regex literal from a division sign is the classic hard case, because both are written with a forward slash. The rule used here is contextual: a slash after something that ends a value, such as an identifier, a number, a closing parenthesis, bracket or brace, is division, and a slash anywhere else starts a regex. Keywords are the exception that has to be listed explicitly, since return /x/.test(s) is a regex while a / b is division.
Automatic semicolon insertion and why some newlines survive
JavaScript lets you omit statement-terminating semicolons, and the parser inserts them for you at certain line breaks. That rule, automatic semicolon insertion, makes a newline semantically significant. Blindly collapsing every line break into nothing will join two statements into one and change the meaning of the program. The classic failure is a return on its own line followed by the value on the next, but the same hazard applies to any two adjacent statements that both happen to lack semicolons.
This minifier therefore removes a newline only where it can prove the break cannot be ending a statement. A line break is dropped when the last character before it cannot end a statement, which covers the set { ( [ , ; : ? = + - * / % & | ^ < > ! ~ and the dot, or when the first character after it cannot begin one, which covers ) ] } , ; : . = * % & | ^ < > and ?. Everywhere else the newline is preserved, which is why minified output from this tool is not always a single line.
That is a deliberate trade. A production-grade compiler parses to an abstract syntax tree and reinserts semicolons where needed, which lets it flatten everything onto one line safely. A token-level tool has to be conservative, so it keeps a handful of line breaks in exchange for never silently changing behaviour.
A worked example and the reverse direction
Given the sample input function greet(name) { if (!name) { return "Hello, World!"; } const msg = "Hello, " + name + "!"; console.log(msg); return msg; } the minifier removes both the line comment and the block comment, collapses the indentation, and keeps exactly one space where two identifiers would otherwise merge, as in const msg. The string literals are untouched, including the spaces inside "Hello, ", because they are part of the program's data rather than its formatting.
The beautify direction runs the minifier first and then re-expands the result, so it normalises formatting rather than preserving your original layout. It breaks a line after each top-level semicolon and after each opening brace, and indents by two spaces per nesting level. Semicolons inside parentheses are left alone, which is what keeps for (let i = 0; i < n; i++) on a single line rather than splitting its three clauses into separate statements.
Because beautifying starts from minified code, comments are already gone by the time the expansion runs. Use beautify to make unfamiliar minified code readable, not to reformat code you want to keep working in; for that, a formatter such as Prettier preserves comments and applies far richer layout rules.