Если кратко - да все с ними ТАК. Это замечательный набор современных браузерных технологий, для решения реальных задач веб-разработки. Веб-компоненты позволяют делать очень многое, более просто и элегантно, чем это было бы без них. А главное, они позволяют, с потрясающей гибкостью, решать задачи “со звездочкой” - те, которые немного выходят за рамки и требуют более творческого подхода от разработчика.
Почему-же тогда по Хабру гуляют, кхм… некие одиозные личности (не будем показывать пальцем) и рассказывают нам про то, что веб-компоненты это ужас-ужас и полный провал? Давайте разберемся.
Сперва, небольшая ремарка о том, почему именно я чувствую за собой моральное право взять микрофон и встать на защиту упомянутых стандартов. Дело в том, что я активно использую все это в работе уже много лет, еще со времен первых черновиков и ранних спецификаций. Я держу руку на пульсе с первых сырых версий Polymer и до современного Lit. За это время, я лично спроектировал и воплотил множество, по настоящему, сложных приложений и отдельных универсальных виджетов. Я понимаю, что определение “сложного приложения” - для каждого может быть своим, и я имею в виду большие дэшборды и панели управления реального времени, с кучей графовых данных, летающих по веб-сокетам, с коллекциями объектов из сотен тысяч элементов. Простых, но очень разношерстных проектов, также, реализовано немало. Я, также, являюсь автором библиотеки Symbiote.js, которая построена на тех самых API, о которых дальше пойдет речь.
Еще одно важное уточнение. Когда мы обсуждаем какую-либо технологию, важно понимать, что любое инженерное решение - это компромисс. Не существует абсолютно идеальных “сферических в вакууме” технологий (ну кроме, конечно, <sarcasm>$mol</sarcasm>). При оценке чего-либо, мы всегда взвешиваем “за” и “против” - это и есть, по сути, наша работа. Само по себе, наличие “против” не перевешивает все возможные “за”, априори. У каждого такого компромисса - есть конкретная цена. Если эта цена приемлема - это повод всерьез рассмотреть использование той или иной технологии. Из этого принципа я и буду исходить в своих рассуждениях и выводах.
Бред
Начну с самой жести - откровенного вранья, манипуляций и подтасовок.
Нам рассказывают про то, что в веб-компоненты можно передавать свойства ТОЛЬКО с типом “строка”. При этом, ссылаются на нативный хук жизненного цикла attributeChangedCallback. Для передачи сложных типов - предлагают использовать парсинг JSON из значений атрибутов. Ребята, опомнитесь, HTML-атрибуты - это часть обычного HTML, у которого нет никакой прямой связи с JS-рантаймом. Если вам нужно модно-реактивно установить свойство любого типа для компонента - используйте простые советские модификаторы доступа - геттеры и сеттеры! Используйте нативные прокси для продвинутой работы с данными. Кто вам запрещает это делать? Удобно обратиться к свойству веб-компонента, таким образом, можно, практически, из любого современного фреймворка или напрямую через DOM API. Всерьез говорить про этот “недостаток” все равно, что пытаться общаться по факсу с человеком, который сидит рядом с вами в одной комнате, и жаловаться на медленную связь. Не доводите до маразма простые вещи!
Далее Shadow DOM - отличный способ изолировать внутренности вашего компонента от внешний влияний, как непредсказуемых так и, потенциально, вредительских. Нам говорят, что сама по себе изоляция - это огромная проблема, так как, сюрприз-сюрприз, не дает нам произвольно стилизовать внутреннее содержимое снаружи. Тут явная фича выдается за баг. Во первых, ну кто вас заставляет использовать Shadow DOM там, где это вам НЕ НУЖНО? Использовать Custom Elements БЕЗ Shadow DOM - это нормальная и даже хорошая практика. И это вообще разные независимые API. К слову, Shadow DOM вы можете подключить вообще, практически, к любому DOM-элементу. А во вторых, у вас есть куча элементарных способов безопасной настройки внутренностей вашего компонента через самые обычные CSS-переменные (дизайн-токены) и правила ::part(). И это даже еще не все. Ребята, просто учите современный CSS и то, как строятся нормальные дизайн-системы, чтобы так не позориться.
Еще, иногда, упоминают необходимость избыточного дублирования стилей в Shadow DOM каждого экземпляра компонента. Ой, а такой необходимости - нет. А есть API Adopted Style Sheets, которые давно поддерживаются во всех современных браузерах.
Еще, жизненный цикл веб-компонента начинается с конструктора, а вовсе не с аттача компонента в DOM, как нам уверенно заявляют некоторые малограмотные “специалисты”. Вы можете создавать кастомные элементы исключительно в памяти, манипулировать ими в рамках общего Document Fragment через самые обычные методы DOM API, без затратной отрисовки чего-то лишнего в браузере, и прикручивать, таким образом, виртуализацию для обмана широкой публики в цифрах бенчмарков, лол.
Полуправда
Общий реестр имен кастомных элементов в браузере - это правда. А вот то, что это фундаментальная неразрешимая проблема - НЕТ. Помните, я говорил о стоимости компромисса? Ну вот, тут эта стоимость - мизерная. Решение на уровне соглашения о именованиях с префиксами (или постфиксами), что может быть проще? Даже если вы что-то упустили и коллизия имен произошла - вы всегда можете переопределить проблемный компонент банальным наследованием. Браузер вам явно укажет на проблему в момент попытки регистрации. Это легко ловится интеграционным тестом. Никаких silent-падений. Естественно, это можно оборачивать в try/catch. Исходя из многолетней личной практики, я вам уверенно заявляю: все это не стоит выеденного яйца.
Вообще, общие пространства имен - это базовый и древнейший паттерн всего нашего программирования. От ключей в объектах, до доменных имен и статистически-уникальных идентификаторов сущностей в распределенных системах. Имена стандартных тегов HTML входят сюда-же. Этот паттерн используется повсеместно и вокруг него давно устоялись практики и стратегии. Плоская структура, часто, быстрее и надежнее древовидной. Делать из этого какую-то особую драму, в случае с веб-компонентами - не вижу никакого смысла. Это просто нюанс, который нужно держать в уме и придерживаться системного подхода.
Теперь о основных хуках жизненного цикла: connectedCallback и disconnectedCallback. Нам заявляют, какой ужас, эти коллбеки могут сработать “вхолостую”, при простом переносе элемента из одного места в DOM - в другое! Действительно, какой кошмар! Это ведь можно решить одним маленьким флагом, установленным при первичной инициализации. Но это, видимо, слишком сложно для некоторых. Таким образом, одну из главных и самых мощных возможностей стандарта (управление жизненным циклом изнутри) нам представляют как, высосанный из пальца, “недостаток”.
Также, отметим невозможность де-регистрации компонента, однажды зарегистрированного в одном документе. Это правда. А то, что это большая проблема - нет. Браузер просто хранит ссылку на конструктор. Памяти это занимает - минимум, на производительность - не влияет. Больше проблем бы было, если бы такая возможность существовала: нам приходилось бы гадать какой именно класс сейчас активен и думать что делать с ранее созданными элементами.
Правда
Я не был бы достаточно объективен, если бы не упомянул реально существующую проблему, или, скорее, важный нюанс: когда браузер парсит HTML документ в потоке, он дергает хук connectedCallback сразу, как находит ОТКРЫВАЮЩИЙСЯ кастомный тег, но ДО того, как распарсит его содержимое. Это приводит к тому, что, например, методы DOM API, с помощью которых, вы хотите взаимодействовать с потомками - могут не работать в этот момент. Человек, который не знает причину такого поведения - может обжечься. Решается отложенной загрузкой скрипта (defer) или использованием type="module". В этом случае, инициализация сработает ПОСЛЕ окончания парсинга и ошибки не будет. В Symbiote.js, для удобства, есть готовый кастомный хук жизненного цикла renderCallback, который решает этот вопрос и гарантирует доступ к DOM API.
Есть и обратна сторона: вы можете получить ссылку на веб-компонент, который еще не был зарегистрирован. В этом случае, API самого компонента будет недоступен. Для того, чтобы быть уверенным, что все готово к полноценному взаимодействию, можно использовать promise, возвращаемый customElements.whenDefined(name).
Вся эта неожиданная асинхронность в поведении может сбить с толку, если не знать о ней заранее. Но тут как с потерей контекста в функции - иногда мы с этим сталкиваемся, но знаем, что делать. В Symbiote.js эти вопросы решены на уровне архитектуры.
Обманутые надежды
Часто, негатив в сторону веб-компонентов начинается с обманутых надежд. Люди ждали нативную и высокооптимизированную замену популярным фреймворкам, а получили слишком низкоуровневый API. Но чьи-то нереалистичные ожидания вовсе не означают, что сам стандарт - плохой. Веб-компоненты не призваны заменить собой весь наш JS-зоопарк, они решают свой ряд специфических но очень важных проблем, и не имеют никаких достойных аналогов или альтернатив в этом.
Суперспособности
Так в чем-же реальная сила веб-компонентов? Я бы выделил 3 основные вещи:
1. Связь HTML c JS-рантаймом на уровне узлов.
Часто, фронтенд-разработчики теряют из фокуса тот факт, что именно HTML - это альфа и омега всего веба. И начинается путь HTML-файла, определяющего все, что происходит на странице, задолго до попадания в браузер. В общем случае, на серверной стороне, нет никаких абстракций, призванных обеспечить прямое взаимодействие с пользователем. А кастомные теги, в теле формируемого текстового документа - есть. Эти теги - идеальные нативные маркеры для вашей JS-интеграции, они дают не только удобные точки привязки, но и естественную бесшовную структуру (XML), которая органично ложится на старый добрый HTML, без изобретения отдельных велосипедов и лишнего вендор-лока. Это-же касается и шаблонов самих компонентов.
2. Атомарное и естественное управление жизненным циклом.
Custom Elements - это способ узнать реальное текущее состояние DOM напрямую от браузера и реагировать на него. Компонент появился - мы знаем, компонент исчез - мы знаем. И это без каких-либо внешних громоздких абстракций, универсально и надежно. Не важно кто и как создал компонент, или в какой момент участок документа был полностью перерисован - мы знаем, когда и как нужно реагировать.
3. Базовая компонентная модель.
Custom Elements - это идеальная основа и фундамент для создания решений более высокого уровня. От библиотек общего назначения (типа Lit или Symbiote.js), до ui-китов, сложных универсальных виджетов, микро-фронтендов и мета-приложений-агностиков. Мощнейшая компонентная модель и базовый API - у нас есть из коробки. Сразу с документацией и широкой поддержкой. Не нужно изобретать очередное колесо. Работает везде, в React, Angular, Vue, с другими веб-компонентами, самостоятельно, в SPA, на динамических, гибридных или полностью статических страницах и так далее.
Мотивы хейтеров
Довольно очевидны. Инвестиции в альтернативные решения под угрозой. Страх потерять актуальность на рынке. Необходимость что-то опять изучать за рамками своей уютной мета-платформы. Попытки продать себя и продукты своей деятельности, под предлогом “экспертности”. И так далее.
Да простит меня Хабр за мой раздраженный тон. Но, честно говоря, достало уже. Изучайте истинные возможности платформы и современные стандарты. Не слушайте псевдо-экспертов.
