QRL
QRL (Qwik URL) - это особая форма URL, которую Qwik использует для ленивой загрузки содержимого.
QRL-ы:
- Это форматированные специальным образом URL-адреса, которые вставляются в HTML в виде атрибутов, чтобы указать Qwik, откуда должны быть загружены обработчики кода;
- Указывают на фрагменты JavaScript-кода для ленивой загрузки;
- Содержат имя символа, который необходимо извлечь из фрагмента;
- Могут содержать ссылки на объекты с лексической областью (переменные, захваченные из замыканий);
- Если QRL относительный, то для разрешения полного пути используйте
q:base
.
QRL-кодирование
./path/to/chunk.js#SymbolName
В своей простейшей форме QRL содержит URL-адрес (вроде ./path/to/chunk.js
), который браузер может использовать для ленивой загрузки ресурса, и SymbolName
для извлечения из лениво загруженного чанка.
Qwik использует q:base
для преобразования QRL в абсолютный URL-адрес, если URL-адрес является относительным (если атрибут q:base
отсутствует, то в качестве базы используется document.baseURI
).
Кодирование захваченных переменных с лексической областью видимости
QRL-адреса также могут восстанавливать переменные с лексической областью видимости. В этом случае переменные кодируются в конце QRL в виде массива индексов атрибута q:obj
.
./path/to/chunk.js#SymbolName[0,1]
Массив используется функцией useLexicalScope()
для восстановления значений переменных.
Пример
Давайте рассмотрим пример того, как части QRL связаны друг с другом.
Разработчик пишет код простого компонента.
export const Counter = component$((props: { step: number }) => {
const count = useSignal(0);
return <button onClick$={() => (count.value += props.step || 1)}>{count.value}</button>;
});
Оптимизатор разбивает приведенный выше код на части следующим образом:
const Counter = component(qrl('./chunk-a.js', 'Counter_onMount'));
export const Counter_onMount = (props) => {
const count = useSignal(0);
return qrl('./chunk-b.js', 'Counter_onRender', [count, props]);
};
const Counter_onRender = () => {
const [count, props] = useLexicalScope();
return (
<button onClick$={qrl('./chunk-c.js', 'Counter_onClick', [count, props])}>{count.value}</button>
);
};
const Counter_onClick = () => {
const [count, props] = useLexicalScope();
return (count.value += props.step || 1);
};
Рендер HTML
Предположим, текущий маршрут - http://localhost/index.html
После выполнения приведённого выше кода получается такой HTML:
<html>
<body q:base="/build/">
<button q:obj="456, 123" on:click="./chunk-c.js#Counter_onClick[0,1]">0</button>
<script>
/*Qwikloader script*/
</script>
<script type="qwik/json">
{...json...}
</script>
</body>
</html>
Главное, на что следует обратить внимание, это атрибут on:click
. Этот атрибут считывается Qwikloader-ом, когда пользователь нажимает на кнопку.
- HTML загружается в браузер, и Qwikloader регистрирует глобальный слушатель
click
. Никакой другой JavaScript не загружается/выполняется в этот момент. - Пользователь кликает на
<button>
. Это вызывает событиеclick
, которое всплывает и обрабатывается Qwikloader-ом. - Qwikloader прослеживает путь всплытия события и ищет атрибут
on:click
, который он находит на<button>
. - Теперь Qwikloader пытается загрузить соответствующий чанк. Для этого Qwikloader должен разрешить относительный путь
./chunk-c.js
. Он использует эти значения для построения абсолютного пути, начинающегося в<button>
и идущего к документу.on:click="./chunk-c.js#Counter_onClick[0,1]"
<body q:base="/build/">
document.baseURI = "http://localhost/index.html"
- В результате получается абсолютный URL
http://localhost/build/chunk-c.js
, по которому Qwikloader забирает данные.
- Qwikloader теперь получает ссылку на функцию
Counter_onClick
изhttp://localhost/build/chunk-c.js
и вызывает её.const Counter_onClick = () => { const [count, props] = useLexicalScope(); return (count.value += props.step || 1); };
- В этот момент выполнение передается из Qwikloader-а в лениво загруженный чанк. Это делается для того, чтобы Qwikloader был как можно меньше, поскольку он встраивается в HTML.
useLexicalScope
импортируется из@builder.io/qwik
и отвечает за получениеcount
иprops
.const [count, props] = useLexicalScope();
- Происходит парсинг JSON
<script type="qwik/json">{...json...}</script>
и распределение десериализованных объектов по атрибутуq:obj
. В нашем случае:<div q:id="123" q:obj="456" q:host>
получает объект с идентификатором123
. Это будетcount
, созданный в функцииCounter_onMount
.<button q:obj="456, 123"
получаетcount
, а также ссылку на<div q:id="457">
.
- После десериализации
qwik/json
,useLexicalScope
может использовать массив QRL[0,1]
для поиска вq:obj="456, 123"
, откуда получает объекты с id456
и123
, являющиеся параметрами<div q:id="123" q:obj="456" q:host>
, а такжеstore
из функцииCounter_onMount
.
ПРИМЕЧАНИЕ: По соображениям производительности
q:obj
и<script type="qwik/json">
обновляются только при десериализации приложения в HTML. Когда приложение работает, эти атрибуты могут иметь устаревшие значения.
import()
?
Почему просто не динамический Браузеры уже имеют механизм динамического импорта, доступный через import()
. Почему бы не использовать его вместо того, чтобы изобретать новый формат QRL?
На это есть несколько причин:
- Для работы Qwik необходимо, чтобы QRL были сериализованы в HTML. Это проблематично для динамического
import()
, поскольку нет простого способа получить относительный URL из выраженияimport('./some-path.js')
, чтобы его можно было вставить в HTML. - Динамический импорт имеет дело с чанками, но у него нет механизма для ссылки на конкретный фрагмент в чанке.
- Динамический импорт имеет относительный путь к файлу, который импортируется. Это проблема, потому что когда относительный путь помещается в HTML, он теряет контекст, определяющий, что относительно чего. Когда фреймворк считывает путь из HTML и пытается импортировать его как
import(element.getAttribute('on:click'))
, фреймворк становится контекстом для разрешения относительного пути. Это проблема, поскольку исходный контекст перед сериализацией в HTML был другим. - QRL кодируют информацию о переменных с лексической областью действия, которые были захвачены в замыкании и должны быть восстановлены.
- Динамический импорт требует, чтобы разработчик написал
import('./file-a.js')
, что означает, что разработчик отвечает за определение границ отложенной загрузки. Это ограничивает возможности инструментария для автоматизированного перемещения кода.
Из-за вышеуказанных различий Qwik вводит QRL-адреса как механизм для ленивой загрузки замыканий в приложение Qwik.