Господа, продолжаем разбиратся в тонкостях веб компонент. Сделал тут бенч - сравнени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)


  1. t_tars
    05.04.2026 09:54

    Респект за написание статьи, но чувак, ты со своим $mal иногда выглядишь как фанатик. Люди инертны, консервативны. Статьями на хабре призвать их использовать хорошее доброе вечное... вряд ли получится. Основывай свою корпорацию, делай ее продукт популярным и навязывай хорошие фоеймворки так! :)


    1. cmyser Автор
      05.04.2026 09:54

      спасибо)

      только фанатики могут двигать не фанатиков на изучение нового)

      Корпорация тоже будет!


    1. misterzsm
      05.04.2026 09:54

      Пацан к успеху идет: хочет стать топ-2 токсиком Хабра (после Карловского)


      1. cmyser Автор
        05.04.2026 09:54

        хахахахахаха))


  1. fransua
    05.04.2026 09:54

    Про сравнение памяти для объектов - второй вариант будет отрендерен в какой-нибудь див и будет занимать те же мегабайты наверное. А если виртуализация и он не будет рендериться, то и CE так же можно не рендерить. Логика переходит в контейнер выше.

    Про композицию - делаем функции renderHeader, renderBody, renderFooter и переопределяем одну в наследнике. Лет 40 этой практике наверное.


    1. cmyser Автор
      05.04.2026 09:54

      C виртуализацией не всё так просто
      бенч https://t.me/giper_dev/11/288
      статья про неё https://mol.hyoo.ru/#!section=docs/=gjboo1_xhyrnq

      Про композицию абсолютно согласен, тут поинт скорее в том что это архитектурное решение, то есть разработчик никак не может сделать плохо ( как я показал )
      ну а чем меньше ошибок может допустить програмист и отловить компилятор - тем лучше



      Опять же аппеляция к тому что не надо думать и писать "лишний" код


      1. fransua
        05.04.2026 09:54

        Тогда о чем статья?


        1. cmyser Автор
          05.04.2026 09:54

          не понял вопрос, может выйдет раскрыть ?


          1. fransua
            05.04.2026 09:54

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


            1. cmyser Автор
              05.04.2026 09:54

              в статье есть еще аргументы)
              про память - аргумент выдерживает критику ( веб компоненты обязаны занимать эту память, а с js обьектом можно много манипуляций до рендера провернуть )
              про jit - тоже
              про pull push реактивность тоже


              1. fransua
                05.04.2026 09:54

                CE отлично дружат с JS объектами, совершайте манипуляции в JS объекте. Про jit не вижу в статье.

                WC ( Web components ) используют push семантику для реактивности. Pull думаю врядли буду поддерживать. Это ваше мнение. В CE нет реактивности, но никто не мешает в них добавить mobx, rxjs или вот ваш mol.state, наверное тоже можно?


                1. cmyser Автор
                  05.04.2026 09:54

                  Про jit второй пункт

                  Можно попробовать подключить что то из мола, но всё равно как то проталкивать изменения нужно будет ( опять же лишняя абстракция над мол компонентами выходит, тестируемость теряется, html придется парсить )

                  Mobx хорош, rxjs не очень хорош ( делает много лишней работы ) https://page.hyoo.ru/#!=3ia3ll_rcpl7b/View’3ia3ll_rcpl7b’.Details=RxJS


                  1. fransua
                    05.04.2026 09:54

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


                    1. cmyser Автор
                      05.04.2026 09:54

                      $mol использует абстракцию к dom, обращений к dom на порядок меньше. Мост на .cpp не нужен ( jit ) ( понятно что js вызовы попадают на jit, я же писал про другое, про .cpp мост )

                      обьяснение примера с выделелнием писем из статьи

                      еще аргументы:
                      парсинг html
                      кол-во строк кода
                      pull семантика архитектурно не поддерживается WC
                      композиция - в $mol это архитекрутное решение ( програмисту не нужно думать об этом ) Что бы повторить это на другом фреймворке, нужно выполнить кратно больше работы. И думать тоже нужно больше. Причем всегда.


                      1. fransua
                        05.04.2026 09:54

                        Вы сравниваете mol с каким-то другим фреймворком, с Lit? Тогда при чем тут CE и WC?

                        pull семантика архитектурно не поддерживается WC

                        Чем она не поддерживается, CE или ShadowDom? Pull семантика у стейт менеджера, в WC нет дефолтного стейт менеджера, можно брать любой. Парсинг html - это про lit Кол-во строк кода - по сравнению чего с чем? Опять видимо Lit vs mol. Ну так и назовите статью “Вздор про lit и восхваление Mol”


                      1. 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 сломан"


                        про парсинг да, но я не видел без него реализации на веб компонентах

                        строки кода отражают зависимость от выбранной семантики


                      1. fransua
                        05.04.2026 09:54

                        1. Push - как и все события dom. В mol наверное тоже они обрабатываются. Дальше оборачивается в push/pull в зависимости от statemanager. Не все через атрибуты.

                        2. JS объект не отрендеришь, тебе нужен див - оверхеда нет

                        3. Obj.title потом все равно вызовет div.textContent. И наоборот customElement.data.title быстрый.


                      1. cmyser Автор
                        05.04.2026 09:54

                        так мы ни к чему не придём)
                        нужен бенч)

                        было бы интересно скооперироваться и подготовить такой ? можем потом статью выпустить


                      1. fransua
                        05.04.2026 09:54

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


                      1. cmyser Автор
                        05.04.2026 09:54

                        ага, я вот тут начал https://github.com/b-on-g/todomvc-compare

                        там как раз симбиот лит и мол


            1. cmyser Автор
              05.04.2026 09:54

              если добавить виртуализацию к СЕ , получиться $mol но с оверхэдом, лишней абстракцией
              смысла маловато в этом


              1. fransua
                05.04.2026 09:54

                Чтобы получить mol к ce нужно добавить mol и выкинуть ce.


                1. cmyser Автор
                  05.04.2026 09:54

                  на моле тоже можно делать веб компоненты


  1. ulisma
    05.04.2026 09:54

    Я работал с Lit, но совсем не понял, что там такого ценного. Компоненты JS можно и так писать.

    Может и есть что-то очень полезное в Web Component, но лично я ничего такого не нашел.


    1. cmyser Автор
      05.04.2026 09:54

      лит добавляет реактивность для веб компонент
      вроде у него еще и своя модель компонент есть, глубоко не копал


  1. i360u
    05.04.2026 09:54

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


    1. 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 вышло щас


      1. i360u
        05.04.2026 09:54

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


        1. cmyser Автор
          05.04.2026 09:54

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


        1. misterzsm
          05.04.2026 09:54

          Подтверждаю: это настоящий человек - знаю его ирл)


  1. Artur_frontDev
    05.04.2026 09:54

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


    1. cmyser Автор
      05.04.2026 09:54

      я так же думаю, но почему многим это важно ( вот тут https://habr.com/ru/articles/1014708/#comment_29737390 )

      эти многие реально смотрят на https://github.com/krausest/js-framework-benchmark
      и думают что там реальная производительность показывается

      тут фрустрация и отторжение из за "попытки обмануть" бенч этот
      но если бенч плохой, зачем он нужен

      я считаю лучше применять всё что можно, все допинги, и показывать реальную максимальную производительность JS. Она тоже не бесконечна, но насколько Не ? никто не знает. все упёрлись в html

      по моим расчётам на моле можно свою OC написать, и она будет быстрее и стабильнее многих ныне живущих) ( и даже уведомления будут сразу прилетать и колокольчик краситься )


  1. steel835
    05.04.2026 09:54

    Не стоит рендерить то - что не видно.

    Да, я именно так и думаю, когда поиск по странице ничего не находит, если это не на экране /s


    1. cmyser Автор
      05.04.2026 09:54

      плюсик в карму) согласен)

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


      1. SergeyBondar93
        05.04.2026 09:54

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


        1. cmyser Автор
          05.04.2026 09:54

          Да, поиск есть, можно на иконку нажать и откроется ввод

          На сочетание клавиш по идее тоже должно работать, но я не знаю как нажать на телефоне его )


  1. misterzsm
    05.04.2026 09:54

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


    1. cmyser Автор
      05.04.2026 09:54

      вопрос приоритетов
      для ssg я бы всё равно конечно мол взял ( темы\локализация\адаптивность ) по дефолту идут

      А вот WC если нужно куда в уже существующий проект пропихнуть то почему бы и нет
      я бы даже к себе в $mol приложение попробовал сунуть


  1. FireLynx
    05.04.2026 09:54

    Вы так сподвигнете наконец изучить веб-компоненты и попробовать их на чём-нибудь реальном


    1. cmyser Автор
      05.04.2026 09:54

      это же хорошо)
      на $mol, кстати, тоже можно их использовать, и писать )


  1. ecalcutin
    05.04.2026 09:54

    Хорошая попытка, но нет. Привычнее Vue/React.


    1. cmyser Автор
      05.04.2026 09:54

      попробую как нибудь еще)


    1. DimNS
      05.04.2026 09:54

      .../Svelte )


  1. Sol0Zon3
    05.04.2026 09:54

    А как всё-таки читается это название? $mol типа маленький? Или тут доллар + моль (единица вещества)? Или типа как jQuery, но маленький?


    1. cmyser Автор
      05.04.2026 09:54

      как нравится) я по простому пишу - мол
      но задумка да, $mol = small ( маленький )


  1. tzlom
    05.04.2026 09:54

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


    1. cmyser Автор
      05.04.2026 09:54

      Как аргумент слабовато, это скорее мнение

      Мне тоже сначала было необычно, но стоило понять что этот "непонятный" код декларативной описывает интерфейс и превращается в js, как сразу стало понятнее и проще

      Тут бы хорошо реальное исследование провести


      1. tzlom
        05.04.2026 09:54

        Ну сделайте опрос, тупо по коду из этой статьи, кому понятно что у TodoApp есть footer и кому понятно что у $hyoo_todomvc есть foot (мне вот вообще непонятно почему у него есть нога), ну и попробуйте объяснить откуда она взялась.

        Дальше больше - вы пишете что в MyTodoApp у вас не вышло заменить только футер. Конечно можно было бы вызвать родительский render и перетряхнуть его результат как нужно, например поменяв хедер и футер местами в зависимости от времени суток.

        Попробуйте такое сделать в вашем декларативном описании.


        1. cmyser Автор
          05.04.2026 09:54


          Согласен что не понятно ( по этому поводу есть предложения, сделать нагляднее ) надо исходники компонента смотреть что бы узнать какие свойства у него есть, тут стоит относиться к компоненту как к либе с API который нужно узнать

          от времени суток - это будет уже в ts, делается не сложно
          примерно в таком духе



        1. cmyser Автор
          05.04.2026 09:54

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


          1. tzlom
            05.04.2026 09:54

            Я читал статью о нем и понимаю как он работает, проблема в том, что он не более чем json в смешной обёртке. Представьте что вместо шаблонов будет json с экзотическими полями - на сколько доступно это будет?

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

            Можно ли sub вызвать в произвольном месте и будет ли он всегда работать?

            Как будет выглядеть код, который заменяет <sub> на <sub>/</sub> ?