coreinfrastructure / best-practices-badge

🏆Open Source Security Foundation (OpenSSF) Best Practices Badge (formerly Core Infrastructure Initiative (CII) Best Practices Badge)
https://www.bestpractices.dev
MIT License
1.21k stars 201 forks source link

Minimize downloaded font sizes #1045

Open david-a-wheeler opened 6 years ago

david-a-wheeler commented 6 years ago

We use very few characters in the fonts we use, namely font awesome and glyphicons. See: https://blog.webjeda.com/optimize-fontawesome/

david-a-wheeler commented 6 years ago

https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/webfont-optimization

david-a-wheeler commented 6 years ago

May be useful:

https://pypi.org/project/FontTools/

david-a-wheeler commented 6 years ago

Fonts are currently a significant slowdown on initial page load. This comment discusses why it would be a good idea to optimize icon handling.

Pingdom reports for the home page that it uses:

That's a total of 95.2 kB for icon font files, out of a total of ALL bytes downloaded of 380.1 kB (that's including all the project logos!). That means that 25% of all the bytes of the front page are just font icon files. That's not counting all the CSS overhead used to support icon font files, which means that the icon files impose even more overhead than this.

Similarly, running on /projects/1 shows those same two font files, and the total is 259.7 kB... so 37% of the initial load of a project file is just the font files (not counting their supporting CSS).

Counting the supporting CSS in addition is harder, but that's probably another 5%, so we're talking around 1/3 of all the bytes on the initial page load are just for icons.

The good news is that these are cached, so the big hit is on first load, not later pages.

As pingdom notes, it would be better if we changed our configuration to "Serve static content from a cookieless domain". But that is something of a pain to do with our current setup.

No matter what, reducing the size of the content that needs sending makes life better for our users, especially if they have slow internet connections. The icons take such a large percentage of the overall number of bytes that they become an obvious target for optimization.

david-a-wheeler commented 6 years ago

The next step is to ask, "what to change things to?" There's some debate about using icon fonts, individual SVGs, or SVG sprites. Icon fonts can be made dramatically smaller via subsetting, but there is still legitimate disagreement on the approaches. They have different pros and cons, unsurprisingly. Some pages:

A negative of icon fonts is that if loading fails, they generally show "unknown character" or other ugliness. On the other hand, failing to load SVGs isn't all that wonderful either.

Font-awesome is originally an icon font, but they do provide SVGs and other formats too, so it's flexible.

Bootstrap is dropping glyphicons, the glyphicons font has an odd license, and font-awesome is expanding. So if we do stick with icon fonts, we need to extract just what we use. We might be able to switch to just font-awesome for practically all the icons - that would completely eliminate loading glyphicons, and the subset should make the font icon small (and not just the font file itself, but also its supporting CSS will get smaller).

I don't like the font-awesome language selection icon. Instead, I like the one we use, which is the one advocated by languageicon.org. But that's not a big deal - that's available as an SVG, and we could use the SVG for the "language selection" case.

david-a-wheeler commented 6 years ago

As noted earlier, Bootstrap is dropping glyphicons, font awesome has a clearer/better license for its free set, and there are advantages to using a set of icons from a single place (for consistency and optimization).

If we want to eliminate glyphicons , we'll need to change some JavaScript code and some locale text. We also need to figure out what to replace. These appear to be all the glyphicons we use:

Font awesome has a massive number of "up" and "down" indicators if we want to use them instead, but we've been using chevrons, so we may as well keep doing so.

We say "log in" instead of "sign in" in the English text, so that the text "Log in" is very distinct from the text "Sign up". I think we should keep the English text as-is because the difference is more obvious (nobody will notice or care that the icon names are different). For signing up we've been using the generic "glyphicon-user" image, however font awesome has a relatively-new icon fa user-plus that's specifically designed to indicate "add a new user", and that seems better. They also have user-times for "delete a user". There are discussions related to that in fa 872 and fa 902.

david-a-wheeler commented 6 years ago

As best as I can tell, this is the complete list of font awesome icons we are currently using:

fa-twitter
fa-reddit-alien
fa-facebook
fa-linkedin
fa-googleplus
fa-hacker-news
fa-envelope
fa-language
fa-github

fa-language is in the list but I don't like it as much as the "book-like" image advocated by languageicon.org (which is what we're currently using) - making that an SVG (maybe with a backup PNG) would be fine.

david-a-wheeler commented 6 years ago

Hmmm, this analysis is clearly misleading. In particular, when the width is small, we switch to a "hamburger" icon, but the hamburger isn't listed. Bootstrap must be quietly inserting a glyphicon that shows the hamburger. That means it may be excessively difficult to eliminate glyphicons entirely as long as we use Bootstrap 3. That said, we could move to mostly font awesome, which would simplify a later move to Bootstrap 4, and we can certainly create a subset of font awesome so much less has to be loaded.

david-a-wheeler commented 6 years ago

Other useful font awesome icons:

david-a-wheeler commented 6 years ago

We should preload the icons (be they in a font file, separate SVG files, or an SVG sprite), so that the user will get them faster. Preloading is very well supported.

More info: Mozilla page on preloading, W3C TR on preloading,

It's easy, like this:

<link rel="preload" href="main.js" as="script">
david-a-wheeler commented 6 years ago

The font awesome site has a whole new approach that they prefer, SVG with JavaScript. It requires the use of JavaScript to support icons. The JavaScript searches for the old style, and replaces it with SVG embedded in the page.

I don't think we should use that approach. I think the more conventional icon font is a better place, at least for a start. The performance of this alternative doesn't impress me, the browsers will have to reparse and reexecute the SVG every single time, and that's after the delay of running the JavaScript.

The interface from the point of view of the HTML is the same, so it is much easier to upgrade to a font icon anyway.

david-a-wheeler commented 6 years ago

Ok, proposed plan:

  1. Switch everything we can from glyphicons to font awesome, including adding some new icon uses (glyphicons are not in Bootstrap 4 and have a less-good license).
  2. Switch to the SVG for the language selector.
  3. Subset font awesome (version 5) as an icon set, and embed the subset (removing the font awesome gem), and preload it. Continue to use the icon set as-is.

We have a separate issue to move to Bootstrap 4; when we do that, glyphicons will disappear from our use.

david-a-wheeler commented 6 years ago

font-awesome-rails issue 194 discusses supporting Font Awesome 5 (the latest version) instead of Font Awesome 4. They're still discussing it. I hope they'll enable people to refer to SVG directly (without requiring JavaScript), but that is not clear, and indeed there are many questions about how they'll support Font Awesome 5.

david-a-wheeler commented 6 years ago

Some web browsers don't support SVG sprites. Web fonts are more supported, but dyslexics have problems. Both are harder to subset.

The easy solution, long-term, is to use basic SVG files using img references. This is 100% standard, supported by practically all GUI web browsers, easily supports subsetting, and is super-easy to cache. That creates multiple files, which HTTP/1.1 doesn't like so much, but preload basically resolves that.

One annoyance: Rails doesn't serve compressed SVG files. SVG files aren't huge (since we'd serve a small subset), but it's absurd. See: https://stackoverflow.com/questions/43711209/rails-not-serving-compressed-svg-files https://github.com/rails/rails/commit/8e31fa3b72892f7421b85b5a79d71a2d726ccddd#commitcomment-23933895 There's a monkey-patch available, but I'm not a fan of monkey patches.

Precreating the .gz is possible: https://github.com/rails/sprockets/issues/26#issuecomment-121316510 https://mattbrictson.com/gzipped-assets-nginx-rails-42

The common historical was to compress is .gz. Of course, we could use brotli instead: https://caniuse.com/#feat=brotli

david-a-wheeler commented 6 years ago

It may be useful to switch to font-awesome-sass - which supports font-awesome 5, etc.