Minify or beautify JavaScript, TypeScript, CSS, HTML, JSON, XML and SQL with a real lexer per language, so a comment inside a string is not deleted and a line break the code depends on is kept, with the byte saving measured
Initializing in your browser…
Format, validate and minify JSON, JSON with comments and JSON5, without changing a single value: every number is written back as the document spelled it. Errors carry a line, a column and the offending line. JSONPath queries, tree view, schema checks and diff
Format and validate XML without changing the document: CDATA, xml:space="preserve" and mixed content all survive, with errors carrying a line, a caret and a next step, plus XPath queries, a tree view and JSON conversion that names what JSON cannot carry
Format YAML without rewriting it: comments, anchors, quoting, block scalars, multi-document streams and the exact spelling of every number all survive, with YAML 1.1 and 1.2 both offered because the same file means different things under each
You have a small standalone JS snippet for a landing page and want it minified without a build step. It holds a URL in a string, which is where a regular-expression minifier goes wrong.
Source
const u = "https://example.com/a"; /* real comment */
function add (a, b) {
return a + +b;
}Minified
const u="https://example.com/a";function add(a,b){return a+ +b}
(the shipped version gave: const u = "https:)The two slashes of the URL are inside a string, so they do not start a comment, and the space in `a + +b` is what keeps it from becoming `a++b`. Both are decided by a lexer rather than by a pattern, which is why the corpus of fifty cases now keeps its meaning in all fifty where the previous version managed twenty-seven. The saving is shown in bytes from the UTF-8 encoding, matching the file you download. The tool also beautifies the other way, and a minify, beautify, minify round trip gives the identical result.
Minify or beautify code in seven languages. The point of a minifier is that the output does the same thing as the input, so that is what this one is scored on: every case in its test corpus is run rather than measured. `node` runs the JavaScript, `sqlite3` executes the SQL, `xmllint` canonicalises the XML, Python's `json` reads the JSON, and Chrome's own CSS and HTML parsers read the other two. Measured on 2026-09-03, the version this replaced kept the meaning of 27 of 50 cases; this one keeps all 50. Everything runs over a token stream rather than over the raw text, which is the whole difference. A regular expression cannot tell a comment from the same characters inside a string, and cannot tell a division from a regular expression literal, and cannot tell whitespace that is free from whitespace a program depends on.
Three faults are worth naming, because they are what a text-substitution minifier always has and they are all silent.
**Comments.** Stripping them with a pattern destroys any string that contains the same characters. Measured: `const u = "https://example.com/a";` followed by a real comment came out as `const u = "https:` and the rest of the file was gone, because the two slashes of the URL started a line comment. The mirror image is worse because it still runs: a string whose contents were block-comment syntax came out as the empty string. Here a lexer finds the comments, and a licence comment (one that opens with a slash, a star and a bang, or holds `@license`) is kept and reported.
**Whitespace.** Replacing every run of it with a space reaches inside every string and every template literal, and it joins lines that automatic semicolon insertion was holding apart. A `return` on its own line followed by `42` came back as `return 42`, which returns a different value, and two `let` statements on two lines were joined into something that does not parse at all. This minifier removes the whitespace between two tokens only when the two cannot merge into one token, and keeps a line break wherever removing it would change what the program means, saying how many it kept and why. In CSS it knows a space in a selector is a descendant combinator, so `a :hover` is not turned into `a:hover`, and that `calc()` requires the spaces around its plus and minus. In HTML it knows `pre`, `textarea`, `script` and `style` hold content rather than layout, and that the space between two inline elements separates two words. In XML it does only what `xmllint --noblanks` does: remove a text node that is whitespace and nothing else, honouring `xml:space="preserve"`.
**Operators.** Deleting the whitespace around every operator turns `a + +b` into `a++b` and `a - -b` into `a--b`, both of which are different programs. Two tokens are only joined here when re-scanning the joined text gives the same two tokens back.
The JSON path goes through the same document reader the JSON Formatter uses, so a 20 digit identifier is not silently rounded and `19.90` does not become `19.9`. The SQL path reads the dialect you choose, because whether a backslash escapes inside a string literal is true of MySQL and of no other dialect, and what quotes an identifier differs across all five.
What this does NOT do is rename variables or remove unreachable code. Doing either safely needs scope analysis, and a build-time minifier such as Terser or esbuild does it properly. The shipped version offered a "shorten variables" switch that no minifier in the file ever read, so the Maximum level's headline feature did nothing; the switch is gone rather than made to lie more convincingly.
Minify CSS and JavaScript assets before deployment to reduce file sizes, with the confidence that the output was checked by running it rather than by looking at it.
Beautify a compressed file into something you can follow while debugging. The formatter works on the token stream, so a brace inside a string or a regular expression does not become a line break.
Minify a snippet before embedding it inline in HTML to keep page size down without configuring a bundler.
It must not, and here that is measured rather than assumed. Every case in the test corpus is run before and after: node executes the JavaScript, sqlite3 executes the SQL, xmllint canonicalises the XML, Python reads the JSON and the browser parses the CSS and HTML. All 50 keep their meaning. The version this replaced kept 27.
Because removing them would change what the program does. JavaScript inserts semicolons at line breaks, so a statement that ends without one is relying on the line break to end it. The tool keeps exactly those and tells you how many, which is a few bytes; gzip makes a newline almost free anyway.
No, and it never did, although a switch used to say it might. Renaming safely needs scope analysis, which belongs in a build-time tool like Terser or esbuild. What this does is remove the characters that carry no meaning, which is most of the saving on ordinary source.
You can beautify it back into something readable, and a minify, beautify, minify round trip gives the identical result, which is asserted in the tests. What cannot come back are the comments and the original names.
On ordinary commented source, 25 to 50 percent. The figure shown is the real one: both sizes are the byte length of the UTF-8 encoding, and the driver checks the number against the byte length of the file you actually download.
Because the answer to "where does this string literal end" is different per vendor. MySQL treats a backslash inside a string as an escape and no other dialect does; the identifier quote is a backtick in MySQL, a double quote in PostgreSQL, SQLite and Oracle, and a square bracket in SQL Server; PostgreSQL nests block comments and has dollar quoting. Choosing the wrong one can make a comment stripper cut a string in half.
Your text is processed locally in the browser. Nothing you paste or open is transmitted or logged.