Опубликовано: 23 ноября 2024 г.
Разработка на основе модулей предлагает некоторые реальные преимущества с точки зрения кэширования, помогая вам сократить количество байтов, которые необходимо отправить пользователям. Более высокая степень детализации кода также помогает при загрузке, позволяя вам расставить приоритеты для критического кода в вашем приложении.
Однако зависимости модулей создают проблему с загрузкой: браузеру приходится ждать загрузки модуля, прежде чем он узнает, каковы его зависимости. Одним из способов решения этой проблемы является предварительная загрузка зависимостей, чтобы браузер заранее знал обо всех файлах и мог поддерживать соединение занятым.
<link rel="preload">
<link rel="preload">
— это способ декларативного запроса ресурсов заранее, до того, как они потребуются браузеру.
<head>
<link rel="preload" as="style" href="critical-styles.css">
<link rel="preload" as="font" crossorigin type="font/woff2" href="myfont.woff2">
</head>
Это особенно хорошо работает с такими ресурсами, как шрифты, которые часто спрятаны внутри файлов CSS, иногда на несколько уровней. В этой ситуации браузеру придется ждать несколько циклов обработки, прежде чем он обнаружит, что ему нужно получить большой файл шрифта, тогда как он мог бы использовать это время для начала загрузки и использования всей пропускной способности соединения.
<link rel="preload">
и его эквивалент HTTP-заголовка предоставляют простой декларативный способ немедленного уведомления браузера о важных файлах, которые понадобятся в рамках текущей навигации. Когда браузер видит предварительную загрузку, он начинает загрузку ресурса с высоким приоритетом, так что к моменту, когда он действительно понадобится, он либо уже загружен, либо частично доступен. Однако это не работает для модулей.
Почему <link rel="preload">
не работает для модулей?
Здесь все становится сложнее. Существует несколько режимов учетных данных для ресурсов, и чтобы получить попадание в кэш, они должны совпадать, иначе вам придется получать ресурс дважды. Излишне говорить, что двойная выборка — это плохо, поскольку она тратит впустую полосу пропускания пользователя и заставляет его ждать дольше без веской причины.
Для тегов <script>
и <link>
вы можете установить режим учетных данных с помощью атрибута crossorigin
. Однако оказывается, что <script type="module">
без атрибута crossorigin
указывает режим учетных данных omit
, которого не существует для <link rel="preload">
. Это означает, что вам придется изменить атрибут crossorigin
как в <script>
, так и <link>
на одно из других значений, и у вас может не быть простого способа сделать это, если то, что вы пытаетесь предварительно загрузить, является зависимостью. других модулей.
Более того, получение файла — это только первый шаг на пути к фактическому запуску кода. Сначала браузер должен его проанализировать и скомпилировать. В идеале это также должно произойти заранее, чтобы, когда модуль понадобится, код был готов к запуску. Однако V8 (движок JavaScript Chrome) анализирует и компилирует модули иначе, чем другой JavaScript. <link rel="preload">
не предоставляет никакого способа указать, что загружаемый файл является модулем, поэтому все, что может сделать браузер, — это загрузить файл и поместить его в кеш. После загрузки скрипта с помощью тега <script type="module">
(или другого модуля) браузер анализирует и компилирует код как модуль JavaScript.
Так является ли <link rel="modulepreload">
просто <link rel="preload">
для модулей?
В двух словах, да. Имея определенный тип link
для предварительной загрузки модулей, мы можем писать простой HTML, не беспокоясь о том, какой режим учетных данных мы используем. Значения по умолчанию просто работают.
<head>
<link rel="modulepreload" href="super-critical-stuff.mjs">
</head>
[...]
<script type="module" src="super-critical-stuff.mjs">
А поскольку браузер теперь знает, что вы предварительно загружаете модуль, он может действовать умно, анализировать и скомпилировать модуль сразу после завершения загрузки, вместо того, чтобы ждать, пока он попытается запуститься.
А как насчет зависимостей модулей?
Забавно, что вы спрашиваете! Действительно, есть кое-что, чего не было затронуто в этой статье: рекурсия.
Спецификация <link rel="modulepreload">
фактически позволяет загружать не только запрошенный модуль, но и все его дерево зависимостей. Браузеры не обязаны это делать, но они могут.
Итак, какое кроссбраузерное решение будет лучшим для предварительной загрузки модуля и его дерева зависимостей, поскольку для запуска приложения вам понадобится полное дерево зависимостей?
Браузеры, которые выбирают рекурсивную предварительную загрузку зависимостей, должны иметь надежную дедупликацию модулей, поэтому, как правило, лучше всего объявить модуль и плоский список его зависимостей и доверять браузеру, чтобы он не извлекал один и тот же модуль дважды.
<head>
<!-- dog.js imports dog-head.js, which in turn imports
dog-head-mouth.js, which imports dog-head-mouth-tongue.js. -->
<link rel="modulepreload" href="dog-head-mouth-tongue.mjs">
<link rel="modulepreload" href="dog-head-mouth.mjs">
<link rel="modulepreload" href="dog-head.mjs">
<link rel="modulepreload" href="dog.mjs">
</head>
Повышает ли предварительная загрузка модулей производительность?
Предварительная загрузка может помочь максимально эффективно использовать полосу пропускания, сообщая браузеру, что ему нужно получить, чтобы он не застрял без дела во время этих долгих обходов. Если вы экспериментируете с модулями и сталкиваетесь с проблемами производительности из-за глубоких деревьев зависимостей, создание плоского списка предварительных загрузок определенно может помочь.
Тем не менее, над производительностью модуля все еще ведется работа, поэтому обязательно внимательно посмотрите, что происходит в вашем приложении с помощью инструментов разработчика, и рассмотрите возможность объединения вашего приложения на несколько частей. Однако в Chrome ведется большая работа над модулями, так что мы приближаемся к тому, чтобы дать сборщикам заслуженный отдых!