Всем привет, я Роман Троицкий. Очень люблю веб-разработку; участвовал в проектах, попавших на Awwwards, Tagline и GoldenSite; помогаю организовывать митап Moscow CSS; участвовал в записи и разработке курса по фронтенду для Skillbox. На примере своего проекта я расскажу о сложившейся с Web Components ситуации, опишу их достоинства и недостатки. 

Зачем нужны веб-компоненты?

У крупных технокомпаний, как правило, есть множество ИТ-продуктов. Разрабатывают их, обычно, не одни и те же люди, целые группы команд. При этом фронтенд во всех этих продуктах обычно пишут на одних и тех же технологиях: берут готовый шаблон, что-нибудь обновляют — и готово. А что делать, если все эти продукты написаны на разных фреймворках? Конечно, можно сделать десяток UI-наборов на все случаи жизни, но будет крайне дорого их писать и поддерживать. А ещё придётся потратить колоссальное количество времени — конкуренты обгонят, сотрудники выгорят. Переписывать на один и тот же стек нецелесообразно по тем же причинам. 

Какие есть варианты? 

Взять готовый headless UI-набор с логикой компонентов, поверх которой можно добавить какой-то адаптер к фреймворку и корпоративный дизайн. Самый известный представитель такого подхода — это Tan Stack, известный также как React Query и Vue Query. 

Использовать мета-фреймворки для создания UI-наборов. Скажем, Mitosis позволяет собрать на JSX-компоненты, сделать что-то вроде AST и сгенерировать наборы компонентов с обвязками под нужные нам фреймворки. К примеру, есть у нас простой компонент с состоянием:

Сначала он превратится в JSON, тоже с состоянием:

Разметка превратится в массив nodes. Сначала идёт родительский div, в нём вложенный input. Дальше мы видим биндинги, onChange, привязку слушателей, значение состояния. И из этого кода средствами Mitosis мы можем десериализовать обратно в компонент, например, для Angular. 

Но ещё более интересными и перспективными показались веб-компоненты. 

Web Components

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

Веб-компоненты — это набор Web-API для создания собственных HTML-тегов, со своей логикой, дизайном, отображением и так далее. Например:

Для работы этого элемента нужно создать класс: 

Сохраним внутренности шаблона в переменную. В шаблоне опишем разметку нашего компонента, и дальше с ним можем работать с помощью DOM API — клонировать, применять, добавлять стили и так далее. 

Чтобы компонент был максимально независимым от стилей, мы можем его инкапсулировать с помощью метода attachShadow. Это можно сделать с помощью атрибутов тега template, в котором мы пишем разметку. Инкапсулировать можно и какую-нибудь логику. Модифицировать стили в таком случае мы можем только через sys-переменные, потому что Shadow DOM позволяет нам эти стили абстрагировать от содержимого родителей, страниц и так далее. 

{mode: `open`} покажет, что мы можем через JavaScript со страницы достучаться к коду веб-компонента. Фичу можно закрыть передачей {mode: `closed`}. В целом, эта изоляция — защита от дурака, а не полноценное средство безопасности, её можно очень легко нарушить.

Теперь зарегистрируем наш компонент в Windows Custom Elements. 

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

А если его не зарегистрировать, то мы увидим весь контент обычным текстом, как в div. При нажатии на кнопку выпадет меню:

Если зайти в инструменты разработчика, то мы увидим вот что:

Здесь есть похожий на iframe тег shadow-root с открытым режимом, который мы передавали в классе. В теле компонента можно посмотреть, откуда пришли дочерние элементы. Или можно по тегам slot выяснить, где отобразился компонент. 

Давайте на минуту вернёмся к нашей волшебной инкапсуляции. Возьмём простой WebInput:

У него другой шаблон и идентификатор. Как и предыдущий компонент, мы его собираем и упаковываем с помощью Shadow DOM, регистрируем, в браузере всё правильно отображается. Как вы думаете, если мы положим такой компонент в форму, будет ли введённое пользователем значение доступно в этой форме? Оказывается, благодаря инкапсуляции, не будет. Придётся либо передавать значение выше через события, либо указать formAssociated = true, чтобы показать, что этот веб-компонент элемент формы. Также придётся примениять set и get, чтобы можно было использовать компонент прямо в форме, применить submit или другие методы работы с нативными формами без дополнительной обработки.

Первый API — это Custom Elements, то есть возможность создавать свои теги, описывать их логику и стили. Следующий блок спецификации — это Shadow DOM, который нам даёт инкапсуляцию стилей и JavaScript внутри компонента. Третий важный API — это HTML-теги <template> и <slot>, которые мы использовали для отрисовки разметки. 

Промежуточные итоги

Какие достоинства и недостатки у веб-компонентов?

Достоинства:

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

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

  • Переиспользование. Мы можем переиспользовать веб-компоненты.

  • Агностицизм. Мы можем переиспользовать их в любом фреймворке. Собранный компонент — это немного JavaScript-кода и HTML-тег с какими-то атрибутами. 

  • Меньше зависимостей. Нужно гораздо меньше кода для работы в браузере. 

  • Производительность. Меньше объём, меньше кода тянет за собой, и поэтому требует значительно меньше ресурсов 

Недостатки:

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

  • Свойства — строки. Все эти проблемы связаны ещё и с тем, что свойства могут быть только строками, поэтому приходится сериализовать и десериализовать данные для работы с ними внутри компонента, передавать HTML-атрибуты. 

  • Связывание данных. Из-за этого многие задачи трудно реализовать, например, кросс-валидацию.

  • Очень много работы с DOM API. В целом, ничего страшного, просто писать приходится гораздо больше, чем если бы мы делали компоненты на Vue.js, например. 

  • Очень сильно хромает доступность на уровне браузерных элементов. 

  • Проблемы с поисковой оптимизацией, потому что мы отображаем веб-компоненты с помощью JavaScript. 

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

Почему я тогда рассказываю о веб-компонентах?

Во-первых, есть ещё один веб-API, который появился в Chrome в ноябре 2023 — Declarative Shadow DOM. Он позволяет определить shadow DOM для компонента прямо в HTML без JavaScript. Это упрощает создание веб-компонентов и уменьшает количество JavaScript-кода — а значит, сильно вырастает производительность. Этот API пока сыроват, однако его поддержку запланировали внедрять во все браузеры. Посмотрим, что из этого выйдет. 

А во-вторых, на самом деле технология интересная. Она не для гиков и докладов на конференциях, её используют в production такие компании, как Google, Adobe, SpaceX. Например, интерфейс управления ракетами в SpaceX написан на веб-технологиях с применением веб-компонентов. Среди российских компаний я не нашёл упоминаний об использовании этой технологии. На HeadHunter не встречаются требования уметь работать с веб-компонентами, нет примеров с ними.  

Библиотека Stencil

Есть очень много библиотек, которые так или иначе нивелируют описанные выше недостатки технологии, упрощают работу с ней. Например, Lit, StencilJS. Последняя мне нравится тем, что позволяет без проблем отрисовывать веб-компоненты на сервере. С помощью этой библиотеки можно написать полноценное приложение. Кроме того, Stencil позволяет сделать универсальный UI-набор для веб-компонентов или с готовыми обвязками под большую тройку фреймворков. Вдобавок, есть отличный консольный интерфейс, куча встроенных команд. Покажу, как легко создавать веб-компоненты с помощью Stencil:

Из коробки доступно много декораторов. Библиотека написана на TypeScript, всё по стандартам. С помощью декоратора Component дадим название тегу, укажем путь до всех стилей, и выберем режим Shadow DOM. 

В классе компонента можно указать типы свойства, значения по умолчанию, виды классового события, которое будет генерировать наш компонент — всё это тоже с помощью декораторов, которые предоставляет Stencil. Если мы хотим отрисовать компонент при изменении его свойства, то можем прокинуть mutable: true в нужный декоратор. Нужно реактивное состояние? Пожалуйста. Состояние должно быть комплексным, с объектом, массивом и так далее? Без вопросов. 

И в конце обязательный метод render, в котором мы можем написать всю разметку в корректном JSX:

Писать такие компоненты получается быстро, они очень просто выглядят, всё декларативно. Stencil ещё позволяет снабдить наш UI-набор обёртками для фреймворков. Сначала сделаем два NPM-пакета, покажу на примере Vue и React:

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

Всё на модулях, есть reshaking. Можно использовать это как есть из готовой сборки, а можно упаковать в отдельные NPM-пакеты, версионировать, публиковать в разные зоны. 

Давайте посмотрим, как это будет выглядеть в приложении. Я взял для примера Vue.js. По документации Stencil делаем плагин в папке со сборкой компонентов, импортируем и используем:

Компоненты будут видны в приложении, свойства проверяются, события генерируются, всё это с автозавершением. 

Ещё есть документация:

Будет сгенерирован один огромный JSON-файл со всей метаинформацией по всем компонентам. Настройка type: `docs-readme` создаёт readme-файлы в каждой директории с компонентом. То есть верхняя часть — генерация Stencil, а если мы будем модифицировать компонент, то в сборке будет модифицироваться readme-файл. Stencil генерирует эту документацию благодаря декораторам и TypeScript'у.

Можем добавить JSDoc для компонентов. Он пойдёт либо в метаполя, либо в описание полей для документации. JSDoc — это движок, который собирает вот такие комментарии и позволяет генерировать с их помощью какую-то документацию:

Поскольку в нашем случае JSDoc пойдёт именно в storybook, я считаю, что есть смысл потратить на него время и упростить работу с UI-набором. Есть только небольшие ограничения:

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

После этого достаточно завести storybook, добавить дополнение, оно примет сгенерированные Stencil файлы документации и создаст стенд с документацией:

Можем что-то поменять, но из коробки всё будет готово на 90 %.

Производительность

Ещё один аргумент в пользу веб-компонентов — их производительность. Есть регулярно обновляемый набор тестов с открытым исходным кодом от Krausest, вот с каким результатом они выполняются:

Конечно, это лабораторные условия, в реальности всё зависит от квалификации того, кто пишет, но выглядит очень позитивно. А ведь здесь ещё нет Declarative Shadow DOM, который позволит часть компонентов использовать вообще без JavaScript. 

Заключение

Почему веб-компоненты, появившиеся ещё до React и других компонентных фреймворков, не смогли захватить рынок? Выскажу своё мнение.

Несколько лет назад я использовал веб-компоненты в эксплуатации. Самыми сложными элементами были графики на Canvas, которые мы с помощью Chart.js делали в веб-компонентах, и комплексные таблицы. Там было немало сложностей. Графики были динамические, приходилось всё отслеживать, влияли зависимости. Чтобы облегчить себе работу, я воспользовался StencilJS. Она сильно помогла. Потом удалось подружить веб-компоненты с Vue.js-приложением. В общем, большинство проблем были связаны с моими руками и сложными бизнес-требованиями.

Какие я сделал выводы?

Простые вещи — кнопки, поля ввода, заголовки и так далее — делать на веб-компонентах легко и приятно, они хорошо работают. Если компоненты посложнее, но замкнуты в себе, например, как таблица, то с ними тоже ещё можно жить. Например, я свойствами пробрасывал URL, по которому таблица сама запрашивала данные, которые нужно было отрисовать. Это было гораздо проще других способов проброса данных, их нужно централизованно зачем-то хранить. Но если бы найденный мной способ не подошёл, было бы намного неприятнее. Были вопросы только с доступностью и с SEO, но в том проекте это не особо требовалось. 

Сейчас веб-компоненты используются в некоторых проектах Сбера. Есть ряд виджетов, которые ранее рендерили в iFrame и отдавали партнёрам. Сейчас для упрощения встраивания эти виджеты перевели на веб-компоненты, и партнёры могут их там немного модифицировать, устанавливать через NPM-пакеты и так далее. Про внутренние проекты с веб-компонентами рассказать не могу из-за NDA, но такие тоже есть. 

На текущий момент я всё-таки считаю, что веб-компоненты — это сомнительно, но окей. Они не самые удобные, есть и достоинства, и недостатки, их можно использовать в популярных фреймворках. И многое зависит от дальнейших действий Interop: обещают много хорошего, новый API, отказ от JavaScript. Поэтому в перспективе веб-компоненты могут стать очень даже интересной технологией. 

Комментарии (5)


  1. Zoolander
    09.08.2024 13:00

    То есть для компонентов со сложной логикой и серьезным внутренним состоянием веб-компоненты не очень удобны?

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


  1. pojiloj_voin
    09.08.2024 13:00

    А что делать, если все эти продукты написаны на разных фреймворках?

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

    Выход за рамки вью либы/фреймворка ограничивает использование его фичей. Так можно было и компоненты на чистом js+css написать и вставлять в контейнеры html.

    А то что вы описали про встраивание виджетов других команд решается на уровне сборки микрофронтов, в т.ч. module federation.

    Короче на мой взгляд пользы не видно, только проблемы с поддержкой + необходимость вкатываться для новых членов команды


  1. isumix
    09.08.2024 13:00

    Я как раз разрабатываю микро-библиотеку чтобы можно было удобно создавать и обновлять DOM деклакративно, примерно как в React. Ее можно использовать как с вэб компонентами так и без них. Кстати логика mount/unmount под капотом реализована с помощью вэб компонентов, но она опциональна.


  1. idd451289
    09.08.2024 13:00

    Немного самопиара в тему
    Я вот недавно узнал о веб компонентах, и загорелся желанием запилить что то похожее на vue только с веб компонентами
    И реактивность вкорячил и хуки и темплейты. Сейчас вот думаю над language server-ом и подсказкой ошибок typescript-а. И cli и сторы есть
    Короче те кто хочет зацените тута


  1. JustAReader
    09.08.2024 13:00

    Во-первых спасибо за статью, мало кто пишет про веб компоненты. Хотелось бы уточнить что всё местами несколько сложнее чем описано. Например, инкапсуляция стилей выглядит немного поломанной, для тех кто только начинает работать с веб компонентами, но по факту просто наследуемые стили протекают по дизайну и это можно пофиксить одной строкой, после чего стили полностью изолированы (внутрь пробрасываются CSS переменные и можно, но не удобно, стилизовать т.н. parts). Кажется что передавать можно только строки в атрибуты, но по факту это только важно для полностью декларативного рендеринга на сервере, сами компоненты имеют настоящие свойства доступные из других фреймворков и вообще js. Ну и для SEO вполне реально отрендерить контент и запихать в слот, а потом распарить или даже выкинуть на клиенте. В целом внутри веб компонента может жить как сложное приложение так и простые контролы.