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

Улучшение отрисовки самого большого содержимого в экосистеме JavaScript.

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

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

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

<!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 ожидает, что последний будет представлен в v12.2.

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

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

Другое улучшение включает улучшение метрик First Contentful Paint (FCP) и Largest Contentful Paint (LCP) путем встраивания критического CSS. Критический CSS страницы включает все стили, используемые при ее первоначальном рендеринге. Чтобы узнать больше об этой теме, ознакомьтесь с разделом Defer non-critical CSS .

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

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

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

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

Видео выше показывает рендеринг страницы, которая загружает свои стили асинхронно. Мерцание происходит потому, что браузер сначала начинает загрузку стилей, а затем рендерит HTML, который следует за ними. Как только браузер загрузит стили, он запускает событие onload элемента ссылки, обновляя атрибут 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 {
  /* ... */
}

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

В приведенном выше примере critters прочитает и проанализирует содержимое styles.css , после чего сопоставит два селектора с HTML и обнаружит, что мы используем section button.primary . Наконец critters встроит соответствующие стили в <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 и включена по умолчанию в v12. Если вы используете v11, включите ее, установив свойство inlineCritical в true в angular.json . Чтобы включить эту функцию в Next.js, добавьте experimental: { optimizeCss: true } в ваш next.config.js .

Выводы

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

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