From the translator: This is the eighth article from the Node.js series from the Mozilla Identity team, which deals with the Persona project.
All articles of the cycle:
We were able to reduce the amount of fonts for Persona by 85%, from 300 to 45 kilobytes, using subsets of fonts. This article tells you exactly how we did it and what tools we used.
')
Introducing connect-fonts
Connect-fonts is a middleware for Connect that improves
@font-face
performance by distributing subsets of fonts specifically chosen for their language, thereby reducing their size. Connect-fonts also generates locale and browser-specific
@font-face
and CORS headers for Firefox and IE9 +. To distribute subsets of fonts, so-called font packs are created - subdirectories with subsets of fonts plus a simple JSON configuration file. Some sets of common open source fonts are available in a ready-made form in
the npm package , however, it is not difficult to create your own packages.
If you are not too well versed in working with fonts on the Internet, we have collected a
small collection of links on the subject of
@font-face
. [
From the translator: and on Habré, an article on the performance of web fonts is very useful ]
Static and dynamic font loading
When you just give one big font file to all users, it's pretty simple:
- write the
@font-face
block in your CSS file; - generate a font file for the web from the source in TTF or OTF and put it in a directory to which the web server has access;
- configure the CORS headers if you refer to fonts from another domain, since the policy of the same Firefox and IE9 + source also applies to fonts.
These are simple steps. The first two can be made even easier with, for example,
FontSquirrel , an online font generator that automatically creates font files and CSS code. To add CORS headers you will have to read the Apache or Nginx documentation, but this is not too difficult.
But if you want to use all the advantages of subsets of fonts, everything becomes more complicated. You will need separate font files for each locale, and you will need to dynamically change the
@font-face
declarations to point to the correct URLs. You will also have to deal with CORS. These problems are solved by connect-fonts.
Font subsets: an overview
By default, each font contains a variety of characters - Latin, accented characters for languages such as French or German, additional alphabets such as Greek or Cyrillic. Many fonts also contain characters, especially if they support Unicode (for example, the snowman symbol -
). There are fonts with support for hieroglyphs. All this is contained in the font file so that it is useful to the widest possible audience. This flexibility leads to large file sizes. Microsoft Arial Unicode, which contains all Uncode 2.1 characters, weighs an incredible 22 megabytes!
At the same time, a typical web page uses a font for one specific task - displaying content, usually in the same language and without exotic characters. By limiting the font to this small subset, we can save a lot.
Performance gains when using subsets of fonts
Let's compare the size of full files of common fonts with subsets for several locales. Even if your site works only in English, you can save a lot by producing a localized subset.
Smaller font file size means faster loading and appearance of fonts on the screen. This is especially important for mobile devices. If the user happens to enter the site via a 2G connection, reducing the weight of the page by 50 kilobytes can speed up the download by 2-3 seconds. One more thing - the cache in mobile devices is often small, and a small font has more chances to stay in it.
Comparing the size of the full Open Sans Regular font with subsets for different locales:
The same in gzip compressed form:
Even with compression, you can reduce the font size by 80%, from 63 to 13 kilobytes, in the case of using only the English subset of Open Sans. And this is only one font - most sites use several. Huge potential for optimization!
Using connect-fonts, we in Mozilla Persona achieved a reduction of 85%, from 300 to 45 kilobytes. This is equivalent to a couple of seconds of download time on a typical 3G connection and ten seconds per 2G.
Further optimizations
If you want to get rid of every extra byte, you can configure connect-fonts to return CSS not as a file, but as a string that can be included in a common file. In addition, connect-fonts by default generates a minimal
@font-face
declaration, not including files in formats that a particular browser does not accept.
An example of using connect-fonts in an application
Suppose that we have a simple Express application that tells the client the current time:
using the most primitive pattern:
// tpl.ejs <!doctype html> <p>the time is <%= currentTime %>
We connect connect-fonts to render a localized subset of Open Sans - one of several ready-made sets included in packages.
Changes in the application
First, install the necessary packages:
$ npm install connect-fonts $ npm install connect-fonts-opensans
Connect middleware:
Initialize the module:
connect_fonts.setup()
takes the following arguments:
fonts
- an array of required fontsallow_origin
- the source from which we take fonts; connect-fonts uses this information to set the Access-Control-Allow-Origin
header for Firefox 3.5+ and IE9 + browsers.ua
(optional) - a list of user-agents to which we give fonts. By default, connect-fonts determines the user-agent upon request, independently deciding which file formats to include in the declaration. This behavior can be changed if you transfer ua: 'all'
- then all available formats will be included.
Inside the route you need to transfer the locale to the template:
Mozilla Persona uses
i18n-abide to determine the locale. There is also a very good
locale module, both of which are available via npm. But in order not to complicate an example, we simply take the first two characters from the
Accept-language
request field:
Pattern Changes
Now you need to update the template. Connect-fonts assumes that routes are written as:
/:locale/:font-list/fonts.css
eg:
/fr/opensans-regular,opensans-italics/fonts.css
In our case, we need to add a link to the style file along the path that connect-fonts expects:
// tpl.ejs - connect-fonts <!doctype html> <link href="/<%= userLocale %>/opensans-regular/fonts.css" rel="stylesheet">
and add a style to the page code to use the font:
// tpl.ejs <style> body { font-family: "Open Sans", "sans-serif"; } </style> <p>the time is <%= currentTime %>
That's all. The CSS created by connect-fonts depends on the user's locale and browser. Here is an example for the English locale:
* ua 'all' */ @font-face { font-family: 'Open Sans'; font-style: normal; font-weight: 400; src: url('/fonts/en/opensans-regular.eot'); src: local('Open Sans'), local('OpenSans'), url('/fonts/en/opensans-regular.eot#') format('embedded-opentype'), url('/fonts/en/opensans-regular.woff') format('woff'), url('/fonts/en/opensans-regular.ttf') format('truetype'), url('/fonts/en/opensans-regular.svg#Open Sans') format('svg'); }
If you want to save a single HTTP request, you can use the
connect_fonts.generate_css()
method to get the CSS code as a string and include it in the common CSS file.
Now our little application beautifully shows time. Its full source code is available on Github. We used ready-made font pack, but creating your own is not at all difficult.
The instruction is in the readme.
Shutdown
Subsets can give a very big performance gain for sites that use web fonts. connect-fonts does a lot of work with fonts in a multilingual application. Even if your site only supports one language, you can still use this module to trim fonts to the only locale you need and automatically generate CSS and CORS headers, plus this will facilitate subsequent internationalization.
Further optimizations may consist in excluding hinting for platforms that do not support it (all but Windows), automatic font compression and setting headers for caching.
All articles of the cycle: