<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Kirill Novgorodtsev</title>
    <description>The latest articles on DEV Community by Kirill Novgorodtsev (@kirill_novgorodtsev_f9433).</description>
    <link>https://dev.to/kirill_novgorodtsev_f9433</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2632870%2F6033d1d9-1954-4b3a-a331-659d2e8cdfc4.jpg</url>
      <title>DEV Community: Kirill Novgorodtsev</title>
      <link>https://dev.to/kirill_novgorodtsev_f9433</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kirill_novgorodtsev_f9433"/>
    <language>en</language>
    <item>
      <title>What's Actually Wrong with Web Components</title>
      <dc:creator>Kirill Novgorodtsev</dc:creator>
      <pubDate>Sun, 05 Apr 2026 12:59:54 +0000</pubDate>
      <link>https://dev.to/kirill_novgorodtsev_f9433/whats-actually-wrong-with-web-components-4m8e</link>
      <guid>https://dev.to/kirill_novgorodtsev_f9433/whats-actually-wrong-with-web-components-4m8e</guid>
      <description>&lt;p&gt;Ladies and gentlemen, we &lt;a href="https://habr.com/ru/articles/1019206/" rel="noopener noreferrer"&gt;continue digging&lt;/a&gt; into the intricacies of web components. I made a &lt;a href="https://github.com/b-on-g/todomvc-compare" rel="noopener noreferrer"&gt;bench here — comparing frameworks&lt;/a&gt; ($mol/Lit/Symbiote) on TodoMVC. Seems like we're talking about one thing, but the bench is about something else, right? Nope — to understand web components you need frameworks that put them front and center, the ones that "bet on them."&lt;/p&gt;

&lt;p&gt;Here's what I managed to figure out:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First. Memory:&lt;/strong&gt; 124 bytes per web component, and 16 bytes per JS object. An order of magnitude difference — that's a lot, and without virtualization the interface will most likely lag.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Lit: each todo-item is an HTMLElement (C++ heap, ~124 bytes minimum)&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;customElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;todo-item&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TodoItem&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;LitElement&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// &amp;lt;todo-item&amp;gt; immediately allocates a DOM node on createElement&lt;/span&gt;

&lt;span class="c1"&gt;// Symbiote: same deal, each &amp;lt;todo-item&amp;gt; = HTMLElement&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TodoItem&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Symbiote&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nx"&gt;TodoItem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;todo-item&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// $mol: component is a JS object. DOM is created ONLY on render.&lt;/span&gt;
&lt;span class="c1"&gt;// 1000 tasks in the model ≠ 1000 DOM elements&lt;/span&gt;
&lt;span class="c1"&gt;// $mol renders only visible components (virtualization)&lt;/span&gt;
&lt;span class="nf"&gt;task_rows&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;task_ids_filtered&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Task_row&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Custom Element&lt;/th&gt;
&lt;th&gt;js/mol_view&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1,000 tasks: ~124 KB just for nodes&lt;/td&gt;
&lt;td&gt;~16 KB for objects&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10,000 tasks: ~1.2 MB&lt;/td&gt;
&lt;td&gt;~160 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;And that's just the base nodes — without text nodes, attributes, styles, and event listeners, which also get allocated in the C++ heap. The Custom Elements spec requires &lt;code&gt;class MyEl extends HTMLElement&lt;/code&gt;. You can't create a CE without a DOM node.&lt;/p&gt;

&lt;p&gt;This same argument is explored &lt;a href="https://nolanlawson.com/2024/09/28/web-components-are-okay/" rel="noopener noreferrer"&gt;here&lt;/a&gt; by the author of SolidJS. Below is the response from the article's author:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"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."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Put simply — want performance? Don't use web components.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Second&lt;/strong&gt;, besides increased memory consumption, we lose JIT optimization.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;th&gt;How much worse&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;obj.title = x (JS object)&lt;/td&gt;
&lt;td&gt;~1–2 ns&lt;/td&gt;
&lt;td&gt;baseline — 1x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;element.textContent = x (DOM)&lt;/td&gt;
&lt;td&gt;~30–60 ns&lt;/td&gt;
&lt;td&gt;30x worse&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;element.setAttribute('class', x)&lt;/td&gt;
&lt;td&gt;~50–100 ns&lt;/td&gt;
&lt;td&gt;50x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;element.style.color = x&lt;/td&gt;
&lt;td&gt;~80–150 ns&lt;/td&gt;
&lt;td&gt;80x&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Task: select all (relevant for email, for example, which just can't delete all messages at once if they don't fit on one page). Here's how it degrades:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tasks&lt;/th&gt;
&lt;th&gt;Lit&lt;/th&gt;
&lt;th&gt;$mol&lt;/th&gt;
&lt;th&gt;Difference&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;60 µs (microseconds)&lt;/td&gt;
&lt;td&gt;3 µs&lt;/td&gt;
&lt;td&gt;20x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1,000&lt;/td&gt;
&lt;td&gt;600 µs&lt;/td&gt;
&lt;td&gt;8 µs&lt;/td&gt;
&lt;td&gt;75x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10,000&lt;/td&gt;
&lt;td&gt;8 ms&lt;/td&gt;
&lt;td&gt;12 µs&lt;/td&gt;
&lt;td&gt;650x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100,000&lt;/td&gt;
&lt;td&gt;120 ms (lag — if a frame takes more than 16 ms)&lt;/td&gt;
&lt;td&gt;15 µs&lt;/td&gt;
&lt;td&gt;8,000x&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;And a question for readers. Should we even orient ourselves around &lt;a href="https://github.com/krausest/js-framework-benchmark" rel="noopener noreferrer"&gt;js-framework-benchmark&lt;/a&gt; in this case? I think not. You shouldn't render what isn't visible. They're fighting over pennies there, and everyone knows you don't penny-pinch. And let's recall the perf quote above.&lt;/p&gt;

&lt;p&gt;And a code example. Here we're also parsing HTML... (that's bad)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Lit: update through DOM property&lt;/span&gt;
&lt;span class="c1"&gt;// element.textContent = newValue  ← C++ binding, slow path&lt;/span&gt;
&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`&amp;lt;label&amp;gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &amp;lt;/label&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// Under the hood, Lit does: node.textContent = value&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// $mol: update through JS property&lt;/span&gt;
&lt;span class="c1"&gt;// this.title() — reads from memo cache (JS heap)&lt;/span&gt;
&lt;span class="c1"&gt;// DOM updates in a batch via requestAnimationFrame&lt;/span&gt;
&lt;span class="nf"&gt;task_title&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;?)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...)&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Moving on. WC (Web Components) use push semantics for reactivity. Pull — I doubt they'll ever support it. What does this affect? The number of lines of code &lt;strong&gt;written by you&lt;/strong&gt;. And all people make mistakes — there's a saying for a reason: "The best code is code that was never written." Let's look at the &lt;a href="https://github.com/b-on-g/todomvc-compare" rel="noopener noreferrer"&gt;bench&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F269zuktans8zvvisx8xs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F269zuktans8zvvisx8xs.png" alt="Benchmark comparison" width="800" height="315"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Respectable.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;And a code example, for clarity.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Lit: Push via EventTarget + requestUpdate&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Todos&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;EventTarget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nf"&gt;notifyChange&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dispatchEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;change&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// push!&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;todos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nf"&gt;notifyChange&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// ← manual push&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// Each component subscribes via @updateOnEvent("change")&lt;/span&gt;
&lt;span class="c1"&gt;// On ANY change ALL subscribers get notified&lt;/span&gt;

&lt;span class="c1"&gt;// Symbiote: Push via EventTarget similarly&lt;/span&gt;
&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;change&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="c1"&gt;// $mol: Pull — automatic dependency graph&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nx"&gt;$mol_mem&lt;/span&gt;
&lt;span class="nf"&gt;groups_completed&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Automatically tracks dependencies:&lt;/span&gt;
    &lt;span class="c1"&gt;// task_ids() → task() → groups&lt;/span&gt;
    &lt;span class="c1"&gt;// Recalculates ONLY when dependencies change&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;task_ids&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;completed&lt;/span&gt;&lt;span class="p"&gt;)].&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// No EventTarget needed, no manual subscriptions&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not for nothing did Vue.js choose pull reactivity.&lt;/p&gt;

&lt;p&gt;Let's touch on testability. To test a web component — you need to render it. Maximally inefficient. I think we all hate it when tests take forever.&lt;/p&gt;

&lt;p&gt;Let's look at an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// $mol: tests WITHOUT DOM. Component is just an object.&lt;/span&gt;
&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;task add&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;$hyoo_todomvc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;make&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;$mol_assert_equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;task_rows&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;at&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;title&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// No document.createElement, no connectedCallback&lt;/span&gt;
&lt;span class="c1"&gt;// Just method calls and state checks&lt;/span&gt;

&lt;span class="c1"&gt;// Lit: testing requires DOM&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;todo-app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// connectedCallback!&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;updateComplete&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// wait for render!&lt;/span&gt;
&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shadowRoot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.new-todo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)...&lt;/span&gt; &lt;span class="c1"&gt;// access through Shadow DOM!&lt;/span&gt;

&lt;span class="c1"&gt;// hyoo_todomvc has 140 lines of tests.&lt;/span&gt;
&lt;span class="c1"&gt;// Lit and Symbiote implementations — 0. (and they have even more code)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;About inheritance.&lt;/p&gt;

&lt;p&gt;How it works in Lit with those "convenient little web components":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TodoApp&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;LitElement&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
         &lt;span class="c1"&gt;// The entire template — a monolithic block. 40+ lines of HTML.&lt;/span&gt;
         &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`
             &amp;lt;header&amp;gt;...&amp;lt;/header&amp;gt;
             &amp;lt;section&amp;gt;
                 &amp;lt;ul&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;todos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`...`&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/ul&amp;gt;
             &amp;lt;/section&amp;gt;
             &amp;lt;footer&amp;gt;
                 &amp;lt;span&amp;gt;...&amp;lt;/span&amp;gt;
                 &amp;lt;ul&amp;gt;...&amp;lt;/ul&amp;gt;
                 &amp;lt;button&amp;gt;...&amp;lt;/button&amp;gt;
             &amp;lt;/footer&amp;gt;
         `&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
     &lt;span class="p"&gt;}&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;

 &lt;span class="c1"&gt;// Want to change just the footer?&lt;/span&gt;
 &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyTodoApp&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;TodoApp&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
         &lt;span class="c1"&gt;// Can't say "take parent's render(), replace footer."&lt;/span&gt;
         &lt;span class="c1"&gt;// The only option — copy ALL 40 lines&lt;/span&gt;
         &lt;span class="c1"&gt;// and change the piece you need.&lt;/span&gt;
         &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`
             &amp;lt;header&amp;gt;...&amp;lt;/header&amp;gt;         // copy
             &amp;lt;section&amp;gt;...&amp;lt;/section&amp;gt;        // copy
             &amp;lt;footer&amp;gt;MY FOOTER&amp;lt;/footer&amp;gt;   // all for this
         `&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
     &lt;span class="p"&gt;}&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;How it works in $mol:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Base component: scrollable area&lt;/span&gt;
&lt;span class="nx"&gt;$mol_scroll&lt;/span&gt;
    &lt;span class="nx"&gt;sub&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;           &lt;span class="err"&gt;←&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;
    &lt;span class="nx"&gt;scroll_top&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;    &lt;span class="err"&gt;←&lt;/span&gt; &lt;span class="nx"&gt;scroll&lt;/span&gt; &lt;span class="nx"&gt;position&lt;/span&gt;
    &lt;span class="nx"&gt;event_scroll&lt;/span&gt;     &lt;span class="err"&gt;←&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt;

&lt;span class="c1"&gt;// TodoMVC INHERITS $mol_scroll and replaces only sub (child elements):&lt;/span&gt;
&lt;span class="nx"&gt;$hyoo_todomvc&lt;/span&gt; &lt;span class="nx"&gt;$mol_scroll&lt;/span&gt;
    &lt;span class="nx"&gt;sub&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;Page&lt;/span&gt; &lt;span class="nx"&gt;$mol_list&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;

&lt;span class="c1"&gt;// Scroll, position, handler — all inherited for free.&lt;/span&gt;
&lt;span class="c1"&gt;// We only overrode the CONTENT.&lt;/span&gt;

&lt;span class="c1"&gt;// You can go deeper — inherit $hyoo_todomvc itself and replace, say, just the footer:&lt;/span&gt;
&lt;span class="nx"&gt;$my_custom_todo&lt;/span&gt; &lt;span class="nx"&gt;$hyoo_todomvc&lt;/span&gt;
    &lt;span class="nx"&gt;foot&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;My_footer&lt;/span&gt; &lt;span class="nx"&gt;$mol_view&lt;/span&gt;
&lt;span class="c1"&gt;// Everything else (list, input, filters) — inherited as is.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same goes for inheriting logic and styles (uniquely cascading). Zero copy-paste and boilerplate.&lt;/p&gt;

&lt;p&gt;And to wrap up, I'd like to argue with one statement. Below is my loose quote from &lt;a href="https://habr.com/ru/users/i360u/" rel="noopener noreferrer"&gt;@i360u&lt;/a&gt;'s text (&lt;a href="https://habr.com/ru/articles/1019206/" rel="noopener noreferrer"&gt;first link&lt;/a&gt; in the article):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Any engineering decision is a compromise. The existence of 'cons' doesn't outweigh all possible 'pros' by default."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To some extent you can agree with that. If not for one "BUT." We all have the same task — build the most convenient, fast, beautiful application, without legacy, with clean code that's easy to maintain, and all that jazz. Once more — the task is the same for everyone. And you need to look at who solves it best &lt;strong&gt;across the entirety&lt;/strong&gt; of the frontend world. I know one such solution.&lt;/p&gt;

&lt;p&gt;It's $mol — everything else is worse. That's a fact.&lt;/p&gt;

&lt;p&gt;The whole "vendor lock-in," "scary and confusing to learn something new" — those are childish excuses to avoid facing facts. Everywhere in programming you have to learn new things. And there's no vendor lock at all. Think about it. Kirill. Subscribe.&lt;/p&gt;




&lt;h2&gt;
  
  
  FAQ (from the comments)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;"But if you use virtualization, you won't render those CE nodes either — same thing, no?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not quite. With virtualization the complexity shifts to the container above. And it's &lt;a href="https://github.com/nickmessing/todomvc-compare" rel="noopener noreferrer"&gt;not that straightforward&lt;/a&gt; in practice — $mol has built-in virtualization by default, while in WC-based frameworks you have to architect it yourself. Every extra architectural decision you have to make is another chance to mess up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"For composition — just split render into renderHeader / renderBody / renderFooter and override one in the subclass. This pattern is 40 years old."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Absolutely agree. The point here is more that $mol enforces this as an &lt;strong&gt;architectural constraint&lt;/strong&gt; — the developer physically can't produce that monolithic-template anti-pattern I showed. The fewer mistakes a programmer can make, and the more the compiler can catch — the better. Again, it's about not having to think about or write "extra" code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"You're generalizing too radically. The problem isn't web components themselves — it's how people use them. Naively rendering thousands of elements through DOM will hit limits regardless of approach."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I think so too. But many people actually look at &lt;a href="https://github.com/krausest/js-framework-benchmark" rel="noopener noreferrer"&gt;js-framework-benchmark&lt;/a&gt; and believe it reflects real-world performance. That causes frustration — it feels like the benchmark is misleading. If the benchmark is bad, why does it exist? I think we should use every trick available and show the real maximum performance of JS. It's not infinite either, but how far can it go? Nobody knows — everyone's stuck on HTML. By my estimates, you could write an entire OS on $mol, and it would be faster and more stable than many existing ones. (And notifications would arrive instantly with the bell icon updating too.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"What about Ctrl+F page search? You can't find what's not rendered."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Fair point. But funnily enough, in $mol search actually works — it overrides the standard browser search by default and searches through the full data, not just the visible DOM.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Isn't $mol just for SPAs? For sites with interactive elements — take SSG + web components."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A matter of priorities. For SSG I'd still pick $mol — theming, localization, and responsive layout come out of the box. As for WC — if you need to shove a component into an existing project, sure, why not. I'd even try plugging one into my own $mol app.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Where are the hidden benchmark results that were bad for $mol?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The initial benchmark was set up incorrectly — there were junk files in the root that got pulled into all bundles locally. The real size turned out to be ~185 KB. Honestly noted it in the &lt;a href="https://github.com/b-on-g/todomvc-compare" rel="noopener noreferrer"&gt;bench&lt;/a&gt;. You can verify by building it yourself. I'd welcome a real comparison from an independent author — that's how truth is born, the scientific way.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webcomponents</category>
      <category>mol</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
