Documentation
¶
Overview ¶
Command minifydiff minifies a document conservatively and then proves it did no harm, by parsing the input and the output and diffing what a parser sees.
A minifier is a program whose bugs are invisible. It removes bytes nobody was looking at, the page still renders, and the one that mattered is discovered by a customer three releases later. So this one is two programs: a small set of edits, and a checker that has to agree the edits changed nothing. If the checker disagrees the input is written out unchanged, because a document that is 4% larger is not a bug and a document that means something else is.
The edits are two.
Runs of whitespace outside the elements where whitespace is significant become a single space. Never no space: whether the remaining one is visible depends on whether the surrounding elements are inline, which is a CSS question this program cannot answer.
Comments the document spelled as comments are removed. That qualification is the whole of it - an HTML parser reports four different pieces of syntax as a comment token, and two of them are not comments to whatever reads the document next:
<!--a--> a comment remove <!bogus> a bogus comment keep <?php echo 1; ?> a processing instruction keep <![CDATA[x]]> a CDATA section keep
The delimiters are gone by the time a handler sees the token, and the text does not say which it was: a comment containing "?php x ?" reads exactly like the processing instruction. What does say is the arithmetic on lolhtml.Comment.SourceLocation - the source range less the text is the delimiters, and 7 is "<!--" and "-->" - which needs no copy of the input and so works in a stream. Conditional comments and licence banners are kept on top of that, by their text.
The checker is the interesting half. It parses a document into two projections and compares them.
The first is structure and text: start tags with their attributes sorted, end tags, the doctype, and the text between them with whitespace normalised outside the significant elements and left exactly as it is inside them. Comments are not in this projection at all, and text runs are joined across them - removing a comment merges the text on both sides of it, and that is not a difference.
The second is the comments, in order, each with the length of the delimiters the document used. The output's list has to be a subsequence of the input's, and every comment missing from the output has to have had a delimiter length of 7. That allowance is deliberately narrower than the rule the minifier applies: the checker does not re-check "is this a licence banner", because keeping a banner is a policy and turning a PHP block into nothing is a change of meaning. A checker that shares a rule with the thing it is checking is checking that it is consistent, which is not the same as being right.
The checker's allowance is deliberately wider than what the minifier does. It would accept whitespace between two tags being deleted outright, because a parser keeps no text node for it; the minifier leaves one space there anyway, because whether that space is visible depends on CSS. A checker that permits exactly what its minifier does has stopped being a second opinion.
What that combination catches, measured in the tests rather than argued: a whitespace edit inside a <pre> or a <script>, a comment token that was not a comment, a dropped tag, a mangled attribute value, a text node that lost a character. What it does not catch is anything a parser does not see, which is the honest limit: two documents with the same projections can still differ in what a browser lays out if CSS is involved, and that is why the whitespace rule is the conservative one.
The cost is that this is not a streaming program. The checker needs the whole output before it can approve any of it, so the document is buffered and read three times: once to minify, once to project the input, once to project the output. That is a build-time or a test-time tool, not something to put in front of a request - and it is what a proxy that wants to minify should run over its corpus first.