Встраивание ресурсов в фреймворки JavaScript

Улучшение крупнейшей отрисовки контента в экосистеме JavaScript.

В рамках проекта Aurora компания Google работала с популярными веб-фреймворками, чтобы обеспечить их хорошую производительность согласно данным Core Web Vitals . В Angular и Next.js уже реализована встраивание шрифтов, о чем рассказывается в первой части этой статьи. Вторая оптимизация, которую мы рассмотрим, — это критическая встраивание CSS, которая теперь включена по умолчанию в Angular CLI и находится в стадии разработки в Nuxt.js.

Встраивание шрифтов

Проанализировав сотни приложений, команда Aurora обнаружила, что разработчики часто включают шрифты в свои приложения, ссылаясь на них в элементе <head> файла index.html . Вот пример того, как это будет выглядеть при включении значков материалов:

<!doctype html>
<html lang="en">
<head>
  <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
  ...
</html>

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

/* fallback */
@font-face {
  font-family: 'Material Icons';
  font-style: normal;
  font-weight: 400;
  src: url(https://fonts.gstatic.com/font.woff2) format('woff2');
}

.material-icons {
  /*...*/
}

Обратите внимание, что определение font-face ссылается на внешний файл, размещенный на fonts.gstatic.com . При загрузке приложения браузер сначала должен загрузить исходную таблицу стилей, указанную в заголовке.

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

Затем браузер загружает файл woff2 и, наконец, может приступить к рендерингу приложения.

Изображение, показывающее два сделанных запроса: один для таблицы стилей шрифта, второй для файла шрифта.
Далее делается запрос на загрузку шрифта.

Возможность оптимизации — загрузить исходную таблицу стилей во время сборки и встроить ее в index.html . Это пропускает весь путь к CDN во время выполнения, сокращая время блокировки.

При создании приложения в CDN отправляется запрос, который извлекает таблицу стилей и встраивает ее в файл HTML, добавляя <link rel=preconnect> в домен. Применив эту технику, мы получим следующий результат:

<!doctype html>
<html lang="en">
<head>
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin >
  <style type="text/css">
  @font-face{font-family:'Material Icons';font-style:normal;font-weight:400;src:url(https://fonts.gstatic.com/font.woff2) format('woff2');}.material-icons{/*...*/}</style>
  ...
</html>

Встраивание шрифтов теперь доступно в Next.js и Angular.

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

Это улучшение включено по умолчанию в Next.js v10.2 и Angular v11. Оба поддерживают встраивание шрифтов Google и Adobe. Angular планирует представить последний в версии 12.2.

Вы можете найти реализацию встраивания шрифтов в Next.js на GitHub , а также посмотреть видео, объясняющее эту оптимизацию в контексте Angular .

Встраивание критического CSS

Еще одно улучшение включает в себя улучшение показателей «Первая отрисовка контента» (FCP) и «Наибольшая отрисовка контента» (LCP) путем встраивания критического CSS. Критический CSS страницы включает в себя все стили, использованные при ее первоначальном рендеринге. Чтобы узнать больше об этой теме, ознакомьтесь с статьей «Отложить некритичный CSS» .

Мы заметили, что многие приложения загружают стили синхронно, что блокирует рендеринг приложения. Быстрое решение — асинхронная загрузка стилей. Вместо загрузки сценариев с помощью media="all" установите для атрибута media значение print , а после завершения загрузки замените значение атрибута на all :

<link rel="stylesheet" href="..." media="print" onload="this.media='all'">

Однако такая практика может привести к мерцанию неоформленного контента.

Страница мерцает при загрузке стилей.

На видео выше показан рендеринг страницы, стили которой загружаются асинхронно. Мерцание происходит потому, что браузер сначала начинает загрузку стилей, а затем отображает последующий HTML. Как только браузер загружает стили, он запускает событие onload элемента link, обновляя атрибут media до all и применяя стили к DOM.

В период между отрисовкой HTML и применением стилей страница частично не имеет стилей. Когда браузер использует стили, мы видим мерцание, что неприятно для пользователя и приводит к регрессии в накопительном сдвиге макета (CLS) .

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

Давайте посмотрим на пример:

Не
<head>
   <link rel="stylesheet" href="/styles.css" media="print" onload="this.media='all'">
</head>
<body>
  <section>
    <button class="primary"></button>
  </section>
</body>
/* styles.css */
section button.primary {
  /* ... */
}
.list {
  /* ... */
}

Пример перед встраиванием.

В приведенном выше примере твари будут читать и анализировать содержимое styles.css , после чего они сопоставляют два селектора с HTML и обнаруживают, что мы используем section button.primary . Наконец, твари встроят соответствующие стили в <head> страницы, в результате чего:

Делать
<head>
  <link rel="stylesheet" href="/styles.css" media="print" onload="this.media='all'">
  <style>
  section button.primary {
    /* ... */
  }
  </style>
</head>
<body>
  <section>
    <button class="primary"></button>
  </section>
</body>

Пример после встраивания.

После встраивания критического CSS в HTML вы обнаружите, что мерцание страницы исчезло:

Загрузка страницы после встраивания CSS.

Критическая встраивание CSS теперь доступно в Angular и включено по умолчанию в версии 12. Если вы используете версию 11, включите ее, установив для свойства inlineCritical значение true в angular.json . Чтобы включить эту функцию в Next.js, добавьте experimental: { optimizeCss: true } в ваш next.config.js .

Выводы

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

Узнайте больше об улучшениях . Полный список работ по оптимизации, которые мы проделали для Core Web Vitals, можно найти в статье «Введение в Aurora» .