В мае 2021 года вышел новый frontend-фреймворк Qwik. Его создал Misko Hevery, разработчик фреймворка Angular. Qwik сразу наделал шума: Misko обещал быстрый старт приложения и почти моментальную возможность интерактивного взаимодействия.
Спустя год после первого релиза разбираемся, облегчает ли Qwik работу, вместе с Игорем Кацубой, ведущим frontend-разработчиком в Tinkoff.
Зачем нужен Qwik
По заявлению создателей, архитектура фреймворка позволяет ускорить загрузку сайтов вне зависимости от их сложности. Это очень важно: конверсия посещений в продажи и вовлеченность пользователей напрямую зависит от скорости загрузки сайта. По данным Google, медленный сайт может снизить вероятность возвращения потенциальных клиентов на 60%. Больше того, в прошлом году компания стала учитывать скорость загрузки для ранжирования сайта в поисковой выдаче.
По замыслу Misko, Qwik должен достигать лучшего time-to-interactive, а значит, сильно ускорить загрузку приложения. Другие современные фреймворки работают по принципу «загрузи большой JavaScript бандл» и «только потом запусти приложение». Этот большой кусок кода должен сперва запуститься, выполнить работу и отрисовать интерфейс.
Для примера, ниже представлены первые 500 миллисекунд маленького приложения на Angular из одного (!) компонента. Видно, что за это время успел скачаться пустой HTML и загрузились основные скрипты весом около 200 Кб. До первой отрисовки контента здесь еще 0,2 секунды, а до интерактивности –– 0,4 секунды.
Выполнение скриптов и начало рендера занимает как минимум полсекунды. Как видно, время до первого взаимодействия сильно затягивается.
Фреймворк призван решить и другие проблемы, которые есть у существующих решений. Вот основные недостатки, с которыми борется Qwik:
Строгая иерархия компонентов. Современные фреймворки рендерят компоненты в строгой последовательности –– от родителя к ребенку. Нельзя, например, пропустить рендер родительского компонента и отрисовать только его дочек.
SSR и SSG. Чтобы улучшить метрики FCP (первая отрисовка компонента), frontend-фреймворки применяют техники SSR или SSG –– серверный рендер или статическую генерацию сайта.
В обоих случаях браузер загружает готовый HTML и показывает контент, не дожидаясь загрузки скриптов на JS. До загрузки пользователь видит страницу, но часто не может с ней взаимодействовать. Не хватает определенных обработчиков, может не работать роутинг, а формы невозможно отправить.
И SSR, и SSG направлены на улучшение FCP, но не способны сильно снизить время до первого взаимодействия. В текущем виде они облегчают жизнь только приложениям, которым важна индексация поисковыми роботами.Регидрация. Следующий этап работы в случае привычных фреймворков –– попытка восстановить состояние приложения и регидрировать. При ней будут добавлены недостающие обработчики и отрисованные элементы, которые не рисуются на сервере. После этого приложение становится интерактивным.
Так, например, работает React. Алгоритм замедляет загрузку сайтов: на регидрацию нужно лишнее время, а значит, первое взаимодействие с ресурсом происходит позже.
С Angular дела еще хуже – он умеет восстанавливать состояние, но не умеет переиспользовать DOM. При загрузке фреймворк заново отрисовывает все элементы. Процессорное время, потраченное на рендеринг страницы на сервере, практически обесценивается.
Как Qwik пытается решить проблемы фреймворков
Просто улучшить существующие решения не получится. Их архитектура изначально не позволяет реагировать на события без инициализации самого фреймворка и приложения. Но Qwik отличается от них в выгодном ключе –– вот его преимущества:
Ранняя интерактивность. Фреймворк работает так: сперва на клиент загружается подготовленный на сервере HTML. Чтобы оживить приложение, достаточно загрузить маленький рантайм Qwik (~1kb). Он подписывается на все браузерные события, и приложение становится интерактивным.
Для примера рассмотрим первые 500 миллисекунд простого приложения TodoMVC на Qwik. За это время браузер успел скачать уже подготовленный на сервере HTML, спарсить и отрисовать его. Затем –– исполнить маленький runtime, который заинлайнен в HTML и какое-то время бездельничать.
Вкладка Lighthouse показывает, что время до первой отрисовки и интерактивности у этого приложения равны 0,2 секунды. Приложение на Angular за это время не успело выполнить даже скрипты. Если сравнить скриншот ниже с аналогичным выше, то сразу бросается в глаза разница в количестве работы скриптов.Нет регидрации. Благодаря особой архитектуре Qwik не нужна регидрация — один из самых сложных моментов перед стартом приложения.
Возобновляемость. Qwik –– возобновляемый фреймворк. Все дело в сериализации состояния в HTML. Благодаря ей на клиенте работа приложения продолжится с того момента, на котором остановилась на сервере.
Нет строгой иерархии компонентов. При рендере пользователю не нужно грузить родительский компонент. Например, при итерации Qwik сразу загрузит компонент кнопки. Это еще одно преимущество сериализации состояния в HTML.
Загрузка кода при необходимости. Если элемент еще не понадобился пользователю, он не грузится. Например, та же кнопка активируется только при взаимодействии с пользователем. Для эффективного разделения кода на lazy-чанки в Qwik написан Optimizer на Rust. Он пытается сохранить баланс между функциональностью и удобством для разработчика. Благодаря этому runtime у QWIK впечатляет –– загрузка практически моментальная.
Какие проблемы есть у самого Qwik
Несмотря на все преимущества, недостатков у нового фреймворка тоже хватает. Вот основные:
Реализация Optimizer
Это тот самый кусок кода в сборке, который отвечает за «ленивость» –– загрузку кода при необходимости. Видите знак «$» в коде приложения и функцию напротив него? Скорее всего, функция будет вынесена в отдельный чанк.
Рассмотрим пример с компонентом Counter:// оригинальный Counter.jsx
const Counter = component$(() => {
const store = useStore({ count: 0 });
return <button onClick$={() => store.count++}>{store.count}</button>;
});
Optimizer превратит его в четыре разные функции. Они будут загружаться лениво, каждая в своем случае:// Counter.jsxconst Counter = component(qrl('./chunk-a.js', 'Counter_onMount'));
// chunk-a.jsxexport const Counter_onMount = () => { const store = useStore({ count: 0 });return qrl('./chunk-b.js', 'Counter_onRender', [store]);};
// chunk-b.jsxconst Counter_onRender = () => {const [store] = useLexicalScope();return (<button onClickQrl={qrl('./chunk-c.js', 'Counter_onClick', [store])}>{store.count}</button>);};
// chunk-c.jsxconst Counter_onClick = () => {const [store] = useLexicalScope();return store.count++;};
Магия, достойная Angular. Выглядит и работает хорошо, но, например, в параметр onClick$ функцию по ссылке уже передать не получиться. Это то, что просится наружу даже при первом знакомстве с фреймворком.
Нет комьюнити. Фреймворк пока молодой: он даже не получил первую минорную версию, я уже не говорю о первой мажорной.
Нет экосистемы. Вокруг современных фреймворков экосистемы росли годами. Уже давно выработаны лучшие практики и приемы работы с роутингом, хранением состояния, созданием библиотек. У Qwik все еще впереди.
Чего ждать от фреймворка в будущем
Пока разработчикам не хватает от Qwik хорошей документации. В текущей из раздела в раздел перечисляются киты, на которых стоит фреймворк. Функциональность почти не раскрыта. Например, упоминаются некие containers, которые будут хороши в роутинге и микро frontend. Ни примеров, ни разъяснений нет, даже на уровне proof of concept.
Проблемы кодогенерации пока тоже пугают. Непонятно, какой отпечаток это может наложить на DX и гибкость. По опыту с Angular длительное время на кодогенерацию может вызвать серьезные проблемы. Кроме того, сильно не хватает хорошего production-ready приложения на Qwik: to-do лист не в счет.
Документацию проекта можно посмотреть здесь, а связаться с командой проекта –– тут. Несмотря на инновационность и преимущества фреймворка, его будущее туманно. Оно зависит от того, сможет ли маховик Qwik раскрутиться –– будут ли новые версии и хорошая документация, а еще привлечет ли фреймворк новые проекты. Нам остается только тестировать Qwik в деле и задуматься над инновационными идеями, которые он продвигает.