Производительность фронтенда. Часть 3 — оптимизация шрифтов

в 17:44, , рубрики: connect-fonts, mozilla persona, node.js, node.js holiday season, Блог компании Нордавинд, Веб-разработка, веб-шрифты, подмножества шрифтов

Производительность фронтенда. Часть 3 — оптимизация шрифтовОт переводчика: Это восьмая статья из цикла о Node.js от команды Mozilla Identity, которая занимается проектом Persona.


Мы смогли уменьшить объем шрифтов для Persona на 85%, с 300 до 45 килобайт, используя подмножества шрифтов. Эта статья рассказывает о том, как именно мы это сделали, и какие мы использовали инструменты.

Представляем connect-fonts

Connect-fonts — это middleware для Connect, которое улучшает производительность @font-face, раздавая клиентам подобранные специально для их языка подмножества шрифтов, уменьшая тем самым их размер. Connect-fonts также генерирует специфические для локали и браузера стили @font-face и CORS-заголовки для Firefox и IE9+. Для раздачи подмножеств шрифтов создаются так называемые font packs — поддиректории с подмножествами шрифтов плюс простой конфигурационный файл JSON. Некоторые наборы распространённых open source-шрифтов доступны в готовом виде в пакете npm, впрочем, создавать свои пакеты совсем нетрудно.

Если вы не слишком хорошо ориентируетесь в работе со шрифтами в интернете, мы собрали небольшую коллекцию ссылок по теме @font-face. [От переводчика: а на Хабре очень кстати статья, посвящённая производительности веб-шрифтов]

Статическая и динамическая загрузка шрифтов

Когда вы просто отдаёте один большой шрифтовой файл всем пользователям, всё довольно просто:

  1. пишете блок @font-face в вашем файле CSS;
  2. генерируете шрифтовые файл для веб из исходника в TTF или OTF и кладёте в директорию, к которой есть доступ у веб-сервера;
  3. настраиваете заголовки CORS, если ссылаетесь на шрифты с другого домена, так как политика одного источника Firefox и IE9+ распространяется и на шрифты.

Это несложные шаги. Первые два можно сделать ещё легче с помощью, например, FontSquirrel — онлайн генератора шрифтов, который автоматически создаст файлы шрифтов и код CSS. Для добавления заголовков CORS придётся почитать документацию Apache или Nginx, но и это не слишком трудно.

Но если вы хотите использовать все преимущества подмножеств шрифтов, всё усложняется. Вам нужны будут отдельные файлы шрифтов для каждой локали, и будет нужно динамически менять декларации @font-face, чтобы они указывали на правильные URL. Так же придётся разбираться и с CORS. Именно эти проблемы решает connect-fonts.

Подмножества шрифтов: обзор

По умолчанию, каждый шрифт содержит множество символов — латиницу, диакритические знаки для таких языков как французский или немецкий, дополнительные алфавиты, такие как греческий или кириллица. Многие шрифты также содержат символы, особенно если поддерживают Юникод (например, символ снеговика — ). Есть и шрифты с поддержкой иероглифов. Всё это содержится в файле шрифта, чтобы он был полезен максимально широкой аудитории. Такая гибкость ведёт к большим размерам файлов. Microsoft Arial Unicode, который содержит все символы Uncode 2.1, весит невероятные 22 мегабайта!

В то же время типичная веб-страница использует щрифт для одной конкретной задачи — отображения контента, обычно на одном языке и без экзотических символов. Ограничивая шрифт этим небольшим подмножеством, мы можем очень сильно сэкономить.

Выигрыш в производительности при использовании подмножеств шрифтов

Давайте сравним размеры полных файлов распространённых шрифтов с подмножествами для нескольких локалей. Даже если ваш сайт работает только на английском языке, вы можете очень сильно сэкономить, выдавая локализованное подмножество.

Меньший размер файлов шрифтов означает более быструю загрузку и появление шрифтов на экране. Это особенно важно для мобильных устройств. Если пользователю доведётся зайти на сайт через 2G-соединение, уменьшение веса страницы на 50 килобайт может ускорить загрузку на 2-3 секунды. Ещё один момент — кеш в мобильных устройствах часто невелик, и у маленького шрифта больше шансов в нём задержаться.

Сравнение размеров полного шрифта Open Sans Regular с подмножествами для разных локалей:
Производительность фронтенда. Часть 3 — оптимизация шрифтов

То же самое в сжатом gzip виде:
Производительность фронтенда. Часть 3 — оптимизация шрифтов

Даже с учётом сжатия можно уменьшить размер шрифта на 80%, с 63 до 13 килобайт, в случае использования только английского подмножества Open Sans. И это только один шрифт — большинство сайтов используют несколько. Огромный потенциал для оптимизации!

Используя connect-fonts, мы в Mozilla Persona добились уменьшения на 85%, c 300 до 45 килобайт. Это эквивалентно паре секунд времени загрузки на типичном 3G-соединении и десятку секунд на 2G.

Дальнейшие оптимизации

Если вы хотите избавиться от каждого лишнего байта, можно настроить connect-fonts так, чтобы он возвращал CSS не в виде файла, а виде строки, которую можно будет включить в общий файл. Кроме того, connect-fonts по умолчанию генерирует минимальную декларацию @font-face, не включая в неё файлы в форматах, которые не принимает конкретный браузер.

Пример использования connect-fonts в приложении

Предположим, что у нас есть простое приложение на Express, которое сообщает клиенту текущее время:

// app.js
const
ejs = require('ejs'),
express = require('express'),
fs = require('fs');
 
var app = express.createServer(),
  tpl = fs.readFileSync(__dirname, '/tpl.ejs', 'utf8');
 
app.get('/time', function(req, res) {
  var output = ejs.render(tpl, {
    currentTime: new Date()
  });
  res.send(output);
});
 
app.listen(8765, '127.0.0.1');

используя самый примитивный шаблон:

// tpl.ejs
<!doctype html>
<p>the time is <%= currentTime %>

Подключим connect-fonts, чтобы отдавать локализованное подмножество Open Sans — одного из нескольких включённых в пакеты готовых наборов.

Изменения в приложении

Сначала установим нужные пакеты:

 $ npm install connect-fonts
 $ npm install connect-fonts-opensans

Подключим middleware:

 // app.js с изменениями для использования connect-fonts
 const
 ejs = require('ejs'),
 express = require('express'),
 fs = require('fs'),
 // добавлено:
 connect_fonts = require('connect-fonts'),
 opensans = require('connect-fonts-opensans');
 
 var app = express.createServer(),
   tpl = fs.readFileSync(__dirname, '/tpl.ejs', 'utf8');

Инициализируем модуль:

// продолжение app.js 
// добавим этот вызов app.use:
app.use(connect_fonts.setup({
    fonts: [opensans],
    allow_origin: 'http://localhost:8765'
})

connect_fonts.setup() принимает следующие аргументы:

  • fonts — массив необходимых шрифтов
  • allow_origin — источник, из которого берём шрифты; connect-fonts использует эту информацию для установки заголовка Access-Control-Allow-Origin для браузеров Firefox 3.5+ и IE9+.
  • ua (необязательный) — список user-agents, которым мы отдаём шрифты. По умолчанию connect-fonts определяет user-agent по запросу, самостоятельно решая, какие форматы файлов ключить в декларацию. Это поведение может быть изменено, если передать ua: 'all' — тогда будут включены все имеющиеся форматы.

Внутри маршрута нужно передать локаль в шаблон:

 // продолжение app.js
 app.get('/time', function(req, res) {
   var output = ejs.render(tpl, {
    // передаём локаль в шаблон:
    userLocale: detectLocale(req),
    currentTime: new Date()
   });
   res.send(output);
 });

Для определения локали Mozilla Persona использует i18n-abide. Есть ещё очень неплохой модуль locale, оба они доступны через npm. Но, чтобы не сложнять пример, мы просто возьмем первые два символа из поля запроса Accept-language:

 // примитивное определение локали
 function detectLocale(req) {
   return req.headers['accept-language'].slice(0,2);
 }
 
 app.listen(8765, '127.0.0.1');
 // конец app.js

Изменения в шаблоне

Теперь нужно обновить шаблон. Connect-fonts предполагает, что маршруты прописаны в виде:

/:locale/:font-list/fonts.css

например:

/fr/opensans-regular,opensans-italics/fonts.css

В нашем случае надо добавить ссылку на файл стилей по тому пути, который ожидает connect-fonts:

// tpl.ejs - изменения для connect-fonts
<!doctype html>
<link href="/<%= userLocale %>/opensans-regular/fonts.css" rel="stylesheet">

и добавить в код страницы стиль для использования шрифта:

 // продолжение tpl.ejs
 <style>
   body { font-family: "Open Sans", "sans-serif"; }
 </style>
 <p>the time is <%= currentTime %>

Вот и всё. CSS, созданный connect-fonts, зависит от локали пользователя и его браузера. Вот пример для английской локали:

 * это пример вывода при свойстве 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');
}

Если вы хотите сэкономить один HTTP запрос, вы можете использовать метод connect_fonts.generate_css(), чтобы получать код CSS в виде строки, и включить её в общий файл CSS.

Теперь наше маленькое приложение красиво показывает время. Его полный исходный код доступен на Гитхабе. Мы использовали готовый font pack, но создать собственный совсем не трудно. Инструкция есть в readme.

Зключение

Подмножества могут дать очень большой выигрыш в производительности для сайтов, которые используют веб-шрифты. connect-fonts делает немало работы со шрифтами в многоязычном приложении. Даже если ваш сайт подерживает только один язык, вы всё равно можете использовать этот модуль чтобы урезать шрифты до единственной необходимой вам локали и автоматически генрировать CSS и заголовки CORS, плюс это облегчит последующую интернационализацию.

Дальнейшие оптимизации могут состоять в исключении хинтинга для платформ, которые его не поддерживают (всех кроме Windows), автоматическом сжатии шрифтов и установке заголовков для кеширования.


Продолжение следует...

Автор: ilya42

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js