Как стать автором
Обновить

Все, что вы хотели знать про Qwik — новый фреймворк от создателя Angular

Время на прочтение7 мин
Количество просмотров20K

В начале мая, Misko Hevery, создатель фреймворка Angular, объявил о своем уходе из Google и команды Angular - в компанию builder.io.

Всего через полтора месяца, на его странице в dev.to, появился Анонс нового фреймворка - Qwik.

Я решил разобраться, что он из себя представляет и зачем нужен.

Qwik сейчас на стадии proof of concept, доков очень мало и нет соответствующей инфраструктуры, и даже неясно, выстрелит он или нет, но уже понятны основные идеи, и можно потрогать код.

Давайте разберемся, что интересного он нам принесет, и придется ли нам учить новый фреймворк (спойлер: пока рановато).

Зачем нужен Qwik?

Основная задача Qwik, - как можно догадаться из названия, - быть очень быстрым ​!

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

Большинство современных фреймворков могут, с помощью Server Side Rendering, получить быстрые First Contentful Paint и большинство других метрик, однако одна метрика им пока не покоряется - Time to interactive.

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

Эту проблему и пытается решить Qwik.

Что такое умеет делать Qwik?

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

Работает это вот так:

  • При первом рендере, сервер сериализует все данные в атрибуты HTML узлов

  • Затем подгружается маленький (0.5 кб, 1ms запуск) загрузчик Qwik, который подписывается на все события.

  • Все обработчики событий хранят в себе только текстовую ссылку на файл с обработчиком, никакого кода.

  • Сам код подгружается только по мере необходимости, например когда пользователь запускает событие.

  • Если клиенту нужен доступ к состоянию используемому на сервере, его можно получить из DOM дерева.

Что если у меня плохой инет? Придется ли ждать загрузки кода на каждое действие?

На данный момент - да. В будущем это можно будет оптимизировать, - например, подгружать обработчики кликов при наведении мыши, или загружать заранее наиболее используемые обработчики (как guess.js)

Так ли важен Time to interactive,  это вообще реальная проблема?

Метрика Time to interactive названа не очень удачно, на самом деле она меряет время последней задачи которая заняла более 50 миллисекунд. 

Чтобы понять, насколько она важна, можно глянуть на калькулятор и посмотреть, как именно эта метрика влияет на конечную оценку. 

Хотя вес в оценке всего 10%, можно предположить, что остальные метрики будут легко доводиться до максимальных значений с помощью обычного SSR и, в этом случае, именно Time to interactive может позволить получить преимущество.

Решение такой проблемы, будет наиболее востребовано в интернет магазинах и в коммерческих сайтах, особенно учитывая что у крупнейших игроков рынка результат сейчас в пределах 20-70 из 100.

Свой сайт в можете проверить тут, а Habr.com получил оценку 29.

Про остальные метрики Web Vitals можно прочитать тут

Почему бы просто не проапгрейдить существующий фреймворк?

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

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

Мишко даже выступал на эту тему несколько лет назад по отношению к Angular, но отметил, что он просто размышляет и конкретных планов на имплементацию на тот момент не было.

А нет ли других фреймворков, которые уже решают эту задачу? 

Qwik - не первый фреймворк, который пытается победить Time to Interactive.

Например есть Markojs.com - от Ebay, который пытался решить эту задачу другим способом, но его создатель покинул проект, и сейчас в core team лишь пара человек, которые сфокусированы на поддержке внутри компании. Кстати, один из core разработчиков Marko теперь помогает с Qwik

Также есть Astro.js, но они пытаются решить эту проблему не на уровне обработчиков событий, а на уровне кусков приложения (islands)

Что за название такое?

Фреймворк решили назвать Qwik, по аналогии со словом скорость (quick), как по мне - название не очень удобное: 

  • Плохо гуглится, т.к. Есть несколько компаний с похожим названием

  • Github адрес занят (monorepo лежит в организации builder.io), та же фигня с npm.

Изначально фреймворк хотели называть qoot, но потом переименовали.

Хватит болтать, покажите мне код!

Разработчики выложили TODO приложение, в котором можно покопаться и оценить часть архитектурных решений, глянуть можно в StackBlitz, (ура, отличная возможность потестить новые Web Containers) или на Github.

Весь код, и клиента и сервера, лежит в одном месте и переиспользуется, давайте посмотрим на компонент заголовка - Header:

import { jsxFactory, QRL, injectMethod } from '../qwik.js';
import { HeaderComponent } from './Header_component.js';

export const _needed_by_JSX_ = jsxFactory; // eslint-disable-line @typescript-eslint/no-unused-vars
export default injectMethod(
  HeaderComponent, //
  function (this: HeaderComponent) {
    return (
      <>
        <h1>todos</h1>
        <input
          class="new-todo"
          placeholder="What needs to be done?"
          autofocus
          value={this.$state.text}
          on:keyup={QRL`ui:/Header_addTodo#?value=.target.value&code=.code`}
        />
      </>
    );
  }
);

Первое что бросается в глаза - JSX (что забавно видеть от создателя Angular), с одной стороны это хорошо, потому что синтаксис многим знаком, с другой стороны, это может усложнить жизнь разработчика фреймворка. Скорее всего, по мере развития, когда понадобится дополнительная гибкость, им придется писать свой собственный трансформатор. (Так же см. новую статью @nin-jin про шаблоны, где он ругает JSX)

В каждом шаблоне обязательно присутствует такой код:

export const _needed_by_JSX_ = jsxFactory; // eslint-disable-line @typescript-eslint/no-unused-vars

Но он уйдет, когда разработчики начнут использовать новый JSX transform

Также разработчики не забывают про директивы (как в Angular), например в коде есть такой TODO

// TODO: Create QFor and QIf directive?
<Q for="todos.value" do={(todo) => <Item $item={todo} />} />
<Q if="todos.value.length > 0" then={(value) => <section></section>} />

Обработчики событий

Все обработчики событий лежат в своих собственных файлах, и при добавления событий используется ссылка на этот файл#имя функции:

<input on:keyup={QRL`ui:/Header_addTodo#addTodo?value=.target.value&code=.code`} />

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

  • QRL - Специальный Template Literal Tag, используемый для всех ссылок

  • ui:/ - Namespace, может быть задан пользователем, или прийти из либы, наприме qwik:/ для методов фрейворка.

  • /Header_addTodo - путь к файлу где лежит обаботчик

  • #addTodo - имя функции, которую нудно запустить

  • ?value= - Как и в обычной ссылке можно передать какие-то параметры (query params)

  • .target.value - значение инпута, относительно Event

Сам код обработчика выглядит так, и использует внедрение зависимостей:

export const addTodo = injectEventHandler(
  HeaderComponent,
  provideQrlExp<string>('value'),
  provideQrlExp<string>('code'),
  provideProviderOf(provideEntity(TodoEntity.MOCK_USER)),
  async function (
    this: HeaderComponent,
    inputValue: string,
    charCode: string,
    todoEntity: () => Promise<TodoEntity>
  ) {
    if (charCode === 'Enter' && inputValue) {
      (await todoEntity()).newItem(inputValue);
      this.$state.text = '';
      markDirty(this);
    }
  }
);

Внедрение зависимостей

Dependency Injection в Qwik используется повсеместно (привет из Angular)

Есть несколько методов позволяющих внедрить зависимости, например: 

InjectFunction - Помимо основных зависимостей позволяет получить доступ к параметрам переданным по ссылке (?value=.target.valueв примере выше), для этого можно использовать токен provideQrlExp<string>('value').

injectMethod - (как раз в примере выше) то же самое, только дополнительно внедряет контекст функции (this), это позволяет функциям лежать в отдельных файлах, но при этом вести себя будто они часть класса.

InjectEventListener - то же самое, плюс доступ к значению Event события

Подробнее можно посмотреть в коде.

Сущности/Гидрация

Чтобы переиспользовать код, между клиентом и сервером, Qwik использует умную систему сущностей (Enitity): Каждая сущность, уходящая с севера (например TodoListEntity, или TodoItemEntity) привязана к DOM узлу. Её состояние сериализуется и сохраняется в атрибуте этого узла, привязанном к ID сущности.

Это позволяет восстановить на клиенте сущность, которая была сгенерирована на сервере.

Change Detection

Интересно сделано Change Detection: при изменении данных необходимо вызвать функцию markDirty (прямо как в Angular Ivy), передав туда DOM элемент, Entity или компонент. Функция найдет все затронутые DOM узлы, добавит им атрибут on:q-render и запланирует change detection cycle через requestAnimationFrame .

Во время change detection cycle , узлы, которые нужно обновить, будут получены с помощью простого querySelectorAll('[on:q-render]')

Так как все сущности привязаны к DOM, мы можем пометить сущность как измененную и все соотвествующие DOM  элементы также обновятся

Взаимодействие между компонентами

Передача значений детям:

Передавать значения, как и в большинстве фреймворков, можно через свойства:

<Item $item={item} />

А получать в обработчике через DI:

export const Item = injectFunction(
  provideEntityState<Item>(
    provideComponentProp('$item')
  ),
  function ItemTemplate(item: Item) {
    // code
  }
);

Получение значений от детей

В примере ниже, мы используем функцию emitEvent из Qwik, которая позволяет отправить событие open родительскому компоненту.

Родительский компонент может слушать on:open и запустить соответствующий обработчик

<my-component on:open="./onOpen"">
   <button on:click="base:qwik#emitEvent?$type=open&someArg=someValue">open</button>
</my-component>

С кодом понятно, готово ли это к использованию?

Проект находится в очень ранней стадии, v1 за горами, так что на настоящем проекте использовать это не рекомендуется никому: 

  • Документация минимальная, хотя код неплохо прокоментирован

  • Поддержки IDE нет, а тут она явно нужна (можно глянуть, как подсветка синтаксиса сломана в примерах, в этой статье)

  • Не работает в Firefox 

  • Не используется нигде 

Откуда я знаю, что завтра Qwik не перестанут поддерживать?

Angular поддерживается Google, React поддерживается Facebook, но даже большая компания не позволяет на 100% предсказать судьбу проекта, достаточно глянуть на flow или killedbygoogle

Qwik поддерживается компанией Builder.io, они основаны в 2017 и у них на гитхабе есть несколько небольших проектов, которые они успешно поддерживают сохраняя количество Issues в разумных пределах, например jsx-lite или builder

Смогут ли они справиться с большим проектом, если Qwik выстрелит - покажет время.  

Где rxjs?

Резонный вопрос, видимо пока применения тут не нашлось.

Где я могу узнать больше?

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

Заключение

В этой статье мы рассмотрели идеи, предложенные разработчиками Qwik.

К API и к некоторым техническим решениям есть много вопросов, да и учить новый фреймворк пока рано, а вот задуматься над идеями и перестроить свою ментальную модель - самое время.

Благодарности

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

Я также хочу выразить благодарность ​ всем, кто участвовал в написании, подсказывал и задавал вопросы, ну и большое спасибо всем, кто дочитал это до конца!

Теги:
Хабы:
+19
Комментарии7

Публикации

Истории

Работа

Ближайшие события

Weekend Offer в AliExpress
Дата20 – 21 апреля
Время10:00 – 20:00
Место
Онлайн
Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн