Господа, продолжаем разбиратся в тонкостях веб компонент. Сделал тут бенч - сравнениe фреймворков ( $mol/lit/symbiot ) по todomcv. Вроде говорим об одном, а бенч о другом, разве не так ? Ан-нет, что бы разобраться с веб компонентами нужны фреймворки которые ставят их во главу угла, те, кто "сделал на них ставку".
Вот что мне удалось понять:
Первое. Память: 124 байта на веб-компонент, и 16 байт на JS object. Разница на порядок, это много, и без виртуализации интерфейс скорее всего будет лагать
// Lit: каждый todo-item — это HTMLElement (C++ heap, ~124 байт minimum) @customElement("todo-item") export class TodoItem extends LitElement { ... } // <todo-item> сразу аллоцирует DOM-ноду при createElement // Symbiote: аналогично, каждый <todo-item> = HTMLElement class TodoItem extends Symbiote { ... } TodoItem.reg('todo-item'); // $mol: компонент — JS-объект. DOM создаётся ТОЛЬКО при рендере. // 1000 задач в модели ≠ 1000 DOM-элементов // $mol рендерит только видимые компоненты (виртуализация) task_rows() { return this.task_ids_filtered().map(id => this.Task_row(id)) }
Custom Element |
js/mol_view |
|
1 000 задач |
~124 KB только на ноды |
~16 KB на объекты |
10 000 задач |
~1.2 MB |
~160 KB |
И это только базовые ноды — без текстовых, атрибутов, стилей и event listeners, которые тоже аллоцируются в C++ heap. Спецификация Custom Elements требует class MyEl extends HTMLElement. Нельзя создать CE без DOM-ноды.
Вот тут разбирается этот же довод от автора SolidJs. Далее ответ на довод от автора статьи
This is completely true. If your goal is to build the absolute fastest framework you can, then you want to minimize DOM nodes wherever possible. This means that web components are off the table.
Проще говоря - хотите производительности - не используйте веб компоненты.
Второе, помимо увеличения потребления памяти, мы теряем JIT оптимизацию.
Операция |
Время |
Во сколько раз |
obj.title = x (JS-объект) |
~1–2 ns |
эталон - 1x |
element.textContent = x (DOM) |
~30–60 ns |
в 30x раз хуже |
element.setAttribute(‘class’, x) |
~50–100 ns |
в 50x |
element.style.color = x |
~80–150 ns |
в 80x |
Задача - выделить всё ( актуально для почты например, которая ну никак не может за 1 раз удалить все письма, если они не помещаются на страницу ) Будет деградировать вот так
Задач |
Lit |
$mol |
Разница |
100 |
60 µs ( микросекунд ) |
3 µs |
20x |
1 000 |
600 µs |
8 µs |
75x |
10 000 |
8 ms |
12 µs |
650x |
100 000 |
120 ms (лаг, если кадр занимает больше 16 мс) |
15 µs |
8000x |
И такой вопрос к читателям. Стоит ли ориентироватьс в таком случае на js-framework-benchmark ? Мне кажется что нет. Не стоит рендерить то - что не видно. Там борются за спички, а все знают что экономить на спичках не нужно. Ну и вспоминаем цитату про перф выше.
Ну и пример в коде. Тут мы еще и html парсим... ( это плохо )
// Lit: обновление через DOM property // element.textContent = newValue ← C++ binding, slow path render() { return html`<label> ${this.text} </label>`; // Lit под капотом делает: node.textContent = value } // $mol: обновление через JS property // this.title() — чтение из memo-кеша (JS heap) // DOM обновляется батчем через requestAnimationFrame task_title(id, next?) { return this.task(id, ...)!.title ?? '' }
Идём далее. WC ( Web components ) используют push семантику для реактивности. Pull думаю врядли буду поддерживать. На что это влияет ? На количество строк кода написанных вами. А всем людям свойственно допускать ошибки, не даром есть поговорка: "Лучший код тот - который не написан". Смотрим бенч

Ну и пример кода, для наглядности.
// Lit: Push через EventTarget + requestUpdate export class Todos extends EventTarget { #notifyChange() { this.dispatchEvent(new Event("change")); // push! } add(text) { this.#todos.push({ ... }); this.#notifyChange(); // ← ручной push } } // Каждый компонент подписывается через @updateOnEvent("change") // При ЛЮБОМ изменении ВСЕ подписчики получают уведомление // Symbiote: Push через EventTarget аналогично store.addEventListener('change', () => this.#render()); // $mol: Pull — автоматический граф зависимостей @$mol_mem groups_completed() { // Автоматически отслеживает зависимости: // task_ids() → task() → groups // Пересчитывается ТОЛЬКО когда изменились зависимости for (let id of this.task_ids()) { var task = this.task(id)!; groups[String(task.completed)].push(id); } return groups; } // Не нужен ни EventTarget, ни ручные подписки
Не зря VueJs выбрал pull реактивность.
Затронем тестируемость. Что бы протетстить веб компонент - его нужно отрендерить. Максимально неэффективно. Думаю, все мы не людим когда тесты выполняются долго.
Посмотрим пример:
// $mol: тесты БЕЗ DOM. Компонент — просто объект. 'task add'($) { const app = $hyoo_todomvc.make({ $ }) app.Add().value('test') app.Add().submit() $mol_assert_equal(app.task_rows().at(-1)!.title(), 'test') } // Никакого document.createElement, никакого connectedCallback // Просто вызовы методов и проверка состояния // Lit: для тестирования нужен DOM const el = document.createElement('todo-app'); document.body.appendChild(el); // connectedCallback! await el.updateComplete; // ждём рендер! el.shadowRoot.querySelector('.new-todo')... // доступ через Shadow DOM! // В hyoo_todomvc есть 140 строк тестов. // В Lit и Symbiote реализациях — 0. ( а кода написано даже больше )
Про наследование.
Как это в Lit на "удобных маленьких веб компонентах"
class TodoApp extends LitElement { render() { // Весь шаблон — монолитный блок. 40+ строк HTML. return html` <header>...</header> <section> <ul>${this.todos.map(t => html`...`)}</ul> </section> <footer> <span>...</span> <ul>...</ul> <button>...</button> </footer> `; } } // Хочешь изменить только footer? class MyTodoApp extends TodoApp { render() { // Нельзя сказать "возьми render() родителя, замени footer". // Единственный вариант — скопировать ВСЕ 40 строк // и поменять нужный кусок. return html` <header>...</header> // копия <section>...</section> // копия <footer>МОЙ ФУТЕР</footer> // вот ради этого `; } }
Как это в $mol
// Базовый компонент: прокручиваемая область $mol_scroll sub / ← контент scroll_top 0 ← позиция скролла event_scroll ← обработчик // TodoMVC НАСЛЕДУЕТ $mol_scroll и заменяет только sub ( вложенные элементы ): $hyoo_todomvc $mol_scroll sub / <= Page $mol_list ... // Скролл, позиция, обработчик — всё досталось бесплатно. // Переопределили ТОЛЬКО содержимое. // Можно пойти глубже — унаследовать сам $hyoo_todomvc и заменить, например, только футер: $my_custom_todo $hyoo_todomvc foot <= My_footer $mol_view // Всё остальное (список, ввод, фильтры) — наследуется как есть.
Аналогично с наследованием логики и стилей ( уникально каскадно ) . 0 копипасты и boilerplate
И под конец хотел бы еще поспорить с утверждением. Далее моя вольная цитата из текста @i360u ( первая ссылка в статье )
Любое инженерное решение - это компромисс. Само по себе, наличие “против” не перевешивает все возможные “за”, априори. У каждого такого компромисса - есть конкретная цена. Если эта цена приемлема - это повод всерьез рассмотреть использование той или иной технологии.
В какой то степени с ним можно согласиться. Если бы не одно "НО". У нас у всех стоит задача - разработать максимально удобное, быстрое, красивое приложение, без легаси, чистым кодом, который легко поддерживать и всё в таком духе. Еще раз - задача у всех одна. И надо смотреть кто ёё лучше всего решает в совокупности всего frontend мира. Я знаю одно такое решение.
Это $mol - всё остальное, хуже. Это факт.
То что "вендор лок" "страшно непонятно изучать новое" - это деткие отмазки признать факты. Везде в програмировании надо изучать новое. А вендор лока вообще нет. Думайте. Кирилл. Подписаться.
Комментарии (51)

fransua
05.04.2026 09:54Про сравнение памяти для объектов - второй вариант будет отрендерен в какой-нибудь див и будет занимать те же мегабайты наверное. А если виртуализация и он не будет рендериться, то и CE так же можно не рендерить. Логика переходит в контейнер выше.
Про композицию - делаем функции renderHeader, renderBody, renderFooter и переопределяем одну в наследнике. Лет 40 этой практике наверное.

cmyser Автор
05.04.2026 09:54C виртуализацией не всё так просто
бенч https://t.me/giper_dev/11/288
статья про неё https://mol.hyoo.ru/#!section=docs/=gjboo1_xhyrnq
Про композицию абсолютно согласен, тут поинт скорее в том что это архитектурное решение, то есть разработчик никак не может сделать плохо ( как я показал )
ну а чем меньше ошибок может допустить програмист и отловить компилятор - тем лучше
Опять же аппеляция к тому что не надо думать и писать "лишний" код
fransua
05.04.2026 09:54Тогда о чем статья?

cmyser Автор
05.04.2026 09:54не понял вопрос, может выйдет раскрыть ?

fransua
05.04.2026 09:54Вы привели минусы веб компонентов: много занимают памяти и неудобная компоновка требующая дублирование логики. Оба аргумента не выдерживают критики, по крайне мере я так понял из ответа на мой комментарий. Тогда напрашивается вопрос, в чем минусы, в чем суть статьи?

cmyser Автор
05.04.2026 09:54в статье есть еще аргументы)
про память - аргумент выдерживает критику ( веб компоненты обязаны занимать эту память, а с js обьектом можно много манипуляций до рендера провернуть )
про jit - тоже
про pull push реактивность тоже
fransua
05.04.2026 09:54CE отлично дружат с JS объектами, совершайте манипуляции в JS объекте. Про jit не вижу в статье.
WC ( Web components ) используют push семантику для реактивности. Pull думаю врядли буду поддерживать. Это ваше мнение. В CE нет реактивности, но никто не мешает в них добавить mobx, rxjs или вот ваш mol.state, наверное тоже можно?

cmyser Автор
05.04.2026 09:54Про jit второй пункт
Можно попробовать подключить что то из мола, но всё равно как то проталкивать изменения нужно будет ( опять же лишняя абстракция над мол компонентами выходит, тестируемость теряется, html придется парсить )
Mobx хорош, rxjs не очень хорош ( делает много лишней работы ) https://page.hyoo.ru/#!=3ia3ll_rcpl7b/View’3ia3ll_rcpl7b’.Details=RxJS

fransua
05.04.2026 09:54Да, спасибо, про jit тоже треш какой-то. Как будто в mol вам не нужен textContent и setAttribute. Вы ведь не в канвас рендерить? Значит тот же дом, только на другом этапе. Хоть один аргумент стоящий есть?

cmyser Автор
05.04.2026 09:54$mol использует абстракцию к dom, обращений к dom на порядок меньше. Мост на .cpp не нужен ( jit ) ( понятно что js вызовы попадают на jit, я же писал про другое, про .cpp мост )
обьяснение примера с выделелнием писем из статьи
еще аргументы:
парсинг html
кол-во строк кода
pull семантика архитектурно не поддерживается WC
композиция - в $mol это архитекрутное решение ( програмисту не нужно думать об этом ) Что бы повторить это на другом фреймворке, нужно выполнить кратно больше работы. И думать тоже нужно больше. Причем всегда.
fransua
05.04.2026 09:54Вы сравниваете mol с каким-то другим фреймворком, с Lit? Тогда при чем тут CE и WC?
pull семантика архитектурно не поддерживается WC
Чем она не поддерживается, CE или ShadowDom? Pull семантика у стейт менеджера, в WC нет дефолтного стейт менеджера, можно брать любой. Парсинг html - это про lit Кол-во строк кода - по сравнению чего с чем? Опять видимо Lit vs mol. Ну так и назовите статью “Вздор про lit и восхваление Mol”

cmyser Автор
05.04.2026 09:54я отвечаю на вопрос "стоит ли брать веб компоненты как основу для фреймворка ?"
lifecycle CE (connectedCallback, attributeChangedCallback, observedAttributes) — это push семантика, её не обойтиОверхед по памяти будет всё равно — каждый CE это ~124 байта в C++ куче Blink (Node → Element → HTMLElement), независимо от фреймворка сверху. JS-объект — ~16-64 байта в V8 heap.
операции с JS-объектами V8 оптимизирует на полную (inline caches, hidden classes). Операции с DOM — это binding-вызовы в C++, которые V8 не может оптимизировать так же. Поэтому obj.title = x — 1-2 ns, а el.textContent = x — 30-60 ns. Это и называется "Jit сломан"
про парсинг да, но я не видел без него реализации на веб компонентах
строки кода отражают зависимость от выбранной семантики

fransua
05.04.2026 09:54Push - как и все события dom. В mol наверное тоже они обрабатываются. Дальше оборачивается в push/pull в зависимости от statemanager. Не все через атрибуты.
JS объект не отрендеришь, тебе нужен див - оверхеда нет
Obj.title потом все равно вызовет div.textContent. И наоборот customElement.data.title быстрый.

cmyser Автор
05.04.2026 09:54так мы ни к чему не придём)
нужен бенч)
было бы интересно скооперироваться и подготовить такой ? можем потом статью выпустить

fransua
05.04.2026 09:54Бенч mol и WC? Так для wc нужен фреймворк, там вот где-то в соседних ветках symbiote форсили, я хз что это, но наверное с ним можно. Lit конечно говно редкостное, рядом с mol и не лежал)

cmyser Автор
05.04.2026 09:54ага, я вот тут начал https://github.com/b-on-g/todomvc-compare
там как раз симбиот лит и мол

ulisma
05.04.2026 09:54Я работал с Lit, но совсем не понял, что там такого ценного. Компоненты JS можно и так писать.
Может и есть что-то очень полезное в Web Component, но лично я ничего такого не нашел.

cmyser Автор
05.04.2026 09:54лит добавляет реактивность для веб компонент
вроде у него еще и своя модель компонент есть, глубоко не копал

i360u
05.04.2026 09:54Вы почему-то скрыли от публики часть результатов вашего-же изначального сравнения. Именно ту, где были реальные значимые цифры, и они были СИЛЬНО не в пользу $mol. Кроме того, вы не боитесь открыть ящик Пандоры и добиться того, что кто-то все-таки не выдержит, и потратит время на РЕАЛЬНОЕ сравнение? Боюсь, оно будет разгромным.

cmyser Автор
05.04.2026 09:54буду рад получить реальное сравние от независимого автора, только таким, научным, методом рождается истина
я там бенч изначально неверно составил с размером ( в корне были мои мусорные файлы которые тянулись во все бандлы локально у меня )
Настоящий размер составил 181 кб, честно указал его в бенче
https://github.com/b-on-g/todomvc-compare
что бы проверить вы можете выполнить
git clone https://github.com/hyoo-ru/mam.git cd mam npm install npm start hyoo/todomvcи в папке hyoo/todomcv/-/web.js -- вы тут получите весь собранный код
PS поправил пути, и у меня на чистую 185 вышло щас
i360u
05.04.2026 09:54Я до конца не уверен, клон ли вы Дмитрия, или настоящий живой человек, но что касается общения - ему явно стоило бы у вас поучится. По технической части я вернусь с фидбеком позже.

cmyser Автор
05.04.2026 09:54не клон)
спасибо)
да я там фиксанул еще пути, проверил теперь собирается то что нужно

Artur_frontDev
05.04.2026 09:54Да, в целом ты прав, но ты немного радикально обобщаешь. Проблема не столько в самих веб-компонентах, сколько в том, как их используют. Если просто наивно рендерить тысячи элементов через DOM -любой подход упрётся в лимиты, не только WC.

cmyser Автор
05.04.2026 09:54я так же думаю, но почему многим это важно ( вот тут https://habr.com/ru/articles/1014708/#comment_29737390 )
эти многие реально смотрят на https://github.com/krausest/js-framework-benchmark
и думают что там реальная производительность показывается
тут фрустрация и отторжение из за "попытки обмануть" бенч этот
но если бенч плохой, зачем он нужен
я считаю лучше применять всё что можно, все допинги, и показывать реальную максимальную производительность JS. Она тоже не бесконечна, но насколько Не ? никто не знает. все упёрлись в html
по моим расчётам на моле можно свою OC написать, и она будет быстрее и стабильнее многих ныне живущих) ( и даже уведомления будут сразу прилетать и колокольчик краситься )

steel835
05.04.2026 09:54Не стоит рендерить то - что не видно.
Да, я именно так и думаю, когда поиск по странице ничего не находит, если это не на экране /s

cmyser Автор
05.04.2026 09:54плюсик в карму) согласен)
забавно, но в $mol поиск как раз таки работает)
но для этого нужно переопределять поиск стандартный ( в моле по дефолту )

SergeyBondar93
05.04.2026 09:54А на мобильных устройствах это тоже работает? Или там просто глобальный хендлер на страницу добавлен на ctrl+f?

cmyser Автор
05.04.2026 09:54Да, поиск есть, можно на иконку нажать и откроется ввод
На сочетание клавиш по идее тоже должно работать, но я не знаю как нажать на телефоне его )

misterzsm
05.04.2026 09:54Тут все просто, как по мне. Если делаем SPA - берем $mol. Он для этого топ-1. Если делаем сайт с интерактивными элементами - берем SSG (или классический веб-фреймворк с шаблонизатором, типа Laravel) и одно из вышеупомянутых WC-based решений. Все.

cmyser Автор
05.04.2026 09:54вопрос приоритетов
для ssg я бы всё равно конечно мол взял ( темы\локализация\адаптивность ) по дефолту идут
А вот WC если нужно куда в уже существующий проект пропихнуть то почему бы и нет
я бы даже к себе в $mol приложение попробовал сунуть

tzlom
05.04.2026 09:54Сколько $mol не рекламируй, пока у него такой не читаемый синтаксис он никому не нужен. Как ты правильно заметил-всем нужен поддерживаемый код, но шаблоны $mol это абсолютно не читаемая хрень на уровне зубодробительных однострочников лиспа

cmyser Автор
05.04.2026 09:54Как аргумент слабовато, это скорее мнение
Мне тоже сначала было необычно, но стоило понять что этот "непонятный" код декларативной описывает интерфейс и превращается в js, как сразу стало понятнее и проще
Тут бы хорошо реальное исследование провести

tzlom
05.04.2026 09:54Ну сделайте опрос, тупо по коду из этой статьи, кому понятно что у TodoApp есть footer и кому понятно что у $hyoo_todomvc есть foot (мне вот вообще непонятно почему у него есть нога), ну и попробуйте объяснить откуда она взялась.
Дальше больше - вы пишете что в MyTodoApp у вас не вышло заменить только футер. Конечно можно было бы вызвать родительский render и перетряхнуть его результат как нужно, например поменяв хедер и футер местами в зависимости от времени суток.
Попробуйте такое сделать в вашем декларативном описании.

cmyser Автор
05.04.2026 09:54
Согласен что не понятно ( по этому поводу есть предложения, сделать нагляднее ) надо исходники компонента смотреть что бы узнать какие свойства у него есть, тут стоит относиться к компоненту как к либе с API который нужно узнать
от времени суток - это будет уже в ts, делается не сложно
примерно в таком духе

cmyser Автор
05.04.2026 09:54если хотите, давайте созвонимся, постараюсь быстро обьяснить концепцию view.tree

tzlom
05.04.2026 09:54Я читал статью о нем и понимаю как он работает, проблема в том, что он не более чем json в смешной обёртке. Представьте что вместо шаблонов будет json с экзотическими полями - на сколько доступно это будет?
В конкретном примере это не просто странный json, а json который содержит в себе кучу магии. И эта магия зависит от контекста и на сколько программисту её реализующему было не лень.
Можно ли sub вызвать в произвольном месте и будет ли он всегда работать?
Как будет выглядеть код, который заменяет <sub> на <sub>/</sub> ?
t_tars
Респект за написание статьи, но чувак, ты со своим $mal иногда выглядишь как фанатик. Люди инертны, консервативны. Статьями на хабре призвать их использовать хорошее доброе вечное... вряд ли получится. Основывай свою корпорацию, делай ее продукт популярным и навязывай хорошие фоеймворки так! :)
cmyser Автор
спасибо)
только фанатики могут двигать не фанатиков на изучение нового)
Корпорация тоже будет!
misterzsm
Пацан к успеху идет: хочет стать топ-2 токсиком Хабра (после Карловского)
cmyser Автор
хахахахахаха))