От переводчика: Это восьмая статья из цикла о Node.js от команды Mozilla Identity, которая занимается проектом Persona.
Мы смогли уменьшить объем шрифтов для Persona на 85%, с 300 до 45 килобайт, используя подмножества шрифтов. Эта статья рассказывает о том, как именно мы это сделали, и какие мы использовали инструменты.
Connect-fonts — это middleware для Connect, которое улучшает производительность
Если вы не слишком хорошо ориентируетесь в работе со шрифтами в интернете, мы собрали небольшую коллекцию ссылок по теме
Когда вы просто отдаёте один большой шрифтовой файл всем пользователям, всё довольно просто:
Это несложные шаги. Первые два можно сделать ещё легче с помощью, например, FontSquirrel — онлайн генератора шрифтов, который автоматически создаст файлы шрифтов и код CSS. Для добавления заголовков CORS придётся почитать документацию Apache или Nginx, но и это не слишком трудно.
Но если вы хотите использовать все преимущества подмножеств шрифтов, всё усложняется. Вам нужны будут отдельные файлы шрифтов для каждой локали, и будет нужно динамически менять декларации
По умолчанию, каждый шрифт содержит множество символов — латиницу, диакритические знаки для таких языков как французский или немецкий, дополнительные алфавиты, такие как греческий или кириллица. Многие шрифты также содержат символы, особенно если поддерживают Юникод (например, символ снеговика — ). Есть и шрифты с поддержкой иероглифов. Всё это содержится в файле шрифта, чтобы он был полезен максимально широкой аудитории. Такая гибкость ведёт к большим размерам файлов. Microsoft Arial Unicode, который содержит все символы Uncode 2.1, весит невероятные 22 мегабайта!
В то же время типичная веб-страница использует щрифт для одной конкретной задачи — отображения контента, обычно на одном языке и без экзотических символов. Ограничивая шрифт этим небольшим подмножеством, мы можем очень сильно сэкономить.
Давайте сравним размеры полных файлов распространённых шрифтов с подмножествами для нескольких локалей. Даже если ваш сайт работает только на английском языке, вы можете очень сильно сэкономить, выдавая локализованное подмножество.
Меньший размер файлов шрифтов означает более быструю загрузку и появление шрифтов на экране. Это особенно важно для мобильных устройств. Если пользователю доведётся зайти на сайт через 2G-соединение, уменьшение веса страницы на 50 килобайт может ускорить загрузку на 2-3 секунды. Ещё один момент — кеш в мобильных устройствах часто невелик, и у маленького шрифта больше шансов в нём задержаться.
Сравнение размеров полного шрифта Open Sans Regular с подмножествами для разных локалей:
То же самое в сжатом gzip виде:
Даже с учётом сжатия можно уменьшить размер шрифта на 80%, с 63 до 13 килобайт, в случае использования только английского подмножества Open Sans. И это только один шрифт — большинство сайтов используют несколько. Огромный потенциал для оптимизации!
Используя connect-fonts, мы в Mozilla Persona добились уменьшения на 85%, c 300 до 45 килобайт. Это эквивалентно паре секунд времени загрузки на типичном 3G-соединении и десятку секунд на 2G.
Если вы хотите избавиться от каждого лишнего байта, можно настроить connect-fonts так, чтобы он возвращал CSS не в виде файла, а виде строки, которую можно будет включить в общий файл. Кроме того, connect-fonts по умолчанию генерирует минимальную декларацию
Предположим, что у нас есть простое приложение на Express, которое сообщает клиенту текущее время:
используя самый примитивный шаблон:
Подключим connect-fonts, чтобы отдавать локализованное подмножество Open Sans — одного из нескольких включённых в пакеты готовых наборов.
Сначала установим нужные пакеты:
Подключим middleware:
Инициализируем модуль:
Внутри маршрута нужно передать локаль в шаблон:
Для определения локали Mozilla Persona использует i18n-abide. Есть ещё очень неплохой модуль locale, оба они доступны через npm. Но, чтобы не сложнять пример, мы просто возьмем первые два символа из поля запроса
Теперь нужно обновить шаблон. Connect-fonts предполагает, что маршруты прописаны в виде:
например:
В нашем случае надо добавить ссылку на файл стилей по тому пути, который ожидает connect-fonts:
и добавить в код страницы стиль для использования шрифта:
Вот и всё. CSS, созданный connect-fonts, зависит от локали пользователя и его браузера. Вот пример для английской локали:
Если вы хотите сэкономить один HTTP запрос, вы можете использовать метод
Теперь наше маленькое приложение красиво показывает время. Его полный исходный код доступен на Гитхабе. Мы использовали готовый font pack, но создать собственный совсем не трудно. Инструкция есть в readme.
Подмножества могут дать очень большой выигрыш в производительности для сайтов, которые используют веб-шрифты. connect-fonts делает немало работы со шрифтами в многоязычном приложении. Даже если ваш сайт подерживает только один язык, вы всё равно можете использовать этот модуль чтобы урезать шрифты до единственной необходимой вам локали и автоматически генрировать CSS и заголовки CORS, плюс это облегчит последующую интернационализацию.
Дальнейшие оптимизации могут состоять в исключении хинтинга для платформ, которые его не поддерживают (всех кроме Windows), автоматическом сжатии шрифтов и установке заголовков для кеширования.
Все статьи цикла:
- "Охотимся за утечками памяти в Node.js"
- "Нагружаем Node под завязку"
- "Храним сессии на клиенте, чтобы упростить масштабирование приложения"
- "Производительность фронтэнда. Часть 1 — конкатенация, компрессия, кэширование"
- "Пишем сервер, который не падает под нагрузкой"
- "Производительность фронтэнда. Часть 2 — кешируем динамический контент с помощью etagify"
- "Приручаем конфигурации веб-приложений с помощью node-convict"
- "Производительность фронтенда. Часть 3 — оптимизация шрифтов"
- "Локализация приложений Node.js. Часть 1"
- "Локализация приложений Node.js. Часть 2: инструментарий и процесс"
- "Локализация приложений Node.js. Часть 3: локализация в действии"
- "Awsbox — PaaS-инфраструктура для развёртывания приложений Node.js в облаке Amazon"
Мы смогли уменьшить объем шрифтов для 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
. [От переводчика: а на Хабре очень кстати статья, посвящённая производительности веб-шрифтов]Статическая и динамическая загрузка шрифтов
Когда вы просто отдаёте один большой шрифтовой файл всем пользователям, всё довольно просто:
- пишете блок
@font-face
в вашем файле CSS; - генерируете шрифтовые файл для веб из исходника в TTF или OTF и кладёте в директорию, к которой есть доступ у веб-сервера;
- настраиваете заголовки 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 с подмножествами для разных локалей:
То же самое в сжатом gzip виде:
Даже с учётом сжатия можно уменьшить размер шрифта на 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), автоматическом сжатии шрифтов и установке заголовков для кеширования.
Все статьи цикла:
- "Охотимся за утечками памяти в Node.js"
- "Нагружаем Node под завязку"
- "Храним сессии на клиенте, чтобы упростить масштабирование приложения"
- "Производительность фронтэнда. Часть 1 — конкатенация, компрессия, кэширование"
- "Пишем сервер, который не падает под нагрузкой"
- "Производительность фронтэнда. Часть 2 — кешируем динамический контент с помощью etagify"
- "Приручаем конфигурации веб-приложений с помощью node-convict"
- "Производительность фронтенда. Часть 3 — оптимизация шрифтов"
- "Локализация приложений Node.js. Часть 1"
- "Локализация приложений Node.js. Часть 2: инструментарий и процесс"
- "Локализация приложений Node.js. Часть 3: локализация в действии"
- "Awsbox — PaaS-инфраструктура для развёртывания приложений Node.js в облаке Amazon"