Symbiote.js VS LitElement

ea7ad685ff7e2f79ffbceb5cb3f40b28.png

Мотивация разработчиков библиотек и фреймворков для фронтенда может быть разной. И если вы, являясь таким разработчиком, хотите работать не «в стол», а с расчетом на какое-то признание и пользу для индустрии, вы должны четко понимать, что именно и зачем вы делаете. Если вы хотите сэкономить пользователям пару килобайт трафика или пару миллисекунд отклика — вам будет очень тяжело доказать миру, что ради этого стоит выбрать именно ваше решение. Люди выберут размер комьюнити, богатую экосистему и крупного вендора. Ваш набор аргументов должен быть достаточно веским, чтобы обратить на себя внимание. Сейчас я попытаюсь доказать, что при наличии такого решения как LitElement от гиганта индустрии Google, имеет смысл посмотреть в сторону Symbiote.js.

Почему сравнение именно с LitElement?

LitElement — это бесспорный лидер среди библиотек, основанных на веб-компонентах. Принципиальная разница таких либ, со всеми остальными, в том, что они используют нативную браузерную компонентную механику (современный DOM API), а не собственный дополнительный рантайм. Это позволяет экономить ресурсы пользователей и создавать более универсальные решения. Я очень давно слежу за развитием группы стандартов Web Components, c момента их зарождения. И не просто слежу, а активно участвую в ее внедрении. LitElement — это логическое продолжение проекта Polymer, а тот, в свою очередь, начинался как адоптация черновика стандарта. Очень много воды утекло с тех пор. Из эксперимента энтузиастов, все превратилось в набор принятых стандартов, поддержка которых, давно реализована во всех современных браузерах.

Как и LitElement, Symbiote.js — это, также, библиотека для создания веб-компонентов, но с уклоном в организацию их последующего взаимодействия.  

Трекшн

За последний год, число еженедельных скачиваний пакета npm для Symbiote.js, увеличилось с 500 до 3000. То есть, мы видим 6-кратный стабильный рост популярности. Если принять во внимание тот факт, что продвижением Симбиота пока никто активно не занимался, то, наверное, это неплохой результат. В любом случае, с выходом уже 2-й мажорной версии библиотеки, на продвижение планируется потратить гораздо больше сил.

У LitElement, рост составил от 700 000 скачиваний до, почти, 1 400 000. Масштаб, конечно, несопоставимый. Но кривая роста существенно более пологая и, как мне кажется, отражает рост популярности веб-компонентов в целом. Но, двукратный рост популярности это отличный результат на таких масштабах.

К слову, у, безусловно, более популярного, на текущий момент, React роста не наблюдается, практически, совсем.

Лицензия

У Симбиота лицензия MIT. У LitElement — BSD-3-Clause. Эти лицензии довольно близки, но в версии BSD-3-Clause, дополнительно указано ограничение на использование имен авторов и брендов. Я нашел неплохой разбор нюансов этих двух типов лицензирования: тут (https://opensource.stackexchange.com/questions/217/what-are-the-essential-differences-between-the-bsd-and-mit-licences)

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

Размер

По размеру Symbiote.js выигрывает, но не очень значительно. ~ 5.13 KB (gzip) у Симбиота, против ~ 6.85 KB (gzip) у LitElement. И то и другое можно справедливо отнести к компактным решениям.

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

Shadow DOM

Я знаю, что работа с Shadow DOM вызывает трудности у многих разработчиков. Это очень классная технология, но она бывает плохо совместима с привычными практиками работы со стилями.

Помимо этого, существует, как минимум, три подхода к Shadow DOM:

  1. Когда теневая разметка есть у всех компонентов приложения и стили добавляются в shadow root каждого.

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

  3. Когда Shadow DOM не используется вообще и работают все классические приемы работы с CSS (общий CSS на документ).

По умолчанию, Symbiote НЕ использует Shadow DOM. Для включения теневого режима, необходим дополнительный флаг (renderShadow) или добавление стилей через специальный интерфейс shadowStyles. Это позволяет избежать лишнего оверхеда по производительности и существенно упрощает работу со стилями в классическом стиле, с возможностью использования имен кастомных тегов в качестве удобных селекторов. 

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

LitElement использует Shadow DOM для всех компонентов по умолчанию. Это поведение можно отключить, но функции определения внешнего верхнеуровневого теневого корня и добавления стилей в него, у него нет.

И Симбиот и LitElement умеют работать с новым нативным браузерным интерфейсом adoptedStyleSheets, который является частью CSSOM API.

Шаблоны

Декларативные шаблоны Симбиота — это просто HTML. Это позволяет использовать, для их описания, html-файлы, использовать лоадеры для прямого импорта в ваш код или оживлять, предварительно сформированный на сервере, документ. 

В Symbiote.js, как и в LitElement, есть функция-тег (html), для использования с шаблонными литералами, но, результатом ее работы является не специальный объект (как в lit), а именно HTML, в виде строки. Таким образом, в Symbiote.js, функция html — это просто хелпер, для более удобного синтаксиса привязки данных, использование которого — опционально. 

Также, шаблоны Симбиота абстрактны, они не привязаны к контексту самого компонента и вы легко можете писать их в отдельных файлах и даже в отдельных пакетах, чтобы, к примеру, использовать их для генерации документа на сервере без каких-либо дополнительных инструментов или специальных «серверных компонентов».

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

Symbiote.js позволяет определять и переопределять (кастомизировать) шаблоны в общем документе, вне вашего JavaScript-кода.

Функция-хелпер Симбиота — html позволяет использовать использовать обычную интерполяцию строк, что не работает с функцией-тегом из пакета lit-html, который используется в LitElement.

В LitElement, рендеринг шаблонов связан с контекстом класса компонента и чтобы сделать его более абстрактным, вам придется изобретать отдельный враппер.

Для подсветки синтаксиса шаблонных литералов, для Symbiote.js и LitElement, можно использовать одно и то-же расширение для вашей IDE, это будет работать, практически, идентично. 

Рендеринг динамических списков

Для рендеринга списков, LitElement использует циклы, вложенные в шаблонные литералы, в которые, в свою очередь, вложены шаблоны айтемов. Либо, вы можете определять внешние циклы и вставлять в основной шаблон готовый результат. Также, Lit предоставляем специальную директиву repeat, которая предназначена для эффективного рендеринга списков, основанного на ключах.

Symbiote.js, в своем itemize API, использует простые атрибуты-директивы itemize и item-tag, которые выглядят компактнее. А шаблоном элемента, может быть просто вложенный HTML. Для эффективных обновлений можно использовать обновление исходных данных. Симбиот максимально эффективно вносит изменения в DOM, создавая новые элементы и изменяя их только когда это необходимо. Умеет работать как с ключами айтемов так и без, но под капотом именно unkeyed метод рендеринга, который более производителен.

Для каждого элемента списка, Symbiote.js создает свой экземпляр компонента, который реализует собственное локальное состояние, что делает это решение гибким и производительным. Вы, также, можете использовать заранее определенный веб-компонент, который может быть дополнительно оптимизирован. Элементы списка, в этом случае, не обязательно должны быть Symbiote-компонентами, это может быть любой экземпляр CustomElement.

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

Зависимости

В пакете Lit — три основные зависимости: lit-html, lit-element и @lit/reactive-element.

У Symbiote.js нет внешних зависимостей. 

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

Дополнительные возможности

Базовые возможности Симбиота и Lit — схожи. Это общая компонентная модель, основанная на стандарте CustomElements, декларативные шаблоны, реактивные привязки данных, коллбеки жизненного цикла…

Но у Симбиота есть ряд дополнительных фич, которые облегчают настройку взаимодействий между компонентами в их естественной среде — DOM-окружении. Например, Симбиот умеет инициализировать свойства из значений CSS переменных. Таким образом, используется каскадная модель распространения конфигураций, которая может поставляться вместе с остальными кастомизациями для ваших UI-сущностей. Вы можете легко определять и переопределять свойства компонентов для целых ветвей DOM-дерева, также просто, как и правила их стилизации. И вам будут не нужны для этого никакие дополнительные библиотеки и функции, все работает на стандартном DOM API и CSS.

Помимо этого, Симбиот-компоненты умеют подключаться напрямую к контекстам данных компонентов верхнего уровня в иерархии, а также, создавать общие shared-контексты (работает это по аналогии с поведением встроенных радио-инпутов, которые «узнают» о состоянии своих коллег по атрибуту name). Таким образом, вы можете реализовать сложный граф связей между компонентами без привлечения каких-либо дополнительных инструментов.

Примеры

Здесь я приведу два примера кода компонентов, полностью идентичных по функционалу. Оригинальный пример взят с официального сайта проекта Lit:

import { LitElement, html, css } from 'lit';

export class MyElement extends LitElement {

  static properties = {
    greeting: {},
    planet: {},
  };

  static styles = css`
    :host {
      display: inline-block;
      padding: 10px;
      background: lightgray;
    }
    .planet {
      color: var(--planet-color, blue);
    }
  `;

  constructor() {
    super();
    this.greeting = 'Hello';
    this.planet = 'World';
  }

  render() {
    return html`
      ${this.greeting}
        ${this.planet}
      
    `;
  }

  togglePlanet() {
    this.planet = this.planet === 'World' ? 'Mars' : 'World';
  }
  
}

customElements.define('my-element', MyElement);

Тот-же пример, но написанный с использованием Symbiote.js:

import { Symbiote, html, css } from '@symbiotejs/symbiote';

export class MyElement extends Symbiote {

  init$ = {
    greeting: 'Hello',
    planet: 'World',
    togglePlanet: () => {
      this.$.planet = this.$.planet === 'World' ? 'Mars' : 'World';
    },
  };

}

MyElement.shadowStyles = css`
  :host {
    display: inline-block;
    padding: 10px;
    background: lightgray;
  }
  .planet {
    color: var(--planet-color, blue);
  }
`;

MyElement.template = html`
  {{greeting}}
    {{planet}}
  
`;

MyElement.reg('my-element');

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

Выводы

Выводы делать вам. Автор этой статьи сам является скептиком и не является любителем бессмысленного увеличения энтропии. Все вышеописанные различия и нюансы родились из реальной практики решениях задач, часто, весьма нетривиальных. Не факт, что лично вы с такими задачами столкнетесь, но если столкнетесь — Симбиот не подведет.

Как и свою предыдущую статью, эту я закончу просьбой, по возможности, отметить проект звездочкой на GitHub. Это добавит разработчикам энтузиазма.

© Habrahabr.ru