- PVSM.RU - https://www.pvsm.ru -

Производительность фронтэнда. Часть 1 — конкатенация, компрессия, кэширование (4-я из 12 статей о Node.js от Mozilla)

Производительность фронтэнда. Часть 1 — конкатенация, компрессия, кэширование (4 я из 12 статей о Node.js от Mozilla)От переводчика: Это четвёртая статья из цикла о Node.js [1] от команды Mozilla Identity, которая занимается проектом Persona [2]. Эта статья о том, как увеличить скорость загрузки ресурсов сайта и модуле connect-cachify, созданном в Mozilla для этой цели.


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

Но сначала окинем взглядом наиболее распространённые пути улучшения производительности. Если у вас уже есть опыт оптимизации фронтэнда, можете пропустить эти разделы и перейти в конец статьи, где описано, как с помощью connect-cachify автоматически делать то, что вам раньше доводилось делать вручную.

Три 'К' производительности на клиенте

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

Конкатенация

Цель конкатенации — минимизировать количество запросов к серверу. Запросы стоят дорого. Время на открытие соединения HTTP часто больше чем время на саму передачу данных. Каждый лишний запрос замедляет работу сайта, особенно на мобильных устройствах, где сетевые задержки могут быть очень большими. Вам когда-нибудь приходилось заходить в какой-нибудь интернет-магазин с мобильного по EDGE и мучительно ждать поочерёдной загрузки множества изображений?

Новый протокол SPDY, [6] уже легший в основу спецификации новой версии протокола HTTP 2.0, умеет объединять запросы к нескольким ресурсам в одно соединение HTTP. К сожалению, пока что его поддержка есть только в последних версиях Chrome, Firefox и Opera.

Объединение нескольких ресурсов в один в ручном режиме, по-старинке, работает где угодно и будет работать и после повсеместного распространения SPDY. Есть много инструментов для объединения основных типов ресурсов — JavaScrit, CSS и изображений.

JavaScript и CSS

Если сайт использует более чем один внешний файл JavaScript, то стоит подумать об объединении всех этих файлов в один. Браузеры приостанавливают рендеринг страницы на время загрузки скриптов. Так как каждый запрос требует накладных расходов времени, пример ниже работает медленнее, чем мог бы:

  <script src="jquery.min.js"></script>
  <script src="main.js"></script>
  <script src="image-carousel.js"></script>
  <script src="widget.js"></script>

Объединив несколько запросов в один, можно значительно снизить время загрузки:

  <script src="main.production.js"></script>

Работать с объединённом файлом во время написания кода и отладки очень неудобно, поэтому обычно конкатенацию делают только для готового сайта. Файлы CSS объединяются точно так же как и JavaScript.

Изображения

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

data: URI

data: URI — это особая форма URL, которая позволяет внедрять небольшие изображения в виде URL прямо в текст документа HTML или CSS. URI с данными может использоваться как в атрибуте src тега img, так и в url фонового изображения в CSS. Так как внедрённые изображения кодируются в base64, они занимают больше места, но требуют на один запрос меньше, чем обычные изображения. Если картинка достаточно маленькая, то выигрыш за счёт уменьшения количества запросов больше проигрыша в размере. IE6 и IE7 не поддерживают data: URI, имейте это в виду.

Спрайты

Спрайты — отличная альтернатива data: URI. Спрайт — это коллекция изображений, объединённая в одну большую картинку. Средствами CSS из неё можно вырезать маленькие изображения. Недостаток спрайтов — неудобство в обслуживании и модификации. Добавление или изменение изображений в спрайте может потребовать соответствующих изменений в CSS.

Существует множество инструментов для создания спрайтов из набора отдельных изображений. Один из них — сайт Sprite Cow [7], который генерирует код CSS для спрайтов на основе загруженной картинки.

Удаляем лишние байты — минификация, оптимизация и компрессия

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

JavaScript и CSS

JavaScript и CSS — это текстовые ресурсы, которые эффективно минифицируются. Минификация — это удаление всех символов, которые игнорирует браузер. Преобразование как JavaScript, так и CSS начинается с удаления лишних пробелов, переводов строк и комментариев. В случае JavaScript можно пойти гораздо дальше. Некоторые минификаторы заменяют многосимвольные имена переменных на односимвольные, убирают излишние конструкции языка и даже заменяют их на эквивалентные более короткие.

Среди популярных инструментов для минификации JavaScript — UglifyJS [8], YUICompressor [9] и Google Colsure Compiler [10]. Для CSS часто используются YUICompressor и UglifyCSS [11].

Изображения

Изображения часто содержат избыточные данные, которые можно удалить без ухудшения визуального качества. Это не трудно, но требует применения специальных инструментов. Франсуа Марье из нашей команды более подробно писал на эту тему в статьях, посвящённых работе с PNG [12] и GIF [13].

Yahoo! предлагает онлайн-инструмент Smush.it [14]. ImageOptim [15] — аналогичная оффлайновая программа для OSX. Она может работать в полностью автоматическом режиме — достаточно перетащить на неё нужные файлы, и они могут заметно похудеть.

Если допустимо некоторое ухудшение визуального качества, можно дополнительно пережать картинку с большей степенью сжатия.

Сервер тоже может помочь

Даже после конкатенации и минификации есть на чём сэкономить. Почти все серверы и браузеры поддерживают компрессию трафика HTTP. Две самых популярных схемы компрессии — deflate и gzip. Они обе используют очень эффективные алгоритмы сжатия, чтобы уменьшить размер данных ещё до того, как они покинут сервер.

Кеширование

Конкатенация и компрессия помогают тем, кто в первый раз зашёл на наш сайт. Третья «К» — кеширование, помогает постоянным посетителям. Пользователь, который уже был на сайте, не должен заново загружать все ресурсы. HTTP предлагает два механизма чтобы добиться этого — заголовки для кеширования и ETags, или entity tags.

Есть два вида заголовков HTTP, относящихся к кешированию статических ресурсов, которые никогда или почти никогда не меняются. Это два поля заголовка ответа HTTP — Expires и Cache-Control: max-age. В поле Expires хранится дата, после которой ресурс нужно будет обновить. max-age — время жизни ресурса в секундах. Если эти поля в заголовке установлены, браузер будет запрашивать уже имеющийся ресурс только по истечению заданного времени.

ETag — это метка версии ресурса, которая служит для того, чтобы убедиться что локальная версия ресурса совпадает с актуальной версией на сервере. Etag подходит для динамического контента, который может меняться в любое время. Когда у ресурса установлен ETag, это говорит браузеру о том, что нужно проверить версию, и если она не изменилась, ресурс можно не загружать. Так как ресурс c ETag всё таки требует передавать с сервера небольшое количество информации каждый раз, он не так эффективен, как ресурс с указанием времени жизни.

Принудительная очистка кеша

Преимущество использования полей Expires и max-age перед Etag состоит в том, что браузер запрашивает ресурсы, только если они просрочены. В этом же состоит и главный недостаток. Что если ресурс внезапно изменится? Иногда бывает необходимо принудительно очищать кеш.

Обычно это делают путём добавления номера версии ресурса в URL. Любое изменение URL приводит к тому, что загружается новая версия ресурса.

Например, если время жизни http://example.com/logo.png составляет один год, но логотип меняется, все, кто уже загрузил старую версию, увидят новую только через год. С этим можно бороться, добавив в URL какой-то идентификатор версии:

http://example.com/v8125/logo.png

или

http://example.com/logo.png?v8125

Когда логотип меняется, изменится и URL, а значит все пользователи загрузят новую версию.

Connect-cachify: библиотека Node.js для конкатенации и кеширования ресурсов

Connect-cachify [16] — это middleware для Node.js, разработанное в Mozilla для того чтобы облегчить конкатенацию и кеширование ресурсов.

В продакшн-режиме connect-cachify отдаёт объединённые и минифицированные ресурсы и устанавливает время их жизни в один года по-умолчанию. В режиме разработки вместо этого ресурсы отдаются поодиночке, что облегчает отладку. Connect-cachify не занимается конкатенацией и минификацией, оставляя это на совести build скрипта.

connect-cachify конфигурируется через вызов функции setup(). Она принимает два аргумента, assets и options. assets — это словарь, устанавливающий соответствие между ресурсами в режимах production и development. Каждому объединённому и минифицированному ресурсу соответствует несколько отдельных для режима разработки.

options — необязательные параметры, среди которых:

  • prefix — строка, пердваряющая хеши в ссылках (по-умолчанию — пустая);
  • production — флаг, переключающий режимы production и development (по-умолчанию — true);
  • root — полный путь к каталогу со статическими ресурсами (по-умолчанию — '.').

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

Предположим, что у нас есть простой HTML, который ссылается на три файла CSS и три отдельных скрипта:

...
<head>
  <title>Dashboard: Hamsters of North America</title>
  <link rel="stylesheet" type="text/css" href="/css/reset.css" />
  <link rel="stylesheet" type="text/css" href="/css/common.css" />
  <link rel="stylesheet" type="text/css" href="/css/dashboard.css" />
</head>
<body>
  ...
  <script type="text/javascript" src="/js/lib/jquery.js"></script>
  <script type="text/javascript" src="/js/magick.js"></script>
  <script type="text/javascript" src="/js/laughter.js"></script>
</body>
...

Подключим connect-cachify к нашему серверу, зададим соответствие между production и development-ресурсами и сконфигурируем модуль:

// Include connect-cachify
const cachify = require('connect-cachify');
 
// Create a map of production to development resources
var assets = {
  "/js/main.min.js": [
    '/js/lib/jquery.js',
    '/js/magick.js',
    '/js/laughter.js'
  ],
  "/css/dashboard.min.css": [
    '/css/reset.css',
    '/css/common.css',
    '/css/dashboard.css'
  ]
};
 
// Hook up the connect-cachify middleware
app.use(cachify.setup(assets, {
  root: __dirname,
  production: your_config['use_minified_assets'],
}));
...

Чтобы не нарушать принцип DRY, карта соответствия ресурсов может храниться в отдельном файле использоваться как для connect-cachify, так и в build скрипте.

Затем обновим наши шаблоны, указав место включения CSS и JavaScript. CSS подключается с помощью хелпера cachify_css, JavaScript, соответственно, cachify_js.

...
<head>
  <title>Dashboard: Hamsters of North America</title>
  <%- cachify_css('/css/dashboard.min.css') %>
</head>
<body>
  ...
  <%- cachify_js('/js/main.min.js') %>
</body>
...

Вывод connect-cachify

Если флаг production установлен в false, connect-cachify сгенерирует три ссылки на CSS и три ссылки на JS, в точности как в исходном HTML выше. Но если он установлен в true, будет создано только по одному тегу. URL в каждом теге будет включать в себя хеш MD5 ресурса. Это делается для принуджительного обновления в случае изменения ресурса. Вот и всё.

...
<head>
  <title>Dashboard: Hamsters of North America</title>
  <link rel="stylesheet" type="text/css" href="/v/2abdd020a6/css/dashboard.min.css" />
</head>
<body>
  ...
  <script type="text/javascript" src="/v/acdd2ab372/js/main.min.js"></script>
</body>
...

Заключение

Многие вещи, которые можно сделать для ускорения сайта, очень просты. Даже самые базовые вещи, наши три «К» — конкатенация, компрессия и кеширование — могут значительно снизить время загрузки страниц и улучшить впечатление пользователей. Connect-cachify помогает автоматизировать конкатенацию и кеширование приложений, но впереди ещё много других улучшений. В следующей статье об ускорении фронтэнда мы расскажем, как кешировать динамический контент с использованием ETag.


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

Автор: ilya42

Источник [17]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/news/44982

Ссылки в тексте:

[1] цикла о Node.js: https://hacks.mozilla.org/category/a-node-js-holiday-season/

[2] Persona: http://ru.wikipedia.org/wiki/Mozilla_Persona

[3] Охотимся за утечками памяти в Node.js: http://habrahabr.ru/company/nordavind/blog/195494/

[4] Нагружаем Node под завязку: http://habrahabr.ru/company/nordavind/blog/195686/

[5] Храним сессии на клиенте, чтобы упростить масштабирование приложения: http://habrahabr.ru/company/nordavind/blog/196018/

[6] SPDY,: http://ru.wikipedia.org/wiki/SPDY

[7] Sprite Cow: http://www.spritecow.com/

[8] UglifyJS: https://github.com/mishoo/UglifyJS

[9] YUICompressor: http://developer.yahoo.com/yui/compressor/

[10] Google Colsure Compiler: https://developers.google.com/closure/compiler/

[11] UglifyCSS: https://github.com/fmarcia/UglifyCSS

[12] работе с PNG: http://feeding.cloud.geek.nz/2011/12/optimising-png-files.html

[13] GIF: http://feeding.cloud.geek.nz/2009/10/reducing-website-bandwidth-usage.html

[14] Smush.it: http://www.smushit.com/ysmush.it/

[15] ImageOptim: http://imageoptim.com/

[16] Connect-cachify: https://github.com/mozilla/connect-cachify/

[17] Источник: http://habrahabr.ru/post/196358/