Предварительная загрузка модулей

Qwik предоставляет различные стратегии предварительной загрузки модулей, и Qwik City, опираясь на них, может пойти ещё дальше и использовать спекулятивное получение модулей. Эта страница документации описывает низкоуровневые возможности предварительной загрузки в Qwik. Однако для более полного понимания рекомендуется обратиться к документации Qwik City: Спекулятивное получение модулей.

Предварительная загрузка модулей — это способ для приложений начать загрузку модулей в фоновой задаче до того, как эти модули действительно потребуются пользователю. Идеальным решением является предварительная загрузка минимального количества кода, который с большой вероятностью потребуется для взаимодействия с пользователем, избегая при этом JavaScript, который не будет использоваться.

Только загрузка и выполнение минимального количества JavaScript — это та область, в которой приложения Qwik преуспевают. А поскольку Qwik может понять, как используются отдельные компоненты (и какие не используются), он может наилучшим образом решить, какие фрагменты должны быть предварительно загружены.

Помните, что разница между возобновляемостью и гидратацией заключается в том, что возобновляемость позволяет приложениям Qwik избегать выполнения JavaScript для восстановления прослушивателей событий, дерева компонентов и состояния приложения. Благодаря фундаментальному разбиению компонентов на слушатели событий, функции рендера и состояние, объём кода для предварительной загрузки становится значительно меньше, чем при традиционном подходе.

Сбор используемых символов

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

Например, рассмотрим страницу товара, которая в основном статична, за исключением одной кнопки "Добавить в корзину". При нажатии на эту кнопку пользователь должен немедленно получить обратную связь, чтобы показать товар, добавленный в корзину. В этом примере оптимизатор Qwik сможет понять, что единственный символ, с которым пользователь может взаимодействовать, - это обработчик события нажатия кнопки "Добавить в корзину".

Для нашего примера с добавлением товара в корзину оптимизатор будет собирать символы только для обработчика события клика и для рендера виджета «Добавить в корзину». Однако ему не нужно было бы загружать, обрабатывать и повторно отображать какие-либо другие части приложения, поскольку не было необходимости повторно отображать другие разделы страницы.

Итак, Qwik понимает, что он может предварительно загрузить только код обработчика событий, а не весь JavaScript приложения или маршрута. Это отличается от традиционного подхода, при котором всё приложение или маршрут, а также код фреймворка должны быть предварительно загружены только для добавления обработчика события клика.

prefetchStrategy

Стратегия предварительной загрузки - это логика, которая решает, какой JavaScript, если он есть, Qwik должен предварительно загрузить в фоновом режиме. По умолчанию, Qwik будет предварительно загружать все обработчики, видимые на странице. Для настройки стратегии предварительной загрузки используйте параметры функции renderToStream(), встречающейся в файле src/entry.ssr.tsx. Обеспечение оптимальных стратегий предварительной загрузки - это область, в которой Qwik будет продолжать исследования и эксперименты.

Для приложений Qwik City мы настоятельно рекомендуем использовать спекулятивное получение модулей.

export default function (opts: RenderToStreamOptions) {
  return renderToStream(<Root />, {
    manifest,
    prefetchStrategy: {
      // конфигурация предварительной загрузки
    },
    ...opts,
  });
}

Реализация

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

export default function (opts: RenderToStreamOptions) {
  return renderToStream(<Root />, {
    manifest,
    prefetchStrategy: {
      implementation: {
        // конфигурация предварительной загрузки
      },
    },
    ...opts,
  });
}

Для приложений Qwik City мы настоятельно рекомендуем использовать спекулятивное получение модулей, которая использует реализацию prefetchEvent.

ПараметрОписание
prefetchEventОтправка события qprefetch с данными detail, содержащими URL, которые должны быть предварительно загружены. Скрипт диспетчеризации событий будет вставлен в HTML документа. По умолчанию для реализации prefetchEvent будет установлено значение always.
linkInsertВставка в документ элемента <link>. При использовании опции html-append, каждый элемент <link> будет рендериться непосредственно в html, в конец секции <body>. При использовании опции js-append вместо этого будет вставлен JavaScript, который создаст элементы во время выполнения и добавит их в конец секции <body>.
linkRelЭта опция используется для определения атрибута rel элемента <link>. При использовании опции linkInsert по умолчанию вставляется атрибут prefetch. Также можно выбрать атрибут preload и modulepreload.
workerFetchInsertПредварительная загрузка URL путем вызова fetch() для каждого модуля, с целью заполнения сетевого кэша.

Диспетчеризация события предварительной загрузки

Спекулятивное получение модулей - это предпочтительная стратегия кэширования, используемая Qwik City. Эта стратегия слушает событие qprefetch, которое отправляется фреймворком Qwik. Событие содержит список URL-адресов, которые фоновый поток должен использовать для предварительного заполнения кэша браузера.

Qwik должен быть настроен на использование реализации prefetchEvent, которая будет отправлять событие qprefetch. По умолчанию для реализации prefetchEvent будет установлено значение always. Далее Qwik City будет слушать это событие и общаться со своим сервис-воркером для сохранения пар объектов Запрос / Ответ, для их кэширования в долговременной памяти.

Используя сервис-воркер для перехвата запросов fetch из браузера, этот подход позволяет осуществлять детальный контроль над кэшированием, а также предотвращать дублирование запросов к одному и тому же ресурсу.

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

dispatchEvent(new CustomEvent("qprefetch", { detail: {
  bundles: [...]
}}));

Использование элемента <link> с атрибутом rel является распространенным подходом в современных фреймворках, и Qwik может использовать этот метод, с настройками опций linkInsert и linkRel. Проблема с подходом относительных ссылок заключается в отсутствии поддержки на всех устройствах, по крайней мере, на момент написания статьи. Кроме того, во время разработки может ввести в заблуждение факт, что это работает везде, тогда как на самом деле на мобильных устройствах нелегко добиться правильной работы предварительной загрузки.

Например, Safari (браузер, которым оснащены iPhone и iPad) не поддерживает тип modulepreload. Это важно, поскольку мобильные устройства могут извлечь наибольшую пользу из предварительной загрузки модулей. Следующим является Firefox, который не поддерживает относительные ссылки с типом prefetch при использовании https.

Предварительная загрузка — функция, которая должна помочь увеличить скорость отклика сайта, но при неправильном сочетании браузера и CDN/сервера она может замедлить работу!

- Rel=prefetch and the Importance of Effective HTTP/2 Prioritisation

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

  • Даже если это указано в спецификации HTML, это не означает, что конечные пользователи правильно загружают ваше приложение. Can I Use: modulepreload;
  • Не поддерживается Firefox.

Загрузка веб-воркером

workerFetchInsert инструктирует Qwik использовать веб-воркер для получения функцией fetch() файла JavaScript и загрузил модуль в кэш браузера. При использовании веб-воркера логика получения и кэширования находится в другом потоке. Ответ также будет содержать заголовок immutable или заголовок cache-control с большим сроком, чтобы браузер не делал повторный сетевой запрос.

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

Часто задаваемые вопросы по предварительной загрузке

ВОПРОС: Является ли ленивая загрузка медленной, потому что пользователь должен ждать загрузки кода?

Да, это создаст заметную задержку, особенно в медленных сетях 3G. Именно поэтому предварительная загрузка кода является важной частью приложений Qwik.

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

ВОПРОС: Разве предварительная загрузка кода не приводит к такому же поведению, как у существующих фреймворков, которые жадно загружают и выполняют весь код?

Нет, по нескольким причинам:

  • Существующие фреймворки должны загрузить и выполнить весь код (гидратации), прежде чем приложение станет интерактивным. Как правило, загрузка кода требует меньших временных затрат, чем его выполнение.
  • Предварительная загрузка кода Qwik только загружает, но не выполняет код. Поэтому даже если Qwik загружает такой же объем кода, как и существующие фреймворки, результатом является значительная экономия времени и средств.
  • Qwik выполняет предварительную загрузку только того кода, который необходим для текущей страницы. Qwik избегает загрузки кода, связанного с компонентами, которые являются статичными. В худшем случае Qwik набирает такой же объем кода, как и в лучшем случае у существующих фреймворков. В большинстве случаев Qwik выполняет предварительную загрузку небольшой части кода по сравнению с существующими фреймворками.
  • Предварительная загрузка кода может происходить не в основном потоке, а в других. Многие браузеры могут даже предварительно разобрать AST-код вне основного потока.
  • Если взаимодействие с пользователем происходит до завершения предварительной загрузки, браузер автоматически определяет приоритет взаимодействующего фрагмента перед остальными фрагментами предварительной загрузки.
  • Qwik может разбить приложение на множество небольших фрагментов, и эти фрагменты могут быть загружены в порядке вероятности того, что пользователь будет взаимодействовать с ними. Существующие фреймворки с трудом разбивают приложения на небольшие фрагменты, и нет простого способа определить очередность загрузки фрагментов, потому что гидратация требует наличия единой "главной" точки входа в приложение.

ВОПРОС: Кто отвечает за выбор, какой именно код нужно предварительно загрузить?

Qwik может автоматически генерировать инструкции предварительной загрузки как часть рендеринга SSR. Выполняя приложение, Qwik получает информацию о том, какие компоненты видны, какие события могут вызывать пользователи и какой код необходимо загрузить. Результатом является предварительная загрузка идеального набора файлов для данной страницы. Никаких действий со стороны разработчиков не требуется, кроме добавления стратегии предварительного получения в renderToStream().

Участники

Спасибо всем участникам, которые помогли сделать эту документацию лучше!

  • adamdbradley
  • RATIU5
  • manucorporat
  • literalpie
  • saikatdas0790
  • the-r3aper7
  • mhevery
  • mrhoodz
  • thejackshelton
  • maiieul