Ускорение рендеринга экрана скелета - Блог xiaOp

внешний интерфейс Vue.js

до«Добавить каркасный экран в проект vue»В этой статье представлена ​​концепция каркасного экрана и его применение в проектах Vue. В этой статье рассказывается, как ускорить рендеринг скелетного экрана в браузере.

Время рендеринга скелетного экрана

Давайте сначала посмотрим на временную шкалу и откроем панель производительности в Chrome Devtool:

Нетрудно обнаружить, что после загрузки HTML браузеру все еще нужно дождаться загрузки стиля (index.css) перед рендерингом скелетного экрана. Это связано с тем, что браузеру нужны DOM и CSSOM для построения дерева рендеринга, поэтому и HTML, и CSS являются ресурсами, блокирующими рендеринг. Это имеет смысл в большинстве сценариев, в конце концов, следует избегать того, чтобы пользователь мог видеть мерцание контента до и после загрузки стиля (FOUC).

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

Первое, что приходит на ум, это<link>от<head>перейти к<body>, спецификация HTML позволяет это:

A <link> tag can occur either in the head element or in the body element (or both), depending on whether it has a link type that is body-ok. For example, the stylesheet link type is body-ok, and therefore a <link rel="stylesheet"> is permitted in the body.

Таким образом, CSS будет блокировать только последующий контент, а каркасный экран не будет затронут.

<head>
    <style>Skeleton CSS</style>
</head>
<body>
    <div>Skeleton DOM</div>
    <link rel='stylesheet' href='index.css'>
    <div id='app'>...</div>
</body>

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

  • Chrome по-прежнему блокирует рендеринг.Webpagetest
  • IE работает как положено и просто блокирует последующий контент.Webpagetest
  • Firefox вообще не блокирует рендеринг, если<head>блокировка произошла в<link>. Это приведет к FOUC для последующего контента.Webpagetest. нуждаться в<link>После добавления пустого<script> </script>Для достижения эффекта блокировки последующего рендеринга контента.

В этом длительном обсуждении разработчики попытались достичьСледующий эффект:

  • любой появляется в<link>Последующее содержимое DOM не будет добавлено в дерево визуализации до тех пор, пока не будет загружена таблица стилей, что блокирует последующую визуализацию.
  • для<link>Увеличиватьasyncсвойства, такие как<script>изdefer/async, не блокирует рендеринг и применяется сразу после загрузки.
  • вставлен JS<link>будет загружаться асинхронно.

Таким образом, разработчики смогут настроить порядок объявления браузера, отобразить содержимое страницы как можно быстрее. Разработчики Jake предложили использовать с HTTP2Сцены:

<body>
  <!-- HTTP/2 push this resource, or inline it, whichever's faster -->
  <link rel="stylesheet" href="/site-header.css">
  <header></header>

  <link rel="stylesheet" href="/article.css">
  <main></main>

  <link rel="stylesheet" href="/site-footer.css">
  <footer></footer>
</body>

Но эта функция до сих пор не реализована в Chrome, и приходится обращаться к другим методам.

Асинхронно загружать таблицы стилей

loadCSSПредусмотрено два способа асинхронной загрузки таблиц стилей:

  1. <link ref='preload'>
  2. обеспечить глобальныйloadCSSметод, динамически загружающий указанную таблицу стилей. Мы будем использовать первый метод, который также рекомендуется loadCSS.

<link ref='preload'>Пусть браузер только запросит загрузку таблицы стилей, но не применит стиль, когда это будет сделано, и он не будет блокировать рендеринг в браузере. Если вы хотите применить стили после завершения загрузки, вы можетеonloadИзменено в функции обратного вызоваrelценностьstylesheet, применяется как обычная блокирующая таблица стилей. Кроме того, посколькуподдержка браузерапроблема с loadCSS при условииpolyfill(используя атрибут media) и перейти на более раннюю версию, если JS не поддерживается. Полный пример выглядит следующим образом:

<link rel="preload" href="path/to/mystylesheet.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="path/to/mystylesheet.css"></noscript>
<script>
/*! loadCSS rel=preload polyfill. [c]2017 Filament Group, Inc. MIT License */
(function(){ ... }());
</script>

Мы применим этот подход в проекте Vue, в котором используются каркасные экраны.

Применить в проекте Vue

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

Главная идея

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

app = new App();
window.mountApp = () => {
    app.$mount('#app');
};
if (window.STYLE_READY) {
    window.mountApp();
}

затем используйте<link ref='preload'>, когда загрузка завершена, если обнаружено, что существует глобальнаяmountApp, выполнить монтирование:

<link rel='preload' href='index.css' as='style' onload='this.onload=null;this.rel='stylesheet';window.STYLE_READY=1;window.mountApp&&window.mountApp();'>

С конкретными идеями давайте рассмотрим проблемы, с которыми можно столкнуться при применении в конкретных проектах. Если вас не волнуют подробности, вы можете перейти в раздел «Конечный эффект».

Использовать с HTMLWebpackPlugin

При создании SPA обычно используетсяHTMLWebpackPlugin, этот плагин генерирует окончательный HTML-код на основе шаблона, переданного разработчиком, когда мы включаемinjectопция автоматически вставляется<link>а также<script>. Для реализации изложенных выше идей требуются некоторые модификации.

Во-первых, в шаблон нам нужно добавить JS и CSS<link ref='preload'>:

<head>
    <% for (var jsFilePath of htmlWebpackPlugin.files.js) { %>
        <link rel="preload" href="<%= jsFilePath %>" as="script">
    <% } %>
    <% for (var cssFilePath of htmlWebpackPlugin.files.css) { %>
        <link rel="preload" href="<%= cssFilePath %>" as="style" onload="this.onload=null;this.rel='stylesheet';window.STYLE_READY=1;window.mountApp&&window.mountApp();">
        <noscript><link rel="stylesheet" href="<%= cssFilePath %>"></noscript>
    <% } %>
    <script>
    /*! loadCSS rel=preload polyfill. [c]2017 Filament Group, Inc. MIT License */
    (function(){ ... }());
    </script>
</head>

Затем, поскольку карта не вставлена<link>, мы можем написать простой подключаемый модуль Webpack, который слушает HTMLWebpackPlugin.мероприятие, чтобы отфильтровать CSS. Таким образом, плагин не будет автоматически вставлен<link>сейчас:

module.exports = class OmmitCSSPlugin {
    constructor() {}
    apply(compiler) {
        compiler.plugin('compilation', (compilation) => {
            compilation.plugin(
                'html-webpack-plugin-alter-asset-tags',
                (args, cb) => {
                    args.head = args.head.filter((link) => link.attributes.rel !== 'stylesheet');
                    cb(null, args);
                }
            );
        });
    }
}

окончательный эффект

После использования этого метода снова посмотрите на временную шкалу и обнаружите, что время рендеринга скелетного экрана увеличилось:image

Наконец, вы можете обратиться кДЕМО-адреса такжеадрес гитхаба.

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

использованная литература