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

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

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

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

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

В предыдущем видео есть краткий 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 ) анализатор блокируется с момента обнаружения файла до его загрузки, анализа и выполнения. При использовании встроенного 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 DevTools.

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

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

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

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

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

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

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

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

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

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

Время, необходимое для загрузки файлов CSS, может увеличить FCP страницы. Встраивание критически важных стилей в документ <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. Этот процесс иногда называют уродством . Чтобы увидеть разницу, возьмите следующий исходный код 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> , могут влиять на начальную загрузку страницы и различные показатели, пришло время двигаться дальше. В следующем модуле рассматриваются подсказки ресурсов и то, как они могут дать браузеру ценные подсказки, позволяющие начать загрузку ресурсов и открытие соединений с серверами перекрестного происхождения раньше, чем браузер мог бы без них.