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.