Вперше в Україні! Jeff Atwood, founder @ Stackoverflow — на конференції Highload fwdays
×Закрыть

Мир веб-компонентов: разбираемся в трендах

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

Темы для рассмотрения

  • Поддержка браузерами.
  • Совместимость с фреймворками.
  • SSR + SEO/Боты.
  • Стилизация компонентов.
  • Специальные возможности (Accessibility).
  • Версионирование.
  • Загрузка в браузер.
  • Доступные инструменты.
  • Веб-компоненты vs Фреймворки.

Поддержка браузерами

IE 10–11 и Edge (с движком Chakra) находятся вне игры, поскольку они не поддерживают Shadow DOM. Это делает использование веб-компонентов нецелесообразным из-за сложности полифилов.

Все остальные браузеры (Chrome, FF, Safari, учитывая две последние версии) прекрасно работают со всеми основными технологиями, которые нам нужны. Давайте взглянем на таблицу.

Технология ChromeFFSafariiOSAndroid
(CSS) ::slottedxxx (баги)xx
(CSS) :hostxxx xx
(CSS) :host()
Есть обходной путь
xx--x
(CSS) :host-context()x---x
(CSS) :rootxxx xx
(CSS) CSS Variablesxxx xx
(JS) Constructible Stylesheets
Есть обходной путь
x----
(JS) Custom Elementsxxxxx
(JS) Shadow DOMxxxxx
(JS) Custom Eventxxxxx

Совместимость с фреймворками

Все основные фреймворки, которые существуют в настоящее время, полностью поддерживают веб-компоненты. Но давайте посмотрим внимательно:

SSR (Рендеринг на стороне сервера) + SEO/Боты

Теоретически возможно полностью отрендерить веб-компонент на стороне сервера (и это даже работает для простых случаев), но... На сегодняшний день нет стабильной реализации этого процесса, а также способа представления Shadow DOM в HTML. К счастью, сообщество активно работает над решением.

Так что же нам тогда делать?

Ответ прост — вы можете жить без SSR для веб-компонентов. Конечно, этот подход имеет некоторые ограничения, но приходится работать с тем, что есть... Давайте посмотрим детальнее.

Чтобы рендерить веб-компоненты только на стороне клиента и не жертвовать SEO/совместимостью с ботами, вам нужно хранить контент в Light DOM и использовать атрибуты ARIA. Рассматривайте свои веб-компоненты как нативные элементы HTML. Вы можете согласиться с тем, что боту не нужно видеть Shadow DOM, например, для элемента <select>, чтобы просмотреть его содержимое. Если компоненты хорошо спроектированы, ботам не нужно развернутое (Shadow + Light) DOM-дерево для получения текстового содержимого.

Давайте рассмотрим разметку компонента «Tab» в качестве примера:

<my-tab-group role="tablist" aria-label="My test tabs">
 <my-tab role="tab" slot="tab">Title for tab 1</my-tab>
 <my-tab-panel role="tabpanel" slot="panel">Content 1</my-tab-panel>
 <my-tab role="tab" slot="tab">Title for tab 2</my-tab>
 <my-tab-panel role="tabpanel" slot="panel">Content 2</my-tab-panel>
 <my-tab role="tab" slot="tab">Title for tab 3</my-tab>
 <my-tab-panel role="tabpanel" slot="panel">Content 3</my-tab-panel>
</my-tab-group>

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

Стилизация компонентов

Поскольку в наших компонентах мы используем Shadow DOM для внутренней разметки и слоты для контента, написание CSS поначалу может быть нетривиальной задачей. Давайте рассмотрим основные концепции, которые нужно держать в уме.

Изоляция CSS

Shadow DOM практически полностью изолирует свое содержимое от CSS, включенных на странице. В примере, приведенном выше, тег <p> (который находится внутри Shadow DOM) имеет текст, окрашенный в зеленый цвет, поскольку CSS свойство «color» было унаследовано.

Как включить CSS в Shadow DOM

В настоящее время самый простой способ включения CSS для веб-компонентов — это встраивание их inline в шаблон веб-компонента. Если вы включите их через тег <link>, вы увидите FOUC во время загрузки страницы, точно так же, как в примере выше (обратите внимание на текст «I’m Shared CSS»).

Однако это ограничение можно преодолеть, скрыв весь контент Shadow DOM перед загрузкой связанного CSS или добавив внешний CSS-код компонента в код главной страницы (разумеется, в этом случае его следует каким-то образом заскоупить).

Поэтому остается ждать поддержки спецификации Constructable Stylesheets во всех основных браузерах. Это дало бы нам больше контроля над CSS.

Контекстно-зависимые стили

Часто наши компоненты имеют разные стили отображения в зависимости от переданных им аргументов. Классический пример — <select multiple>. Когда вы передаете аргумент «multiple» компоненту «select» — он полностью меняет свой внешний вид. Для достижения аналогичного поведения веб-платформа предлагает псевдокласс :host() (не путайте с :host).

К сожалению, этот псевдокласс не поддерживается в Safari и iOS, поэтому мы не можем его использовать. В качестве обходного пути мы можем использовать возможности JS для ручного проецирования атрибутов и классов хост-элемента в корневой элемент Shadow DOM, чтобы получить возможность писать обычный CSS.

Стилизация контента внутри слота (<slot>)

Таблицы стилей, которые были добавлены в Shadow DOM, могут также применяться в элементах, находящихся внутри <slot>. Для этого у нас есть псевдоэлемент ::slotted(). Однако его поддержка браузерами все еще не идеальна. Посмотрите на приведенный выше пример: Safari не будет правильно обрабатывать селектор ::slotted(p)::before.

В качестве обходного пути мы можем добавить стили, отвечающие за стилизацию содержимого слота прямо на страницу (в Light DOM). Таким образом, следующий селектор ::slotted(p)::before для <my-component> будет преобразован в my-component p::before. Это, естественно, несколько нарушает концепцию Shadow DOM, однако я не вижу другого пути на текущий момент.

Рекомендации

На этом этапе я бы рекомендовал продолжать (если вы уже это делаете) загружать CSS на страницу отдельным файлом с помощью тега <link> и включать этот тег в каждое Shadow DOM-дерево. В настоящее время я использую следующий код для этого:

class MyComponent extends HTMLElement {

 constructor() {
   super();
  
   this.attachShadow({ mode: 'open' });
   this.__injectGlobalCSS();
 };

 __injectGlobalCSS() {
   const globalCssInclude = document.querySelector(
       'head > [data-global-css="true"]'
   );

   if (globalCssInclude === null) {
       console.warn(`Can't find global CSS for component ${this.tagName}! Trying to render w/o it...`);
       return;
   }

   this.shadowRoot.appendChild(globalCssInclude.cloneNode(true));
 }

};

Специальные возможности (Accessibility)

Когда пользователи специальных возможностей, таких как скринридеры, перемещаются по странице, жизненно важно, чтобы передавалось семантическое значение различных элементов управления. Но как этого добиться с помощью веб-компонентов, учитывая, что теги HTML будут нестандартными и, как следствие, не будут иметь никакого семантического значения?

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

Давайте посмотрим на доступный компонент-слайдер, созданный с помощью технологии веб-компонентов:

<custom-slider min="0" max="10" value="3" role="slider"
              tabindex="0" aria-valuemin="0" aria-valuemax="10"
              aria-valuenow="3" aria-valuetext="3"
              aria-label="Movie rating"></custom-slider>

Чтобы увидеть больше примеров полностью доступных веб-компонентов, вы также можете обратиться к следующим инструкциям от Google.

Версионирование

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

Давайте посмотрим на варианты, которые у нас есть.

Никогда не делайте breaking changes. Это принцип, который используют браузеры. И хотя это возможно сделать, и это может быть даже наилучшим вариантом для начала, очевидно, что данный подход противоречит принципу «fail fast, fail safe» и не способствует инновациям.

Управление версиями на основе тегов. Таким образом, вместо <x-button> у вас будет <x-button-v1> для того, чтобы иметь несколько мажорных версий компонента на странице. Поэтому, если для «Фрагмента 1» требуется button@1.1.5, а для «Фрагмента 2» требуется button@1.2.1 — будет использоваться только button@1.2.1. И если для «Фрагмента 1» требуется версия 1.1.5, а для «Фрагмента 2» требуется версия 2.0.0 — оба компонента будут зарегистрированы.

Версионирование по скоупу фрагмента. Таким образом, вместо <x-button> у вас будет <x-button-myfragment>.

Загрузка в браузер для микросервисного фронтенда

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

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

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

Доступные инструменты

Библиотеки, которые вы можете использовать для создания своих веб-компонентов (отсортировано по моим предпочтениям сверху вниз):

  • LitElement — написан ребятами из Polymer, Google. На мой взгляд, самый оптимальный выбор.
  • Stencil — компилятор веб-компонентов плюс базовые классы. Создан командой Ionic. Его система сборки не очень совместима в Webpack.
  • SkateJS — крошечная обертка вокруг нативных API, которая позволяет использовать различные библиотеки рендера. Создано Trey Shugart, который подарил нам WC SSR PoC.
  • Svelte 3  — это скорее фреймворк, а не библиотека для упрощения создания веб-компонентов.
  • Riot.js.
  • Slim.js.
  • X-Tag — последняя версия все еще в бете.
  • Smart HTML Elements — платный.

Но, постойте, действительно ли мне нужен какой-то инструмент для комфортного написания веб-компонентов? И простой ответ — нет, вы можете написать их, используя Vanilla JS. И это будет работать для большинства простых компонентов, которые вы будете писать.

Веб-компоненты vs Фреймворки

Если вкратце: они разные. Не пытайтесь заменить веб-компонентами старые добрые фреймворки, такие как Vue.js или React.

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

Другой вариант использования — если вы пишете open source библиотеку UI-компонентов (например, Bootstrap или MDC) и хотите сохранить ее независимой от фреймворка.

Пример Material Design Components в эру до веб-компонентов и с веб-компонентами.

Со временем фреймворки, скорее всего, начнут использовать Custom Elements и Shadow DOM внутри, но я не ожидаю увидеть широкого распространения данного подхода в ближайшие 1-2 года.

Примечания

Если вы видите какие-либо пробелы или ошибки в статье, напишите в комментариях или отправьте мне на почту.

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

Полезные ссылки

LinkedIn

4 комментария

Подписаться на комментарииОтписаться от комментариев Комментарии могут оставлять только пользователи с подтвержденными аккаунтами.

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

Может кому интересно будет. На хабре есть несколько статей, описывающих идеи, заложенные в Svelte:
habr.com/ru/post/345028
habr.com/ru/post/414869
habr.com/ru/post/420113
habr.com/ru/post/438834
habr.com/ru/post/446026
habr.com/ru/post/449450

Вот мнение о веб-компонентах от создателя Svelte (кстати в статье ошибка в написании названия этого фреймворка):
dev.to/...​t-use-web-components-2cia
Перевод на хабре — habr.com/ru/post/457010

и этой спецификации: «WAI-AIRA»

Подписаться на комментарии