Notes › Tech Notes › Untick Keep Comments and format-js Compresses a 24 Character URL Down to 13 Broken Characters

Untick Keep Comments and format-js Compresses a 24 Character URL Down to 13 Broken Characters

Untick Keep Comments on this JS beautifier and minifier page and comments are deleted before strings are protected: a 24 character URL comes back as 13 characters that fail new Function. Ticked, the string survives, but a line comment swallows the next statement and beautifying twice is never idempotent.

One URL Goes In and 13 Characters Come Out

var a = "https://x.com"; is 24 characters of ordinary JavaScript. Switch that page to minify and what comes back is var a="https:, 13 characters long, and new Function refuses it with SyntaxError: Invalid or unexpected token. Two cleanup passes simply run in the wrong order.

The order is inverted. Line 249 reads if (!keepComment) and decides whether comments are removed at all, and the removal itself runs on lines 250-251: paired block comments first, then everything from // to the end of the line. The protection that lifts strings out and swaps them for placeholders waits until line 258. Nothing guards the string yet, so the two slashes inside the quotes are already read as a line comment, and the rest of the address leaves together with its closing quote and its semicolon.

None of this happens while Keep Comments is ticked, and it is ticked by default. In that state the code takes the empty else branch on lines 252-254, so the same input only shrinks to 22 characters and the closing quote survives. The checkbox is never disabled in minify mode either: line 67 only sets the opacity of the indent row to 0.5, so the control looks greyed out while it still picks the branch. Comparing the two states on the JS beautify and minify page means treating the checkbox as a second variable.

Three Rewrites and Their Character Bills

InputStateOutputVerdict
var a = 1; // note newline var b = 2; (29 characters / 2 lines)Ticked, line 263 collapses whitespacevar a=1;//note var b=2; (23 characters / 1 line)Legal, and the second statement landed inside the comment
var s = "/*not a comment*/"; (28 characters)Unticked, line 250var s=""; (9 characters)Legal, and the string was emptied
var z = a - -b; (15 characters)Line 265 merges the operatorsvar z=a--b; (11 characters)Unexpected identifier 'b'

Only one of those rows throws an error. The other two parse cleanly, and that is the worse outcome, because the first loses a live statement into a comment while the second simply empties a string.

The Stat Bar Books Lost Characters as Savings

An array holding three URLs measures 70 characters over two lines. Untick the box and 17 characters remain, while the stat bar prints a green -75.7%: what was subtracted is the second and the third element vanishing whole. The bar is written by updateStat, which starts on line 300, and line 292 hands it input.length and result.length. Those are UTF-16 character counts, so the figure on screen has nothing to do with the bytes you would ship.

One URL under two checkbox statesInput24Ticked22 validUnticked13 brokensyntax error

Figure 1: the output length of the same 24 character input under both checkbox states.

The gap between the two readings is measured. A sample carrying a Chinese comment reports 57.9% on the page while its UTF-8 bytes say 71.4%, and feeding the script of this page back into itself gives 28.8% counted in characters against 37.6% counted in bytes. Identical length prints +0%, and a lone line comment with the box unticked prints -100.0% above an empty output field.

What Line 265 Merges and What It Leaves Alone

The character class on line 265 is a single-character set holding + - * / % = & | ! ? < >, and it leaves out ., ^ and ~. That omission decides the fate of a unary minus: the sign is pulled into the minus in front of it and becomes a postfix decrement, so the expression keeps its shape and loses its intent. Symbols outside the class keep the spaces on both sides, which is why var x = ~ y; var z = a ^ b; only falls from 27 characters to 22, and the dot behaves the same way, since var x = a . b; goes from 14 characters to 12 and still reads correctly. Nor is every symbol fatal: var f = (a, b) => a + b; compresses from 24 characters to 17 and stays legal, because the pass eats one character at a time and cannot pull a two-character arrow apart.

Placeholder Recovery Does Not Check Provenance

Placeholders are created inside the string protection on line 258 and recovered on line 267 by ___STR_(\d+)___, but the callback never asks whether the matched text came from the original source. With no string in the input the holding array is empty and the substitution writes a literal straight back: var p = ___STR_0___; shrinks from 20 characters to the 16-character var p=undefined;. With one real string ahead of it, var s = "abc"; var p = ___STR_0___; compresses 35 characters into 24, and the placeholder picks up the wrong owner.

The Lexer Has No Branch for Regular Expression Literals

Beautifying starts in tokenize, which begins on line 161, and that scanner has no branch for a regular expression literal. var re = /a+/g; splits into eight tokens, the slash belongs to the operator character set, and line 225 greedily merges + and / into one operator. The line grows from 26 characters to 30 and comes back as var re = / a +/ g;. Nothing warns you: the result still parses, and the pattern is no longer the one you wrote.

Line Breaks and Indents Come From Line 79

beautifyJS, which starts on line 79, reacts to only a handful of tokens when it breaks a line: { and [ push the depth up, } and ] pull it down, and a semicolon or a comma each adds a break of its own. Round parentheses change neither, so f(a,b,c); splits 9 characters over three lines with the indent still at zero. The comma test on line 143 does not exclude ), so for(var i=0;i<10;i++){sum+=i;} spreads 30 characters into 43 across five lines, and the for header takes three of them. The else written for the closing square bracket is an empty block (lines 108-110), which is how var a=[1,2,3]; goes from 14 characters to 25 over five lines: all three ] sit flush left while the closing } is indented. Case labels have no branch either, so they share a level with the function body.

Default sample beautified twiceSource112Pass 1151 chars11 linesPass 2134 chars6 linesshrinks on rerun

Figure 2: characters and lines after beautifying the default sample a second time.

Beautifying Is Not Idempotent and Minifying Is

Of 10 samples, seven produce a different result when beautified twice. The default one runs 112 characters, then 151 characters over 11 lines, then 134 characters over six lines, so the second pass eats 17 characters and removes five lines. The script block of this page says it better: 8,629 characters over 287 lines come back as 8,540 characters over 277 lines together with SyntaxError: Invalid regular expression: missing /, and one more pass returns 8,819 characters over 335 lines. Minifying runs the other way, with three samples repeating identically, 35 to 13 to 13, and once the two directions are chained beautify(minify(x)) == x holds for none of the groups. Reformatting compressed output is not a repair step; it is a second rewrite of text that is already damaged.

Square-Order Indents and a catch That Never Fires

Indentation copies spaces per depth level, so the cost grows quadratically: 200 levels of braces, 402 characters of input, expand into 80,802 characters in 3,294 ms. At 500 levels the same rule takes 1,002 characters to 502,002 characters in 120,055 ms, and the longest line starts with 1,000 spaces. The input field imposes no length limit, so what a browser shows is a tab that appears to have hung. Minifying ignores the indent control completely: one 42-character sample outputs 42 characters under spacing of two, of four and under Tab alike, because that mode never reads the setting. The error exit is empty too. The catch on lines 293-295 and its "Format error:" text fired zero times across 72 malformed inputs, because the only natural throw sits on line 75 inside unit.repeat(level) and line 105 clamps that argument with Math.max(0, indent - 1). The branch is in the file; those 72 inputs cannot assemble the string that reaches it.

Frequently Asked Questions

Q: Does unticking Keep Comments only delete comments?

No. Comment removal runs before string protection, so the // and the /* written inside a quoted string count as comment starts too. That ordering mistake is why 24 characters of input end as 13 characters of output.

Q: Does a green negative percentage mean that much size was saved?

No. The bar counts characters, not bytes, so the run that saved 17 characters saved nothing but disappeared elements, and the page reading of 57.9% against 71.4% in bytes was already apart.

Q: If minifying breaks the code, can beautifying put it back?

It cannot. The cross checks recovered none of the groups, and the URL array with the box unticked runs 35 to 13 to 15 characters, still broken code rather than a shorter spelling of the same program. How to read compressed output and which version to keep for review belongs to the earlier JS beautify and minify article; this one measures only what ships on this page.

📎 Related articles

I Tested 16 Switch Combinations of an HTML Tag Stripper: It Reports 19 Tags but Deletes 148 Characters 2026-10-30 Linked Images Vanish When SVG Turns to PNG: A 594 Byte File Shows 3 Pictures in Preview and 1 in the Export 2026-10-30 Handing a Base64 string to someone else: 4 edits tried, 3 of them throw an error 2026-10-30 App Store Screenshots Export at 1242×2688: 8 Canvas Presets, Noise at 24.83 Times the Size, and a Phone Frame Cropped 134.93 px by Default 2026-10-30 16 Keys In, 4 Columns Out: How json-to-excel Builds CSV Headers 2026-10-30