Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Server-rendered HTML minification #1432

Open
tigt opened this issue Oct 19, 2019 · 6 comments
Open

Server-rendered HTML minification #1432

tigt opened this issue Oct 19, 2019 · 6 comments

Comments

@tigt
Copy link
Contributor

tigt commented Oct 19, 2019

Description

Marko’s compilation to server-side templates could perform provably-safe HTML minification during compilation. (Runtime minification is probably too expensive to be worth it.)

Why

Perf matters! Running Marko’s current home page through kangax’s minifier saves 2,551 bytes. Not too bad after compression, but it’s still overhead, and fewer characters speed up parsing.

I used the relative URLs option, kept comments because I suspected Marko uses them for rehydration, and turned off the Minify CSS/JS options.

Existing HTML minifiers can be plugged into a Marko app’s render pipeline, but they require buffering the stream, defeating any performance improvements.

Possible Implementation & Open Questions

In development mode, probably don’t bother running this compilation step. But in production…

  • Normalize output for better compression
    • Normalize known tag/attribute name casing (might do this already?)
    • Sort attributes (probably alphabetically, but frequency would be even better)
    • Sort properties in style
    • Sort tokens in class, rel, and other TokenLists/similar
  • Collapse whitespace (Marko largely already does this, but it’s worth reexamining to ensure its behavior is as unsurprising and efficient as it could be)
  • Collapse all whitespace characters to a single space except when <pre>, marko-preserve-whitespace
  • Omit the closing slash on known void elements
  • Optional tags
    • Omit optional opening tags without attributes like <head> or <body>
    • Omit optional closing tags where safe — </body></html>, consecutive <p>s, </option> since </select> autocloses it
  • Omit attributes with default values :method="get", <input type="text">, <script type="text/javascript">, etc. — this would also make rendering client-side slightly more efficient
  • Attribute values
  • Reencode character references
    • Non-HTML syntax characters like &rarr; should be dereferenced
    • Unnecessary encoding should be dereferenced
      • Unambiguous ampersands
      • Quotes and > outside opening tag contexts
      • = outside attribute values
    • Some character references could be reencoded to be shorter:
      • &#60;&lt; (pretty sure only < and > qualify)
      • &#x27;&#39;, since hex numbers are only more compact for characters we’re already decoding
      • &quot;&#34;
  • Turn same-origin attribute values with URLs (href, src, srcset) into relative URLs
  • Encode indices/offsets inside Marko :scoped identifiers and <!--M#…--> boundary comments with something more space-efficient than decimal, like number.toString(36)
  • Use self-closing syntax for foreign elements (SVG & MathML, mostly), instead of the current approach: <path></path><path />

Is this something you're interested in working on?

Yes

@tigt
Copy link
Contributor Author

tigt commented Oct 25, 2019

[moved into own issue]

@tigt
Copy link
Contributor Author

tigt commented May 13, 2020

Some updated thoughts on my proposals above:

  • Normalize output for better compression
    • Normalize known tag/attribute name casing (might do this already?)
    • Sort attributes (probably alphabetically, but frequency would be even better)
    • Sort properties in style
    • Sort tokens in class, rel, and other TokenLists/similar

While DOMTokenLists produced from attributes like class and rel are order-agnostic, author CSS and JS may rely on specific order (mostly attribute selectors like [class^=…]). The compression gains from ordering these tokens are unlikely to be significant enough for that risk.

  • Omit optional closing tags where safe — </body></html>, consecutive <p>s, </option> since </select> autocloses it

  • Omit attributes with default values: method="get", <input type="text">, <script type="text/javascript">, etc. — this would also make rendering client-side slightly more efficient

After some more investigation, these two seem like they have the best reduction:effort ratio. Lotta </option>s in most <select>s, and HTML minification doubling as less JS is always nice.

  • Turn same-origin attribute values with URLs (href, src, srcset) into relative URLs

This one seems like the most difficult, but it also can pay off the most. I’m undecided.

@alexnewmannn
Copy link

@tigt Hey, just been looking through this issue and a few MR's around the unquoted attributes. We upgraded to latest and tracked our issues to the unquoted attributes. I don't feel like this requires an issue, but let me know if you prefer it in that format. Is there a way to remove this behaviour or toggle it on/off? We use a HTML to PDF converter, where our HTML is generated by Marko, and our PDF converter that we are using doesn't parse anything with unquoted attributes due to the type of spec it adheres to so right now we are stuck at 4.20.2 for the foreseeable.

@tigt
Copy link
Contributor Author

tigt commented Aug 4, 2020

@alexnewmannn I don’t believe as it stands this feature has a config toggle. (I didn’t implement it, just yammered about it in this issue.)

For a fix that would solve your problem right now, you could round-trip the HTML Marko produces into and then out of a HTML parsing library, then pass that to your PDF converter. To do that with the parse5 package would look something like:

import { parse, serialize } from 'parse5'

const minifiedHtml = markoTemplate.render() // or however you do it

const normalizedHtml = serialize(parse(minifiedHtml))

@tigt
Copy link
Contributor Author

tigt commented Nov 1, 2020

Removed the stuff about smaller internal ID encodings since any element index between 30 and ~50 would encode as forbidden Latin-1 Supplement control characters. (Aw beans.)

@tigt
Copy link
Contributor Author

tigt commented Mar 7, 2021

Ran some of the remaining suggestions against the eBay home page:

Minification Bytes 𝚫 𝚫%
(none) 388,024
Omit default attributes (also shrinks JS) 387,661 −363 −0.09%
Decode HTML entities 387,600 −424 −0.11%
Omit optional tags (also shrinks JS, DOM size via less text nodes) 386,250 −1,774 −0.46%

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants