Оптимизировать загрузку ресурсов

В предыдущем модуле мы рассмотрели некоторые теоретические основы критического пути рендеринга и то, как ресурсы, блокирующие рендеринг и парсинг, могут задерживать начальную отрисовку страницы. Теперь, когда вы понимаете некоторые теоретические основы, вы готовы изучить некоторые методы оптимизации критического пути рендеринга.

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

Блокировка рендеринга

Как обсуждалось в предыдущем модуле , CSS является ресурсом , блокирующим отрисовку , поскольку он не позволяет браузеру отображать какой-либо контент до тех пор, пока не будет создана объектная модель CSS (CSSOM) . Браузер блокирует отрисовку, чтобы предотвратить «вспышку нестилизованного контента» (FOUC) , что нежелательно с точки зрения пользовательского опыта.

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

В целом, FOUC — это то, что вы обычно не видите, но важно понимать эту концепцию, чтобы знать, почему браузер блокирует отрисовку страницы до тех пор, пока CSS не будет загружен и применен к странице. Блокировка отрисовки не обязательно нежелательна, но вы хотите свести к минимуму её продолжительность, оптимизируя свой CSS.

Блокировка парсера

Блокирующий парсер ресурс прерывает работу HTML-парсера, например, элемент <script> без атрибутов async или defer . Когда парсер встречает элемент <script> , браузеру необходимо оценить и выполнить скрипт, прежде чем продолжить парсинг остальной части HTML. Это сделано намеренно, поскольку скрипты могут изменять DOM или обращаться к нему во время его построения.

<!-- This is a parser-blocking script: -->
<script src="/script.js"></script>

При использовании внешних JavaScript-файлов (без async или defer — или type=module , где defer по умолчанию) парсер блокируется с момента обнаружения файла до его загрузки, анализа и выполнения. При использовании встроенного JavaScript-кода парсер аналогично блокируется до тех пор, пока встроенный скрипт не будет проанализирован и выполнен.

сканер предварительной нагрузки

Сканер предварительной загрузки — это оптимизация браузера в виде вторичного HTML-парсера, который сканирует необработанный HTML-ответ, чтобы найти и предварительно загрузить ресурсы до того, как основной HTML-парсер их обнаружит. Например, сканер предварительной загрузки позволит браузеру начать загрузку ресурса, указанного в элементе <img> , даже если HTML-парсер заблокирован во время загрузки и обработки таких ресурсов, как CSS и JavaScript.

Для использования сканера предварительной загрузки критически важные ресурсы должны быть включены в HTML-разметку, отправляемую сервером. Следующие шаблоны загрузки ресурсов не обнаруживаются сканером предварительной загрузки:

  • Изображения загружаются с помощью CSS, используя свойство background-image . Эти ссылки на изображения находятся в CSS и не могут быть обнаружены сканером предварительной загрузки.
  • Динамически загружаемые скрипты в виде разметки элемента <script> , внедряемые в DOM с помощью JavaScript, или модули, загружаемые с помощью динамического import() .
  • HTML-код, отображаемый на стороне клиента с помощью JavaScript. Такая разметка содержится в строках ресурсов JavaScript и не обнаруживается сканером предварительной загрузки.
  • Объявления CSS @import .

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

CSS

CSS определяет внешний вид и структуру страницы. Как уже говорилось ранее, CSS является ресурсом, блокирующим отрисовку, поэтому оптимизация CSS может существенно повлиять на общее время загрузки страницы.

Минификация

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

/* Unminified CSS: */

/* Heading 1 */
h1 {
  font-size: 2em;
  color: #000000;
}

/* Heading 2 */
h2 {
  font-size: 1.5em;
  color: #000000;
}
/* Minified CSS: */
h1,h2{color:#000}h1{font-size:2em}h2{font-size:1.5em}

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

Удалите неиспользуемый CSS

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

Чтобы обнаружить неиспользуемые CSS-стили на текущей странице, воспользуйтесь инструментом «Покрытие кода» в инструментах разработчика Chrome.

Скриншот инструмента анализа покрытия кода в инструментах разработчика Chrome. В нижней панели выбран CSS-файл, на котором видно значительное количество CSS-кода, не используемого текущей разметкой страницы.
Инструмент анализа покрытия кода в инструментах разработчика Chrome полезен для обнаружения CSS (и JavaScript), не используемых текущей страницей. Он позволяет разделить CSS-файлы на несколько ресурсов, загружаемых разными страницами, вместо того, чтобы отправлять гораздо больший пакет CSS, который может задерживать отрисовку страницы.

Удаление неиспользуемого CSS имеет двойной эффект: помимо сокращения времени загрузки, вы оптимизируете построение дерева рендеринга , поскольку браузеру нужно обрабатывать меньше правил CSS.

Избегайте объявлений CSS @import

Хотя это может показаться удобным, следует избегать объявлений @import в CSS:

/* Don't do this: */
@import url('style.css');

Подобно тому, как в HTML работает элемент <link> , объявление @import в CSS позволяет импортировать внешний ресурс CSS из таблицы стилей. Главное различие между этими двумя подходами заключается в том, что элемент <link> в HTML является частью HTML-ответа и, следовательно, обнаруживается гораздо раньше, чем файл CSS, загруженный с помощью объявления @import .

Причина в том, что для обнаружения объявления @import необходимо сначала загрузить содержащий его CSS-файл. Это приводит к так называемой цепочке запросов , которая — в случае CSS — задерживает первоначальную отрисовку страницы. Другой недостаток заключается в том, что таблицы стилей, загруженные с помощью объявления @import не могут быть обнаружены сканером предварительной загрузки и, следовательно, становятся ресурсами, блокирующими отрисовку с задержкой.

<!-- Do this instead: -->
<link rel="stylesheet" href="style.css">

В большинстве случаев вы можете заменить директиву @import элементом <link rel="stylesheet"> . Элементы <link> позволяют загружать таблицы стилей одновременно и сокращают общее время загрузки, в отличие от объявлений @import , которые загружают таблицы стилей последовательно .

Встроенный критический CSS

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

<head>
  <title>Page Title</title>
  <!-- ... -->
  <style>h1,h2{color:#000}h1{font-size:2em}h2{font-size:1.5em}</style>
</head>
<body>
  <!-- Other page markup... -->
  <link rel="stylesheet" href="non-critical.css">
</body>

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

CSS-демонстрации

JavaScript

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

Блокирующий отрисовку JavaScript

При загрузке элементов <script> без атрибутов defer или async браузер блокирует анализ и отрисовку до тех пор, пока скрипт не будет загружен, проанализирован и выполнен. Аналогично, встроенные скрипты блокируют работу парсера до тех пор, пока скрипт не будет проанализирован и выполнен.

async против defer

async и defer позволяют загружать внешние скрипты, не блокируя HTML-парсер, в то время как скрипты (включая встроенные скрипты) с type="module" автоматически откладываются. Однако async и defer есть некоторые важные различия, которые необходимо понимать.

Изображение различных механизмов загрузки скриптов, подробно описывающее роли парсера, выборки и выполнения в зависимости от используемых атрибутов, таких как async, defer, type='module' и их комбинация.
Источник: https://html.spec.whatwg.org/multipage/scripting.html

Скрипты, загружаемые с помощью async анализируются и выполняются немедленно после загрузки, тогда как скрипты, загружаемые с помощью defer выполняются после завершения анализа HTML-документа — это происходит одновременно с событием DOMContentLoaded в браузере. Кроме того, скрипты async могут выполняться в произвольном порядке, тогда как скрипты defer выполняются в том порядке, в котором они указаны в разметке.

Рендеринг на стороне клиента

Как правило, следует избегать использования JavaScript для рендеринга критически важного контента или элемента LCP страницы. Это называется рендерингом на стороне клиента и широко используется в одностраничных приложениях (SPA).

Разметка, генерируемая JavaScript, обходит сканер предварительной загрузки, поскольку ресурсы, содержащиеся в разметке, генерируемой клиентом, для него недоступны . Это может задержать загрузку важных ресурсов, таких как изображение LCP. Браузер начинает загрузку изображения LCP только после выполнения скрипта и добавления элемента в DOM. В свою очередь, скрипт может быть выполнен только после того, как он будет обнаружен, загружен и проанализирован. Это называется цепочкой критических запросов , и этого следует избегать.

Кроме того, рендеринг разметки с помощью JavaScript с большей вероятностью приведет к длительным задачам , чем рендеринг разметки, загружаемой с сервера в ответ на запрос навигации. Интенсивное использование клиентского рендеринга HTML может негативно повлиять на задержку взаимодействия . Это особенно актуально в случаях, когда DOM страницы очень велик , что вызывает значительную работу по рендерингу, когда JavaScript изменяет DOM.

Минификация

Подобно CSS, минификация JavaScript уменьшает размер файла скрипта. Это может привести к более быстрой загрузке, позволяя браузеру быстрее перейти к процессу анализа и компиляции JavaScript.

Кроме того, минификация JavaScript идёт на шаг дальше, чем минификация других ресурсов, таких как CSS. При минификации JavaScript удаляются не только пробелы, табуляции и комментарии, но и сокращаются символы в исходном коде JavaScript. Этот процесс иногда называют облегчением (uglification) . Чтобы увидеть разницу, рассмотрим следующий исходный код JavaScript:

// Unuglified JavaScript source code:
export function injectScript () {
  const scriptElement = document.createElement('script');
  scriptElement.src = '/js/scripts.js';
  scriptElement.type = 'module';

  document.body.appendChild(scriptElement);
}

После компиляции приведенного выше исходного кода JavaScript результат может выглядеть примерно так:

// Uglified JavaScript production code:
export function injectScript(){const t=document.createElement("script");t.src="/js/scripts.js",t.type="module",document.body.appendChild(t)}

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

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

Демонстрации на JavaScript

Проверьте свои знания

Как лучше всего загрузить несколько CSS-файлов в браузере?

Объявление CSS @import .
Попробуйте еще раз.
Несколько элементов <link> .
Правильный!

Что делает сканер предварительной загрузки браузера?

Это вторичный HTML-парсер, который анализирует необработанную разметку для обнаружения ресурсов до того, как это сможет сделать DOM-парсер, чтобы обнаружить их быстрее.
Правильный!
Обнаруживает элементы <link rel="preload"> в HTML-ресурсе.
Попробуйте еще раз.

Почему браузер по умолчанию временно блокирует анализ HTML-кода при загрузке ресурсов JavaScript?

Чтобы предотвратить внезапное появление нестилизованного контента (FOUC).
Попробуйте еще раз.
Поскольку выполнение JavaScript-кода — задача, требующая значительных вычислительных ресурсов процессора, приостановка анализа HTML-кода позволяет процессору быстрее завершить загрузку скриптов.
Попробуйте еще раз.
Потому что скрипты могут изменять DOM или иным образом получать к нему доступ.
Правильный!

Далее: Помощь браузеру с подсказками о ресурсах

Теперь, когда вы разобрались, как ресурсы, загружаемые в элементе <head> , могут влиять на первоначальную загрузку страницы и различные показатели, пора двигаться дальше. В следующем модуле мы рассмотрим подсказки о ресурсах и то, как они могут давать браузеру ценные указания начать загрузку ресурсов и устанавливать соединения с междоменными серверами раньше, чем это произошло бы без них.