Здравствуйте, меня зовут Дмитрий Карловский, и я.. пилил веб-компоненты, когда их ещё не придумали, делал полноценные компоненты на AngularJS, когда там ещё были только директивы, и разработал компоненто-ориентированный фреймворк $mol с инверсией контроля и статической типизацией, когда это ещё не было мейнстримом. Короче, я немного в теме. И сейчас я расскажу вам, почему мы сразу отказались от Web Components и почему у них нет никаких перспектив.

Немного истории
В десятых годах веб усложнился настолько, что стало понятно, что делать приложения уникальными снежинками страничками контр-продуктивно, и нужна абстракция, которая позволит декомпозировать приложения на автономные части — компоненты.
В браузерах уже были нативные компоненты, такие как кнопки, селекты, инпуты и тд. Но стандартного набора, разумеется, не хватало и нужна была возможность создавать свои. Однако, инерция мышления не позволяла отказаться от HTML в пользу чего-то более подходящего, поэтому каждый пытался сделать из языка разметки гипертекста язык композиции компонент.
Именно в то время появился ReactJS с его вызовами функций через XML, AngularJS с его директивами в виде атрибутов, и герои этой статьи - веб-компоненты с его кучей проблем, о которых мы и пойдёт речь далее.
Особняком тут стоит $mol, где мы изначально спроектировали оптимально заточенный под композицию компонент DSL — view.tree. Но сегодня мы поговорим не про грамотные архитектурные решения.
Итак, первая версия веб-компонент увидела свет в 2014, когда их затащили в Хром. И проча им убийство фреймворков, все ринулись на них молиться. Четыре года молились, поняли, что лыжи не едут, переименовали в v0 и в 2018 перезапустили с новой спекой.
Авторы же учли все недостатки первой нулевой версии и не допустили тех же ошибок в следующей? Ага, конечно, и далее мы об этом поговорим.
Немного бойлерплейта
Давайте создадим простейший веб компонент с одним свойством, используя API Custom Elements:
class MyFoo extends HTMLElement { _bar = 0 get bar() { return this._bar } set bar( next ) { this.setAttribute( 'bar', next ) } static observedAttributes = [ 'bar' ] attributeChangedCallback( name, prev, next ) { this[ '_' + name ] = next } } globalThis.customElements.define( 'my-foo', MyFoo ) const foo = document.createElement( id )
Как-то мне уже больно на это смотреть, а ведь мы ещё толком и не начали. Например, в такой наивной реализации свойства могут быть только строковыми.
Если нужны другие типы данных, то нужно прикручивать какую-нибудь сериализацию, чтобы поместить их в атрибут, десерелиализацию, чтобы доставать их обратно, и костыли, чтобы не десериализовывать то, что мы только что сериализовали.
Ну, давайте малой кровью прикрутим хотя бы JSON:
class MyFoo extends HTMLElement { _bar = 0 get bar() { return this._bar } set bar( next ) { this._bar = next this._muted_ = true; try { this.setAttribute( 'bar', JSON.stringify( next ) ) } finally { this._muted_ = false } } _muted_ = false static observedAttributes = [ 'bar' ] attributeChangedCallback( name, prev, next ) { if( this._muted_ ) return this[ '_' + name ] = JSON.parse( next ) } }
И с числами, например, теперь всё хорошо, но со строками теперь проблема — появляются двойные двойные кавычки в атрибутах:
<my-foo bar=""lol""></my-foo>
И тут уже начинают намазывать ещё слоёв абстракций с разными синтаксисами:
<my-foo [bar]="lol" tabIndex="1"></my-foo> <my-foo bar="lol" tabIndex="{1}"></my-foo> <my-foo bar="lol" @tabIndex="1"></my-foo>
Плюс нужен какой-то нормальный сериализатор, который хотя бы даты сможет передать в компонент, не говоря уж об экземплярах моделей и прочем.
Ну да ладно, с костылями можно приноровиться ходить, а бойлерплейт спрятать за фабриками фабрик, собрав свой собственный велосипедный фреймворк без названия. Но есть одно но...
Немного бенчмарков
Слои абстракции совсем не бесплатны. Кривые абстракции дороже на порядок. Но авторы веб-компонент умудрились сделать всё медленнее уж на 3 порядка. Вот вам, сравнение скорости создания разных объектов:

Казалось бы, одна микросекунда — и глазом моргнуть не успеешь. Но всего-лишь тысяча таких компонент, и на одно только их создание уходит больше миллисекунды. А миллисекунд у нас во фрейме анимации есть всего 16, чтобы не было лагов. И большая часть из них тратится даже не на прикладной код, а на рендеринг того, что мы наворотили.
Ну ладно, мы же не дураки на каждый фрейм создавать новые компоненты, правда? А это не важно, любое взаимодействие с веб-компоненами занимает микросекунды. Вот, например, изменение одного свойства с минимально возможной логикой:

Почему всё так печально? А ответ прост. Любой дом-элемент - это не просто JS-объект, который может быть эффективно оптимизирован JIT-компилятором, это объект в памяти хоста (браузера), для которого дополнительно создаётся JS-прокси, с которым мы и работаем.
И если нативные элементы реализованы на C++ и скомпилированы с AOT-компиляцией, то веб-компонентам приходится постоянно передавать управление между JS-рантаймом и рантаймом хоста, что не позволяет JIT-компилятору что-либо эффективно заинлайнить.
Надо ли говорить, что памяти это всё занимает на порядок больше? 124 байта у тривиального веб-компонента против 16 у обычного JS-объекта.
А ведь мы ещё даже не аттачили компоненты к документу, не добавляли Shadow DOM, не создавали кастомных реестров, и не гоняли события по дереву компонент туда-сюда.
Адепты веб-компонент предпочитают не замечать этого слона, сидящего у них на коленях. И всё ждут, что вот-вот все браузеры оптимизируют и наступит щастье. 12 8 лет уже ждут. Давайте пожелаем им ещё столько же терпения.
Немного детских болезней
Всё, у чего есть начало, есть и конец. У всего, кроме веб-компонент. Единожды зарегистрировав такой, он останется жить навечно, пока вся вкладка не будет закрыта. Хотелось бы посмотреть в глаза тому гениальному учёному, который решил, что отменять регистрацию никогда не требуется, и заставить его реализовывать какой-нибудь Hot Module Reload на таком API, да боюсь меня тогда посадят за насилие над детьми.
И все эти регистрации глобальны. Кто первый встал - того и тапки. А кто вторым — получай эксепшеном по интерфейсу:
customElements.define( 'ya-button', YandexButton ) // ... customElements.define( 'ya-button', YahooButton ) // ? the name "ya-button" has already been used with this registry
Модуль-неудачник мало того, что будет сыпать стрёмными ошибками, так ещё и вместо своих компонент будет создавать и использовать чужие одноимённые. Как думаете, что тут может пойти так?
Ну ладно, ладно, когда-нибудь можно будет обмазываться CustomElementRegistry в Shadow DOM и не регистрировать всё глобально:
const customElementRegistry = new CustomElementRegistry() customElementRegistry.define( 'ya-button', YandexButton ) class YandexForm extends HTMLFormElement { constructor() { super() const shadow = this.attachShadow({ mode: "open", customElementRegistry, }) shadow.innerHTML = `<ya-button>Ну-ну</ya-button>` } }
Кто бы мог подумать, что конфликты имён не стоит допускать? Никогда такого не было и вот опять!
И вот подключу я две такие обмазанные всевозможными реестрами библиотеки, которые регистрируют веб-компоненты... а, нет, не регистрируют, ибо всё равно может быть конфликт имён их рутовых компонент. Поэтому они предоставят мне свои реестры, которые я должен смёржить... а, нет, как ты их смёржишь-то не потеряв компоненты одной из них? Значит мне нужна мёржилка, которая будет переименовывать компоненты, приклеивая к их именам префиксы неймспейсов. В результате один и тот же компонент в разных контекстах будет иметь разное имя. Смотри не перепутай!
А, да, и заполифилить эти реестры для современных браузеров не получится, так что особо рассчитывать на них в ближайшие несколько десятков лет не приходится.
Как эту проблему можно было бы решить уже сейчас? Для этого есть 2 древние идеи: глобальный фрактальный реестр имён, по типу DNS, и VerLess — обновление кода без слома обратной совместимости.
А что насчёт обратной задачи? Допустим нам нужно заменить реализацию какой-нибудь кнопки внутри веб-компонента на свою распрекрасную реализацию с непередаваемыми тактильными ощущениями. Короче, запилить инверсию контроля, говоря умными словами. А, ну да, кому это вообще нужно? Жуй, что дают, и не хоти лишнего.
Немного БДСМ
Так как с дисциплиной у разработчиков всё плохо, ибо они то и дело лезут ломать снаружи наши прекрасные компоненты, то авторы веб-компонент решили, что надо ограничить подвижность с помощью изоляции в Shadow DOM. То есть если вставляешь сторонний компонент в свою страницу, то он должен выглядеть не в стиле твоего приложения, а в каком-то своём уникальном, превращая всё приложение в лоскутное одеяло. Какое прекрасное поведение по-умолчанию!
Для кастомизации стилей веб-компонента нам предлагают вырывать гланды через анус: создаём стайлшит (скачанный не подойдёт), и передаём его в компонент... а, нет, не передаём, у нас же подражание HTML, а значит ничего, кроме строк и DOM элементов передать нельзя. Сам компонент должен взять откуда-то снаружи стайлшит и пропихнуть его в свой Shadow DOM через adoptedStyleSheets. Откуда и как — ну придумайте как-нибудь, чо как маленькие.
Если вам всё ещё не хватило острых ощущений, то вот вам ещё одна задачка — сделать полифил для нового DOM-элемента, который поддерживается, ещё не во всех браузерах. Допустим, какой-нибудь <geolocation>. Казалось бы, вот оно, идеальное применение веб-компонент, но нет, решили садисты-стандартизаторы, разработчики должны страдать! Как из-за того, что имена стандартных элементов должны быть в lowercase (без шампуров), так и из-за того, что имена веб-компонент могут быть только в kebab-case (с хотя бы одним шампуром).
А, и ещё из-за того, что кто-то особо одарённый решил засунуть 100500 совершенно разных компонент в один элемент <input>, который ни расширить, ни переопределить. И до сих пор не понял, что это была плохая идея, продолжая накидывать туда ещё.
Немного граблей
У веб-компонентов есть чудесные хуки жизненного цикла connectedCallback и disconnectedCallback. Первый срабатывает при подключении к документу, а второй при отключении. Отличные места для создания и освобождения ресурсов вокруг жизненного цикла компонента.
А теперь у нас викторина! Как вы думаете, что будет, если веб-компонент перенести в то же место, где он и так находился?
Ничего не меняется, так что ничего и не произойдёт.
Это всё равно перенос, поэтому будет лишь событие DOMSubtreeModified.
Любой перенос — это удаление и вставка, поэтому disconnectedCallback и затем connectedCallback.
Будут каскадные вызовы этих колбэков на всём поддереве компонент.
Ну конечно же последний вариант! Формально точный, но практически бесполезный. Даже если в disconnectedCallback запускать асинхронную задачу освобождения ресурсов с задержкой, а в connectedCallback отменять её, то всё равно запуск сотен-тысяч этих задач на ровном месте — штука не бесплатная.
Можно, конечно, создание ресурсов перенести в конструктор, а освобождение в FinalizationRegistry, но первый не может быть асинхронным, а когда сработает второй, и сработает ли вообще — не возьмётся предсказать даже Ванга.
Про ленивую инициализацию свойств и автоматический контроль времени жизни ресурсов я даже не заикаюсь. Нормальную реактивность мы не дождёмся в веб-компонентах, думаю, никогда.
Стоит ли вообще упоминать, что о статической типизации вообще и TypeScript в частности тоже можно не мечтать? В то время как даже самые слоупок-фреймворки уже худо-бедно статически типизируются, веб-компоненты предлагают нам коммуникацию между компонентами через нетипизированные строки. Самое то в век галюцинирующих Искусственных Идиотов!
Немного выводов
Я рассмотрел далеко не все проблемы веб-компонент, постаравшись отобрать лишь наиболее фундаментальные, но надеюсь мне удалось показать, что этот дважды поспешный стандарт настолько мертворождён, что за десяток лет, так и не снискал популярности, ни у разработчиков фреймворков, ни тем более у прикладников.
Немногие энтузиасты страдают, но продолжают есть кактус, надеясь на светлое будущее, наступления которого ждать не приходится. И причина — в кривом фундаменте, к которому до сих пор приходится лепить подпорки, чтобы он не разваливался на базовых задачах.
И ещё немного
Web Components Are Not the Future
Подкидывайте свои ссылки, добавлю и их сюда.
Комментарии (21)

supercat1337
25.03.2026 09:52Спасибо за публикацию! Полезно.
Добавлю.Например, в такой наивной реализации свойства могут быть только строковыми.
Строковые, потому что вы setAttribute используете, что необязательно. А так, у классов веб-компонент возможности такие же, как и у обычных js-классов. Вот пример когда свойство - объект. Специально добавил connectedCallback, чтобы отобразить его в виде текстовой ноды.
class MyFoo extends HTMLElement { connectedCallback() { this.textContent = this.bar.foo; } bar = { foo: 1337 }; } globalThis.customElements.define("my-foo", MyFoo); const element = new MyFoo(); console.log(element.bar.foo); // 1337

ionicman
25.03.2026 09:52Ну те придумали как всегда сферическую проблему и донкихотите?)
Зачем JSON хранить в атрибуте? Вас кто-то заставляет?
Чтобы не было конфликтов, достаточно, чтобы у тэгов был свой префикс для каждого модуля - и это нормально.
Ну и да, естественно, когда вы делаете дичь, ее трудно делать - и это тоже вполне нормально.
Веб-компоненты - это одно из самых ожидаемых и удачных нововведений (особенно с возможностью изоляции через shadowdom) в браузерах, и да, если пытаться не понимать их, и тыкать каменным топором, как вы привыкли - будет больно.
Я рассмотрел далеко не все проблемы веб-компонент
Вы рассмотрели лишь свое непонимание и старые подходы к новой технологии - не надо пинять на технологию, если руки кривые.

cmyser
25.03.2026 09:52префиксы поддерживаются руками ? есть ли тут человеческий фактор ? человек не надёжен
может будут реальные аргументы ? без топоров и кривых рук
можем к примеру сделать две реализации и сравнить, сейчас это несложно с нейронками, как вам такой формат ?
ionicman
25.03.2026 09:52Реальные аргументы про что? Про то, что в статье абсолютно надуманная дичь типа впихивания JSON в атрибуты? Для этого какие-то аргументы нужны, серьезно?
Что значит поддерживается руками? Это можно сделать автоматизировано без каких-либо проблем, в том числе и при сборке.Можете даже свой кастомный префикс впихнуть в библиотеку, если необходимо и библиотека грамотно написана.
Внезапно использование внутри одного скопа одинаковых наименований приводит к конфликту наименований - как так?! Никогда такого ведь не было, и вот опять!
А если серьезно - а как должно было-бы быть? Браузер должен разруливать нейминг за программиста? )
cmyser
25.03.2026 09:52Хотелось бы fnq уникальные человекочитаемые имена
Ну и что бы не компилилось при конфликте, как вы и написали
Я не говорил что json это плохой аргумент, хороший
Я говорил про то что дальше идёт эмоциональный наброс, как и во втором вашем комментарии

Kahelman
25.03.2026 09:52Нейминг давно разрулен. MS это еще во времена СОМ сделала - все имена -это GUID. :)

cmyser
25.03.2026 09:52нечеловекочитаемые, не удобно ни дебажить ни разбирать инцы
знаете этот момент когда тестер рассказывает про баг, а вы уже сразу знаете как его поправить ? вот с человекочитаемыми именами проще понять где баг и словить этот момент "просветления"

replicate_1
25.03.2026 09:52Вы описываете ряд проблем Web Components как фундаментальные. Но многие из них уже закрыты стандартами платформы - с документацией на MDN и поддержкой браузеров.
Если нужны другие типы данных, то нужно прикручивать какую-нибудь сериализацию, чтобы поместить их в атрибут, десерелиализацию, чтобы доставать их обратно
Здесь смешаны атрибуты (HTML-разметка, всегда строки) и свойства (JS-рантайм, любые типы). Web Component - инстанс JS-класса. Сложные объекты передаются по ссылке через свойства:
document.querySelector('my-widget').data = { user: 'Habr', roles: [1, 2] };Как пишет open-wc: “A great benefit of properties is that they can accept any javascript value, including complex objects and arrays”.
у нас же подражание HTML, а значит ничего, кроме строк и DOM элементов передать нельзя. Сам компонент должен взять откуда-то снаружи стайлшит и пропихнуть его в свой Shadow DOM через adoptedStyleSheets. Откуда и как — ну придумайте как-нибудь, чо как маленькие.
В компонент передается любой ссылочный тип, не только строки. Стандарт Constructable Stylesheets появился в Chrome в 2019-м, с марта 2023-го работает везде (Safari 16.4+). CSS парсится один раз, инстансам раздается ссылка на
CSSStyleSheet:const sheet = new CSSStyleSheet(); sheet.replaceSync('.btn { color: red }'); shadowRoot.adoptedStyleSheets = [sheet];Документация: adoptedStyleSheets (MDN), Constructable Stylesheets (web.dev).
Любой перенос — это удаление и вставка, поэтому disconnectedCallback и затем connectedCallback… Будут каскадные вызовы этих колбэков на всём поддереве компонент… запуск сотен-тысяч этих задач на ровном месте — штука не бесплатная.
JS-инстанс при отрыве от DOM не уничтожается, стейт сохраняется. Повторную инициализацию можно предотвратить guard-флагом:
connectedCallback() { if (this.initialized) return; this.initialized = true; }А платформа уже пошла дальше:
Element.moveBefore()иconnectedMoveCallback()решают эту задачу нативно - перенос узла без вызоваdisconnectedCallback/connectedCallback. MDN прямо пишет: “You could add an empty connectedMoveCallback() to stop the other two callbacks running”. Поддержка: Chrome 133+, Firefox 144+ (Safari пока нет). Документация: Element.moveBefore() (MDN).И все эти регистрации глобальны. Кто первый встал - того и тапки… заполифилить эти реестры для современных браузеров не получится, так что особо рассчитывать на них в ближайшие несколько десятков лет не приходится.
Scoped Custom Element Registries уже реализованы и документированы на MDN. Конструктор
new CustomElementRegistry()позволяет создавать изолированные реестры, привязанные к конкретному Shadow Root:const myRegistry = new CustomElementRegistry(); myRegistry.define('my-button', MyButton); host.attachShadow({ mode: 'open', customElementRegistry: myRegistry });Разные shadow-деревья могут использовать одинаковые имена без конфликтов. Причем первым эту фичу зашипил именно Safari (v26), Scoped Registries включены в программу Interop 2026 (Apple, Google, Microsoft, Mozilla), так что Firefox - вопрос времени. Поддержка: Chrome 146+, Safari 26+ (Firefox пока нет, есть полифил). Документация: Scoped registries (MDN).
если вставляешь сторонний компонент в свою страницу, то он должен выглядеть не в стиле твоего приложения, а в каком-то своём уникальном, превращая всё приложение в лоскутное одеяло
Платформа добавила
CustomStateSetи CSS-псевдокласс:state(). Компонент экспортирует внутреннее состояние наружу, потребитель стилизует через обычный CSS:labeled-checkbox:state(checked) { border: solid; }Плюс
::part()для таргетирования конкретных частей Shadow DOM снаружи. Оба API поддерживаются всеми браузерами:::part()с 2020 года,:state()- Chrome 90+, Firefox 126+, Safari 17.4+. Документация: CustomStateSet (MDN), ::part() (MDN).Реальные ограничения у Web Components есть (работа с формами, например), но основные пункты перечисленные в вашей статье платформа уже закрыла.

replicate_1
25.03.2026 09:52Продолжу разбор остальных утверждений из статьи.
В качестве примера бойлерплейта приводится сырой
HTMLElement, из чего делается вывод о переусложненности всего стандарта. Но это все равно что судить о разработке интерфейсов на чистом JS поdocument.createElementи ручной вставке нод в DOM - так уже давно никто не пишет.Текущие инструменты вроде Lit решают проблему рутинного кода, добавляя реактивность и работу с шаблонами в очень тонкой обертке (порядка 6KB gzip). А новые декораторы из спецификации TC39 (закреплены в Stage 3, базовая поддержка в движках стартовала в 2024 году) позволяют убрать лишний код вообще на уровне самого языка:
class MyWidget extends LitElement { @property() name = ''; render() { return html`<p>Hello, ${this.name}</p>`; } }Бойлерплейт raw API - не аргумент против технологии, иначе raw WebGL “доказывает”, что 3D в браузере невозможен.
создание веб-компонента на 3 порядка медленнее обычного JS-объекта… 124 байта против 16
Методологически странно сравнивать DOM-элемент с plain JS-объектом. С тем же успехом можно сказать, что
document.createElement('div')“на порядки медленнее” пустого{}- это не баг<div>, а природа работы с DOM.Корректное сравнение - Web Component против компонента React или Vue. Абстракции фреймворков (виртуальное дерево, замыкания хуков) вместе с реальным DOM-элементом занимают сопоставимый объем памяти, просто затраты спрятаны под капотом. В эталонном бенчмарке js-framework-benchmark реализация на Lit стабильно идет рядом с React и Vue по скорости рендеринга.
Что касается статической типизации, о которой “можно не мечтать” (аргумент про нетипизированные строки) - это верно только для HTML-атрибутов. Свойства Web Component - это обычные поля ES-класса. Они типизируются ровно так же, как любой другой TypeScript-код, и работа с компонентами из JS идет именно через свойства:
class MyWidget extends HTMLElement { data: UserProfile | null = null; set count(v: number) { /* ... */ } }Инструменты вроде Stencil вообще компилируют
.tsxв нативные Web Components с полными типами.Наконец, называть “мертворожденным” и “не снискавшим популярности” стандарт, на котором работает почти весь фронтенд GitHub (на базе Catalyst), интерфейсы YouTube, Spectrum Web Components от Adobe, Salesforce (Lightning Web Components) и даже дашборды SpaceX Crew Dragon - довольно смело. Если это критерии “мертворожденности”, то непонятно, какой тогда должен быть порог успешности технологии.

nin-jin Автор
25.03.2026 09:52У свидетелей веб-компонент универсальные ответы на претензии к скорости:
А вы не используйте аттрибуты.
А вы не используйте ShadowDOM.
А вы не разбивайте приложение на веб-компонеты.
Проще говоря: не используйте веб-компоненты, иначе получите тормоза даже на таком тривиальном интерфейсе, как у Ютуба.
В эталонном бенчмарке js-framework-benchmark реализация на Lit стабильно идет рядом с React и Vue по скорости рендеринга.
Иронично, что собственно компоненты там и не используются, если не считать однократный бутстрап приложения, конечно. Да и нашли на кого ровняться - вот вам принципиально недостижимая для веб-компонент эффективность:


replicate_1
25.03.2026 09:52Вы описываете ряд проблем Web Components как фундаментальные. Но многие из них уже закрыты стандартами платформы - с документацией на MDN и поддержкой браузеров. Пройдемся по конкретным пунктам.
Если нужны другие типы данных, то нужно прикручивать какую-нибудь сериализацию, чтобы поместить их в атрибут, десерелиализацию, чтобы доставать их обратно
Здесь смешаны атрибуты (HTML-разметка, всегда строки) и свойства (JS-рантайм, любые типы). Web Component - инстанс JS-класса. Сложные объекты передаются по ссылке через свойства:
document.querySelector('my-widget').data = { user: 'Habr', roles: [1, 2] };Как пишет open-wc: “A great benefit of properties is that they can accept any javascript value, including complex objects and arrays”.
у нас же подражание HTML, а значит ничего, кроме строк и DOM элементов передать нельзя. Сам компонент должен взять откуда-то снаружи стайлшит и пропихнуть его в свой Shadow DOM через adoptedStyleSheets. Откуда и как — ну придумайте как-нибудь, чо как маленькие.
В компонент передается любой ссылочный тип, не только строки. Стандарт Constructable Stylesheets появился в Chrome в 2019-м, с марта 2023-го работает везде (Safari 16.4+). CSS парсится один раз, инстансам раздается ссылка на
CSSStyleSheet:const sheet = new CSSStyleSheet(); sheet.replaceSync('.btn { color: red }'); shadowRoot.adoptedStyleSheets = [sheet];Документация: adoptedStyleSheets (MDN), Constructable Stylesheets (web.dev).
Любой перенос — это удаление и вставка, поэтому disconnectedCallback и затем connectedCallback… Будут каскадные вызовы этих колбэков на всём поддереве компонент… запуск сотен-тысяч этих задач на ровном месте — штука не бесплатная.
JS-инстанс при отрыве от DOM не уничтожается, стейт сохраняется. Повторную инициализацию можно предотвратить guard-флагом:
connectedCallback() { if (this.initialized) return; this.initialized = true; }А платформа уже пошла дальше:
Element.moveBefore()иconnectedMoveCallback()решают эту задачу нативно - перенос узла без вызоваdisconnectedCallback/connectedCallback. MDN прямо пишет: “You could add an empty connectedMoveCallback() to stop the other two callbacks running”. Поддержка: Chrome 133+, Firefox 144+ (Safari пока нет). Документация: Element.moveBefore() (MDN).И все эти регистрации глобальны. Кто первый встал - того и тапки… заполифилить эти реестры для современных браузеров не получится, так что особо рассчитывать на них в ближайшие несколько десятков лет не приходится.
Scoped Custom Element Registries уже реализованы и документированы на MDN. Конструктор
new CustomElementRegistry()позволяет создавать изолированные реестры, привязанные к конкретному Shadow Root:const myRegistry = new CustomElementRegistry(); myRegistry.define('my-button', MyButton); host.attachShadow({ mode: 'open', customElementRegistry: myRegistry });Разные shadow-деревья могут использовать одинаковые имена без конфликтов. Причем первым эту фичу зашипил именно Safari (v26) - исторически самый консервативный из браузеров. Scoped Registries включены в программу Interop 2026 (Apple, Google, Microsoft, Mozilla), так что Firefox - вопрос времени. Поддержка: Chrome 146+, Safari 26+ (Firefox пока нет, есть полифил). Документация: Scoped registries (MDN).
если вставляешь сторонний компонент в свою страницу, то он должен выглядеть не в стиле твоего приложения, а в каком-то своём уникальном, превращая всё приложение в лоскутное одеяло
Платформа добавила
CustomStateSetи CSS-псевдокласс:state(). Компонент экспортирует внутреннее состояние наружу, потребитель стилизует через обычный CSS:labeled-checkbox:state(checked) { border: solid; }Плюс
::part()для таргетирования конкретных частей Shadow DOM снаружи. Оба API поддерживаются всеми браузерами:::part()с 2020 года (96%+ пользователей),:state()- Chrome 90+, Firefox 126+, Safari 17.4+. Документация: CustomStateSet (MDN), ::part() (MDN).Реальные ограничения у Web Components есть (работа с формами, например), но перечисленные в вашей статье пункты платформа уже закрыла.

dlartagnan
25.03.2026 09:52Автор как обычно пытается выставить другие технологии в плохом свете, потому что есть прекрасный $mol
cmyser
а если использовать Lit ? он по сути же добавляет реактивность, думаю конечно поверх этих всех костылей, но всё же
скорость должна быть поидее меньше там всё равно
хотелось бы сравнения, в общем
supercat1337
Реактивность может быть реализована с помощью внешних библиотек, например, mobx.
Код под спойлером
cmyser
если честно, выглядит ужасно больно, очень много бойлерплейта
mobx неплох, это единственная альтернатива $mol'у в плане реактивности
но $mol далеко не только про реактивность
supercat1337
Не знаю почему вас удивил низкоуровневый код на js. Это база.
cmyser
не удивил
привык к другой абстракции просто
Скрытый текст
тут ошибка - такое же состояние как и любое другое, при нажатии на плюс или минус произойдет перерасчёт, и появится следующее значение
mobx тихо схавал ошибку, в результате пользователь видит
2, нажимает еще раз - видит снова 2 - нажимает еще раз - видит 4
что выглядит как баг
при этом ошибку если бы мы хотели исправить пришлось бы оборачиравать в try catch
nin-jin Автор
Тут дело не в MobX, а в рендерилке, которая не умеет отображать падения.