Директивы prefetch и preload в webpack

в 8:48, , рубрики: javascript, webpack, Блог компании RUVDS.com, разработка, Разработка веб-сайтов

В webpack 4.6.0. появилась поддержка директив prefetch и preload (они выглядят, соответственно, как «магические комментарии» webpackPrefetch и webpackPreload к командам import()). С их помощью браузеру можно давать подсказки о ресурсах, которые могут понадобиться пользователю в недалёком будущем. Браузер заблаговременно загружает такие ресурсы, что позволяет улучшить впечатление пользователя от работы с сайтом.

image

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

Что такое <link rel=«prefetch»>?

Директива prefetch сообщает браузеру о том, что указанный ресурс, возможно, понадобится в будущем для перемещения по сайту.

Браузеры обычно загружают подобные ресурсы в то время, когда они не заняты другими задачами. После загрузки полученный ресурс оказывается в HTTP-кэше, готовый к использованию при обработке будущих запросов. Если нужно организовать предварительное получение множества ресурсов, такие запросы ставятся в очередь, их выполнение осуществляется, когда браузер находится в режиме простоя. Выходя из этого режима, браузер может прервать текущую загрузку prefetch-ресурса (и поместить частичный результат в кэш для того, чтобы позже вернуться к его загрузке с использованием заголовков Content-Range) и остановить очередь обработки prefetch-запросов.

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

Что такое <link rel=«preload»>?

Директива preload сообщает браузеру о том, что ресурс, отмеченный ей, обязательно понадобится в данном сеансе работы с сайтом, но обращение к нему будет произведено немного позже. При этом Chrome даже выводит предупреждение, когда подобный ресурс не используется через 3 секунды после загрузки.

Обычно браузеры загружают такие ресурсы, давая им средний приоритет (такая загрузка не блокирует пользовательский интерфейс).

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

О пользе директив prefetch и preload

Директива preload позволяет заблаговременно загружать ресурсы, которые скоро понадобятся, и избегать «эффекта водопада», когда, всё, что нужно для вывода страницы, начинает загружаться сразу же после щелчка по соответствующей ссылке. Эта технология позволяет разбить загрузку страницы на два этапа, когда сначала загружается HTML-код, а потом — всё остальное. При этом подобное не означает дополнительной нагрузки на сетевой канал.

Подсказка prefetch используется во время простоя браузера для ускорения будущих взаимодействий с сайтом. Её использование может означать необходимость в дополнительных расходах трафика в том случае, если пользователь не воспользуется загруженными ресурсами.

Разделение кода

Тут предполагается, что речь идёт об огромном приложении, подготовленном средствами webpack с применением механизма загрузки по запросу (команда import()) для загрузки только тех частей приложения, которые нужны пользователю в настоящий момент.

В качестве примера мы рассмотрим домашнюю страницу (HomePage), на которой есть кнопка для входа в систему (LoginButton), открывающая модальное окно для ввода учётных данных (LoginModal). После того, как пользователь войдёт в систему, ему показывают страницу панели управления (DashboardPage). На этой странице могут присутствовать другие кнопки, нужные для перехода к другим страницам, но мы ориентируемся на то, что целью пользователя является именно эта страница.

Для того чтобы достичь наилучшей производительности, на странице с кнопкой для входа в систему имеется команда import("LoginModal"), а на странице для ввода учётных данных — команда import("DashboardPage").

Теперь оказывается, что наше экспериментальное приложение разбито, как минимум, на три части: домашнюю страницу, страницу ввода учётных данных, и страницу панели управления. При первоначальной загрузке нужно показать пользователю лишь домашнюю страницу. Она загружается быстро, что даёт нам полное право ожидать того, что у пользователя будут хорошие впечатления от работы с этой страницей. Однако когда пользователь щёлкает по кнопке входа в систему, он заметит некоторую задержку, необходимую для вывода окна входа в систему, так как, прежде чем пользователь увидит это окно, данную часть приложения ещё надо загрузить. То же самое касается и страницы панели управления. На данном этапе у нас возникает вопрос о том, как улучшить впечатления пользователя от работы с сайтом.

Использование в webpack prefetch-подсказок

Благодаря тому, что теперь в webpack можно использовать prefetch-подсказки, мы можем улучшить вышеописанный сценарий работы с сайтом. Сделать это очень просто. А именно, нужно, на странице с кнопкой входа, использовать, вместо команды import("LoginModal"), команду import(/* webpackPrefetch: true */ "LoginModal"). Аналогично, на странице для ввода учётных данных, вместо команды import("DashboardPage") нужно использовать конструкцию import(/* webpackPrefetch: true */ "DashboardPage").

Эти команды сообщат webpack о том, что он должен загрузить, после загрузки основной страницы (во время простоя браузера), указанные фрагменты приложения. В данном примере, после загрузки домашней страницы, в очередь добавляется загрузка страницы входа в систему. А когда завершится загрузка страницы входа в систему (настоящая загрузка, а не предварительная, вызванная подсказкой webpackPrefetch), в очередь загрузок будет поставлен фрагмент приложения, представляющий страницу панели управления.

Исходя из того, что точкой входа в приложение является домашняя страница, на основе вышеописанных команд на HTML-странице будет сформирована ссылка вида <link rel="prefetch" href="login-chunk.js">. Фрагмент приложения, представляющий страницу для входа в систему, загружается по требованию, поэтому webpack позаботится о том, чтобы внедрить в страницу тег вида <link rel="prefetch" href="dashboard-chunk.js"> после завершения загрузки страницы входа в систему.

Это благотворно повлияет на впечатления пользователя от работы с сайтом. А именно, тут можно будет отметить следующие улучшения:

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

Обратите внимание на то, что выше мы описали идеальный вариант развития событий.

Пользователь не всегда будет видеть страницы, которые предполагается загружать в фоновом режиме, мгновенно. Существует масса факторов, которые могут на это повлиять. Среди них — медленные соединения с интернетом, пользователи, которые очень быстро щёлкают по соответствующим кнопкам, отключённая обработка prefetch-подсказок на устройствах, которые ориентированы на экономию трафика, отсутствие поддержки данной возможности в браузерах, очень медленная обработка загружаемых страниц сайта и так далее.

Использование в webpack preload-подсказок

Выше мы говорили о конструкции import(/* webpackPrefetch: true */ "...") для описания ресурсов, которые предполагается загружать во время простоя браузера. Существует аналогичная команда, import(/* webpackPreload: true */ "..."), для описания preload-ресурсов. Если сравнить два этих подхода, можно выявить множество различий:

  • Preload-ресурс начинает загружаться параллельно с родительским ресурсом. Prefetch-ресурс загружается только после того, как завершится загрузка родительского ресурса.
  • Preload-ресурс имеет средний приоритет, загружается он немедленно. Prefetch-ресурс загружается во время простоя браузера.
  • Preload-ресурс должен быть очень быстро запрошен родительской страницей. Prefetch-ресурс может быть использован в будущем в любое время.
  • Браузеры по-разному поддерживают эти механизмы.

Как результат, prefetch-подсказки используются редко. Например, они могут понадобиться в том случае, если модуль немедленно импортирует что-либо командой import(). Это может иметь смысл, если, например, некий компонент зависит от большой библиотеки, которая должна присутствовать в отдельном фрагменте приложения. Например, компонент для вывода диаграмм, ChartComponent, использует большую библиотеку для работы с диаграммами — ChartingLibrary. Компонент, во время загрузки, показывает индикатор загрузки (LoadingIndicator) и немедленно выполняет команду import(/* webpackPreload: true */ "ChartingLibrary"). В результате, когда запрашивается загрузка страницы, которая использует ChartComponent, запрашивается, с помощью конструкции <link rel="preload">, и загрузка фрагмента приложения с библиотекой. Если исходить из предположения, что страница с компонентом меньше, чем фрагмент приложения с библиотекой, и её загрузка завершится быстрее, эта страница выведется с индикатором загрузки, который будет отображаться до окончания загрузки фрагмента приложения с библиотекой. Это немного уменьшит время загрузки всего необходимого, так как система выполняет загрузки параллельно, не разбивая этот процесс на два отдельных действия. Особенно это будет заметно там, где сетевые соединения характеризуются высокой задержкой.

Надо сказать, что неправильное применение конструкции webpackPreload может ухудшить производительность, поэтому пользоваться ей нужно осмотрительно.

Если вы размышляете о том, какой именно механизм вам нужен — preload или prefetch, вам, вероятно, лучше будет воспользоваться prefetch. При этом вышесказанное справедливо для команды import() webpack. На обычных HTML-страницах вам, вероятно, больше подойдёт preload.

Загрузка множества prefetch-ресурсов

При необходимости на странице можно использовать сколько угодно команд import() с директивой webpackPrefetch. Однако стоит учитывать то, что все загружаемые таким образом ресурсы будут бороться за полосу пропускания.

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

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

К счастью, это возможно, хотя это и выходит за пределы базовых вариантов использования prefetch и preload.

Так, вместо применения конструкции webpackPrefetch: true, можно, вместо логического значения, использовать число. В результате webpack будет загружать фрагменты в заданном порядке. Например, ресурс с webpackPrefetch: 42 будет загружен перед ресурсом с webpackPrefetch: 1, который, в свою очередь, будет загружен ранее ресурса с webpackPrefetch: true, а этот ресурс будет загружен до ресурса с webpackPrefetch: -99999 (это напоминает z-order). На самом деле true здесь — это то же самое, что и 0.

Похожим образом можно работать и с webpackPreload, но вам это вряд ли понадобится.

Часто задаваемые вопросы

  • Что если несколько команд import() запросят загрузку одного и того же ресурса, при этом в них будут использоваться и подсказки prefetch, и подсказки preload?

Директива preload имеет приоритет перед prefetch, а prefetch используется в том случае, если больше никаких указаний по поводу конкретного ресурса у webpack нет. Если используются несколько команд на предварительную загрузку одного и того же ресурса, срабатывает та из них, которой назначен более высокий приоритет.

  • Что если механизмы prefetch и preload не поддерживаются в браузере?

В подобной ситуации браузер игнорирует эти подсказки, а webpack не сделает попытку воспользоваться каким-нибудь резервным механизмом. На самом же деле, prefetch и preload не случайно называют «подсказками», поэтому если даже браузер их поддерживает, он может, по какой-то причине, их проигнорировать.

  • Директивы prefetch и preload на некоей странице, являющейся точкой входа в сайт, не работают. В чём может быть дело?

Среда выполнения webpack воспринимает подсказки prefetch и preload только для ресурсов, загружаемых по требованию. Когда prefetch или preload используются в командах import() во фрагментах сайта, являющихся входными точками, система генерирования HTML ответственна за добавление тегов <link> в получаемый код.

Соответствующие справочные данные доступны в entrypoints[].childAssets.

  • Почему бы не использовать директивы prefetch и preload в каждой команде import()?

Это приведёт к нерациональному использованию полосы пропускания интернет-канала. Кроме того, больше пользы можно извлечь из выборочного применения этих директив для команд import(), относящихся к ресурсам, которые понадобятся пользователю с высокой долей вероятности. Не стоит зря нагружать интернет-каналы, ведь у некоторых пользователей они далеко не безлимитные. Поэтому используйте данные директивы только тогда, когда они действительно нужны.

  • Мне не нужна эта возможность. Каковы минусы отказа от её использования?

Соответствующий код в среде выполнения webpack добавляется только в том случае, если директивы prefetch и preload используются для работы с материалами, загружаемыми по требованию. В результате, если вы этой возможностью не пользуетесь, никакой дополнительной нагрузки на систему не создаётся.

  • У меня уже есть «магический комментарий» в команде import(). Можно ли в неё добавлять несколько таких комментариев?

Да, это возможно. Комментарии надо либо разделить запятой, либо просто использовать несколько отдельных комментариев:

import(
  /* webpackChunkName: "test", webpackPrefetch: true */
  "LoginModal"
)
// или
import(
  /* webpackChunkName: "test" */
  /* webpackPrefetch: true */
  "LoginModal"
)
// переводы строк необязательны

  • Я создал библиотеку с применением rollup, и, кроме того, использую команды import(). Могут ли пользователи моей библиотеки, работающие с webpack, извлечь преимущества из использования prefetch?

Да, можно добавить «магические комментарии» webpack в команды import() и rollup их сохранит. Если готовый проект собирают с помощью webpack, он воспользуется этими комментариями. Если webpack применяться не будет, они просто будут удалены минификатором.

  • Я не уверен в том, что добавление подсказки prefetch к некоей команде import() улучшит производительность. Стоит ли мне добавлять эту подсказку?

Лучше всего — измерить производительность, воспользовавшись методикой A/B-тестирования. В применении к подобной ситуации нельзя дать однозначного универсального совета. Это зависит от вероятности того, что пользователю понадобится посетить некую часть приложения.

  • У меня уже есть сервис-воркер, который, при загрузке, кэширует всё, что нужно приложению. Имеет ли смысл в такой ситуации использовать prefetch?

Это зависит от приложения. В целом, подобные сервис-воркеры загружают все ресурсы приложения не заботясь о порядке, в котором производится загрузка. В то же время, директивы prefetch позволяют указывать порядок загрузки ресурсов, они зависят от места в приложении, в котором находится пользователь. Кроме того, механизм prefetch экономнее относится к полосе пропускания и загружает данные только во время простоя браузера.

Измерьте время, необходимое для полной загрузки приложения с использованием низкоскоростного канала связи, сопоставьте это с тем, когда пользователю понадобится выполнить первый переход в приложении. Если приложение очень велико, в нём имеет смысл рассмотреть использование механизма prefetch, а не кэширование всего приложения за один заход.

  • Мне хотелось бы разделить ресурсы, загружаемые при первой загрузке страницы, на жизненно важные, и на ресурсы второстепенного значения, и загружать их в соответствии с уровнем их значимости. Пригодится ли в подобной ситуации механизм preload?

Да, жизненно важные ресурсы можно поместить в точку входа в приложение, а второстепенные — загрузить с помощью import() с webpackPreload: true. И не забудьте добавить <link rel="preload"> для ресурсов, необходимых для правильной работы точки входа в приложение (перед тегами для дочерних элементов).

Итоги

В этом материале мы рассказали о новшестве webpack, позволяющем использовать директивы prefetch и preload. Их правильное применение помогает улучшить впечатления пользователя от работы с сайтом, а неправильное может привести к ненужным затратам трафика, что особенно актуально для случаев, когда работа с веб-ресурсом ведётся на устройстве, возможности которого ограничены. Надеемся, новые возможности webpack пригодятся вам в деле создания быстрых и удобных веб-проектов.

Уважаемые читатели! Видите ли вы практические ситуации, в которых вам могут пригодиться директивы prefetch и preload?

Директивы prefetch и preload в webpack - 2

Автор: ru_vds

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js