Pull to refresh
445.29
Сбер
Больше чем банк

Как подружить веб-компоненты и JS-фреймворки

Level of difficultyMedium
Reading time9 min
Views3.5K

Всем привет, я Роман Троицкий. Очень люблю веб-разработку; участвовал в проектах, попавших на 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. Поэтому в перспективе веб-компоненты могут стать очень даже интересной технологией. 

Tags:
Hubs:
+13
Comments7

Information

Website
www.sber.ru
Registered
Founded
Employees
over 10,000 employees
Location
Россия