Что можно и нельзя выжать из веб-компонентов

JavaScriptTypeScript
Это не туториал и не вполне обзор — скорее заметки по горячим следам после собирания библиотеки компонентов. Начиналось всё с обычной обыденной истории: есть легаси-код, к легаси нужно прикрутить пипмочек и финтифлюшек, переписывать ничего нельзя, некогда, и вообще не трогайте тут ничего руками; большие и страшные пакеты тоже на всякий случай не трогайте, да и вообще, почему бы вам просто не взять самый прекрасный фреймворк Vanilla JS и не начать на нём писать, как завещали деды?

Но поскольку объем работ предвиделся очень заметный, то творческих порывов от идеи писать всё в голом JS никто почему-то не испытал. Пошли смотреть на инструменты и выбирать из них такие, которые бы не напугали потребителей наших компонентов до дрожи в коленках.

Инструменты


Хоть я и уже проспойлерил весь этот раздел, кое-что сказать тут нужно. Во-первых, веб-компоненты сразу же стали абсолютно безальтернативными: когда результатом творчества — предполагается библиотека компонентов, и нельзя тащить большого монстра а-ля Angular, который разрулит модульность полностью внутри себя, то остаётся то, что вообще не требует тащить библиотечного кода, и работает «прям так» в современных браузерах. Сами по себе веб-компоненты — это всё тот же удивительный фреймворк Vanilla JS, только с немного решенными вопросами модульности. Руками писать остаётся всё еще слишком много — нужна обертка с шаблонизацией, да еще и желательно, чтоб на каждый компонент было минимум бойлерплейта.

Таковых обёрток, да еще и достаточно зрелых для использования в серьезном проде — не так уж много: Hybrids, LitElement, Stencil. И они все делают примерно одно и то же примерно одним и тем же способом, разница — в малозначимых рюшечках. Hybrids пытается быть модным и безклассовым, Stencil — модным и реактоподобным, LitElement — вроде бы даже особо и не пытается. И по результатам выбирания из малозначимых мелочей на выходе остался LitElement — ООПшники поворотили нос от Hybrids, а нелюбители реакта — от Stencil, в котором JSX и вообще чувствуется повторение не самых бесспорных идей реакта.

В бой?


Говорить о том, что можно — не так уж интересно, это всё есть в документации, поэтому далее я буду говорить в основном о том, что нельзя: об этом в документации обычно не пишут.

Шаблоны


С точки зрения шаблонизации LitElement пользуется lit-html, которая отлично минималистична:

const template = html`
  <div
    attr=${a}
    .property=${b}
    ?booleanAttr=${c}
    @click=${handleClick}
  ></div>
`;

Это не html, но запомнить тут нужно ровно три конструкции в один символ — ".", "?", и "@". Всё остальное — таки html. Отностительно JSX с его className и прочим — это немного приятнее и законно отделено от кода JS/TS. Но, впрочем, отсюда и вытекает первое «нельзя» — биндить что попало куда попало не выйдет, биндить можно только в значения атрибутов и свойств, и в текстовое содержимое. Разумеется, в tagged template literals нет магии, и ценой некоторого количества извращений можно собрать строку в рантайме и в рантайме же к ней приделать биндинги lit-html, но это фактически влезание в рендер руками, и с тем же успехом можно собирать строки и отправлять их в innerHTML. Нормальным же образом это всё делается через композицию шаблонов и разбиение сложных компонентов на более простые составляющие. Бойлерплейта довольно-таки мало — обвязка шаблонов фактически отсутствует, поскольку это просто переменная, а для создания компонента потребуется пять «лишних» строк.

Компоненты


Единственная (но весомая) проблема компонентов — в том, что для полноценного «замешивания» компонентов с рендером детей где-то внутри шаблона родителя нужен Shadow DOM. Который, конечно, включён в LitElement по умолчанию, и вообще вроде бы как неплох. Но теневой DOM в настоящее время имеет одну большую проблему со стилями, аналогично css modules: изолировать стили оно прекрасно изолирует, но попутно это рубит на корню всю каскадность. Влезть в изолированные стили снаружи попросту нельзя. Вообще (почти) никак.

Это сильно мешает, например, возможности накатывания на компоненты разных тем. Всё, что можно делать с теневым DOM — это или упаковать все варианты стилизации внутрь компонента, или пытаться сделать тему полностью зависимой от переменных css — это то самое «почти», с которым таки можно подёргать изолированные стили. Но с которым придётся предусмотреть переменные буквально на любой разумный чих стилизации, и, с большой вероятностью, всё равно потом добавлять еще.

К счастью, Shadow DOM в LitElement можно просто отключить в компоненте. К несчастью, это также отключит и возможность адресно отрендерить детей элемента в нужных местах шаблона через <slot>. К счастью, немного извратившись, можно получить и первое и второе: для этого всего лишь нужно завести теневой корень на каждый необходимый слот, и не держать в нём ничего, кроме, собственно, <slot>. Таким образом и стилизация компонента будет открытой, и слоты будут наличествовать. Я хотел привести кратенький пример, но к сожалению код по манипулированию слотами кратеньким не выходит в любом случае — очень интересующиеся могут почитать вот этот вот issue. Я вдохновлялся идеями именно оттуда.

Ну и еще стоит упомянуть, что в обозримом будущем скорее всего заколосится браузерная поддержка и полифиллы ::part и ::theme, и вот с ними теневой DOM наконец-то станет тем решением, которого все ждут уже много лет — чтоб и изолировано, и расширяемо/изменяемо. Но пока этого всего еще нет.

Деплой


На этом пункте писать про «что нельзя» уже не выходит, потому что дальше можно всё — библиотеки lit существуют в виде ES modules, и поэтому без каких-либо проблем подхватываются чем угодно и как угодно (хоть голым браузером, который умеет в модули сам). Для IE и нынешнего Edge нужно подключать полифилл веб-компонентов, для модулей, если есть желание поднять их прямо из браузера, нужно что-то избавляющее от боли браузерных импортов, например es-module-shims. Ну или же бандлер.

Подцепленные в приложение веб-компоненты просто и банально доступны к применению, можно начинать использовать их в html и дёргать их методы и свойства в коде. Посмотреть, насколько хорошо это всё может быть прицеплено к другой либе или фреймворку — можно вот здесь (реакт отличился, но в среднем всё очень хорошо). Мы цеплялись в AngularJS, и всё было банально: ng-prop позволяет передать что-нибудь в компонент, а ng-on — слушать события.

Итог


Если нужно наваять компонентный UI и прикрутить его к чему-то, во что ты совершенно не хочешь влезать (в легаси код, в немодный страшный фреймворк, и в прочие плохие места) — веб-компоненты выручают просто прекрасно. Основные «зрелые» библиотеки, с ними управляющиеся — мелкие по размеру, серьезных технических проблем модульности и компоновки — уже нет, и можно просто брать и делать. Какую именно библиотеку вы возьмете — даже и не так важно, различий между ними на данный момент очень мало; конкретно LitElement, который взяли мы — не создал нам ни одной дополнительной проблемы, и отработал ожидаемым образом во всех случаях.
Tags:lit-elementweb-components
Hubs: JavaScript TypeScript
+7
3.6k 47
Comments 44

Popular right now

Top of the last 24 hours