Возобновление vs. Гидратация

Ключевая концепция приложений Qwik заключается в том, что их можно возобновлять из состояния рендера на стороне сервера. Лучший способ объяснить возобновляемость — понять, каким образом это делают фреймворки текущего поколения (гидратация).

Когда SSR/SSG-приложение загружается на клиенте, оно требует, чтобы фреймворк восстановил три составляющих части:

  1. Слушатели — найти слушатели событий и вставить их в узлы DOM, чтобы сделать приложение интерактивным.
  2. Дерево компонентов — создать внутреннюю структуру данных, представляющую дерево компонентов приложения.
  3. Состояние приложения - восстановление любых данных, которые были получены или сохранены в store на сервере.

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

Гидратация стоит дорого по двум причинам:

  1. Фреймворки должны загрузить весь код компонента, связанный с текущей страницей.
  2. Фреймворки должны выполнять шаблоны, связанные с компонентами на странице, чтобы перестроить местоположение слушателя и внутреннее дерево компонента.
Resumable vs Hydration

Qwik отличается тем, что не требует гидратации для возобновления работы приложения на клиенте. Отсутствие необходимости гидратации — вот что делает запуск приложения Qwik мгновенным.

Гидратация всех других фреймворков воспроизводит всю логику приложения на клиенте. Вместо этого Qwik приостанавливает выполнение на сервере и возобновляет выполнение на клиенте.

Введение в возобновляемость

Возобновляемость — это приостановка выполнения на сервере и возобновление выполнения на клиенте без повторного воспроизведения и загрузки всей логики приложения.

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

Чтобы достичь этого, Qwik должен решить 3 проблемы (слушатели, дерево компонентов, состояние приложения) таким образом, чтобы иметь возможность запуска без кода.

Слушатели

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

Существующие фреймворки решают проблему слушателей событий путем загрузки компонентов и выполнения их шаблонов для сбора слушателей событий, которые затем прикрепляются к DOM. Текущий подход имеет следующие проблемы:

  1. Код шаблона должен быть предварительно загружен;
  2. Код шаблона должен быть предварительно выполнен;
  3. Код обработчика события должен быть предварительно загружен (чтобы быть прикреплённым).

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

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

Qwik решает эту проблему, сериализуя слушателей событий в DOM следующим образом:

<button on:click="./chunk.js#handler_symbol">Нажми меня</button>

Qwik по-прежнему нуждается в сборе информации о слушателях, но этот шаг выполняется как часть SSR/SSG. Результат SSR/SSG сериализуется в HTML, так что браузеру не нужно ничего делать для возобновления выполнения. Обратите внимание, что атрибут on:click содержит всю информацию для возобновления работы приложения, не выполняя предварительных операций.

  1. Qwikloader устанавливает один глобальный слушатель вместо множества отдельных слушателей для каждого элемента DOM. Этот шаг может быть выполнен без кода приложения.
  2. HTML содержит URL-адрес чанка и имя символа. Эти атрибуты сообщают Qwikloader-у, какой файл загрузить и по какому ключу извлечь из него код, а затем выполнить.
  3. Наконец, чтобы сделать всё вышеперечисленное возможным, реализация обработки событий в Qwik поддерживает асинхронность, что позволяет ей автоматически лениво загружать замыкания.

Деревья компонентов

Фреймворки работают с деревьями компонентов. Для этого фреймворкам необходимо полное понимание деревьев компонентов, чтобы знать, какие компоненты и когда требуют рендера. Если вы посмотрите на вывод существующих SSR/SSG фреймворков, то увидите, что информация о границах компонентов будет уничтожена. Нет никакого способа узнать, где находятся границы компонента, глядя на результирующий HTML. Чтобы воссоздать эту информацию, фреймворки повторно выполняют шаблон компонента и запоминают местоположение границ компонента. Повторное выполнение - вот что такое гидратация. Гидратация является дорогостоящей, поскольку требует как загрузки шаблонов компонентов, так и их выполнения.

Qwik собирает информацию о границах компонента как часть SSR/SSG и затем сериализует эту информацию в HTML. В результате Qwik может:

  1. Перестроить информацию об иерархии компонента без фактического присутствия кода компонента. Код компонента может оставаться ленивым;
  2. Qwik может сделать это отложенно, и только для тех компонентов, которые нуждаются в ререндере, а не для всех сразу;
  3. Qwik собирает информацию о взаимоотношениях между состояниями данных и компонентов. Это создаёт модель подписки, которая информирует Qwik о том, какие компоненты должны быть отрендерены в результате изменения состояния. Информация о подписке также сериализуется в HTML.

Состояние приложения

Существующие фреймворки обычно имеют способ сериализации состояния приложения в HTML, так что состояние может быть восстановлено в рамках гидратации. В этом они очень похожи на Qwik. Однако в Qwik управление состоянием более тесно интегрировано в жизненный цикл компонентов. На практике это означает, что компонент может быть лениво загружен независимо от его состояния. Этого нелегко достичь в существующих фреймворках, поскольку параметры компонентов обычно создаются родительским компонентом. Это создает цепную реакцию. Для того чтобы восстановить компонент X, необходимо восстановить и его родителей. Qwik позволяет возобновить работу любого компонента без присутствия кода родительского компонента.

Сериализация

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

Ограничения JSON, которые решает Qwik:

  • JSON производит DAG. DAG (Directed Acyclic Graph) - направленный ациклический граф, что означает, что сериализуемый объект не может иметь циклических ссылок. Это большое ограничение, поскольку состояние приложения часто является циклическим. Qwik гарантирует, что при сериализации графа объектов циклические ссылки будут правильно сохранены, а затем восстановлены.
  • JSON не может сериализовать некоторые типы объектов. Например, DOM-ссылки, или даты. Формат сериализации Qwik гарантирует, что такие объекты могут быть корректно сериализованы и восстановлены. Вот список типов, которые можно сериализовать с помощью Qwik:
    • Ссылки на DOM;
    • Промисы (см. ресурсы);
    • Замыкания функций (если обёрнуты в QRL).
    • Даты;
    • Объекты URL;
    • Экземпляры объектов Map и Set.

Ограничения JSON, которые Qwik не решает:

  • Сериализация классов (instanceof и прототип);
    • Хотя некоторые встроенные классы поддерживаются сериализацией: Date, URL, Map, Set.
  • Сериализация потоков.

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

Написание приложений с учетом сериализации

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

Разработчики должны писать свои приложения, ориентируясь на DOM. Это потребует изменения подхода и навыков веб-разработчиков. Фреймворки должны предоставлять руководства и API, чтобы разработчикам было легко писать приложения таким образом.

Другие преимущества возобновляемости

Наиболее очевидным преимуществом использования возобновляемости является рендер на стороне сервера. Однако, есть и второстепенные преимущества:

  • Сериализация существующих PWA-приложений, чтобы пользователи не теряли контекст при возвращении к приложению;
  • Улучшенная производительность рендера, поскольку только изменённые компоненты нуждаются в ререндере;
  • Фрагментарная ленивая загрузка;
  • Снижение нагрузки на память, особенно на мобильных устройствах;
  • Прогрессивная интерактивность существующих статичных веб-сайтов.

Участники

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

  • voluntadpear
  • RATIU5
  • adamdbradley
  • manucorporat
  • Craiqser
  • ahashem95
  • mhevery
  • aboudard
  • mrhoodz
  • iancharlesdouglas