<?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.us-east-2.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>Why $mol?</title>
      <dc:creator>Kirill Novgorodtsev</dc:creator>
      <pubDate>Sun, 07 Jun 2026 23:27:53 +0000</pubDate>
      <link>https://dev.to/kirill_novgorodtsev_f9433/why-mol-3jmd</link>
      <guid>https://dev.to/kirill_novgorodtsev_f9433/why-mol-3jmd</guid>
      <description>&lt;p&gt;TLDR — $mol comes with reactivity, local storage, offline, and themes all in one bundle. There are downsides, but they're almost all about community and tooling, not the code itself. You can live with them.&lt;/p&gt;

&lt;p&gt;Let me start with those — feels more honest. No particular order.&lt;/p&gt;

&lt;p&gt;There's no CDN build of $mol, the kind where you drop a single &lt;code&gt;&amp;lt;script src=...&amp;gt;&lt;/code&gt; into HTML and start writing UI like you would with Vue. The class name in $mol is tied to its filesystem path (&lt;code&gt;$mol_button&lt;/code&gt; lives in &lt;code&gt;mol/button/&lt;/code&gt;), and the bundler builds the bundle from those paths. A CDN variant is theoretically possible, but someone would have to rewrite the bundler, and no enthusiast has stepped up.&lt;/p&gt;

&lt;p&gt;Not enough "serious" public cases. Corporate apps exist but are hidden behind NDAs, leaving mostly demos and pet projects in public. From what I can show:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://web.giper.dev/" rel="noopener noreferrer"&gt;web.giper.dev&lt;/a&gt; — a product ecosystem ( Google-style ) by $mol's author.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://b-on-g.github.io/blitz" rel="noopener noreferrer"&gt;b-on-g.github.io/blitz&lt;/a&gt; — my real-time quiz built on $mol + Giper Baza. Took a long time, polished it a lot.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Not much built-in tooling. Working in $mol is pleasant, but only once you're already "in the know" — it's rough for newcomers, the IDE support is thin. VS Code has official extensions but no go-to-definition or syntax hints.&lt;/p&gt;

&lt;p&gt;Error stack traces look genuinely awful)&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%2Fgithub.com%2Fuser-attachments%2Fassets%2Faa09a9ff-0001-490e-9571-225b40e65ee8" class="article-body-image-wrapper"&gt;&lt;img width="799" height="495" alt="image" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fuser-attachments%2Fassets%2Faa09a9ff-0001-490e-9571-225b40e65ee8"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;An error stack trace in $mol&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I don't know if this can be fixed, we need a volunteer) Attempts have been made, but it needs more time — basically the noise and duplication just need to be cleaned up. That said, debugging in the browser is pretty straightforward thanks to human-readable class names.&lt;/p&gt;

&lt;p&gt;Some of the tooling I built myself:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;LSP — &lt;code&gt;npm i -g view-tree-lsp@latest&lt;/code&gt;, plus app scaffolding in one command: &lt;code&gt;npm create view-tree-lsp@latest bog/myapp -- --no-docker --no-tauri&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/Dev-cmyser/tree-sitter-viewtree.git" rel="noopener noreferrer"&gt;tree-sitter grammar&lt;/a&gt; for view.tree.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://zed.dev/extensions/viewtree" rel="noopener noreferrer"&gt;Zed extension&lt;/a&gt; built on top of them, with working go-to-definition, highlighting, and other niceties. 30k downloads there — probably bots indexing everything for tests)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The great and dreadful view.tree ( the format for declaratively describing components ) I don't count as a drawback. Compare it with your first time seeing JSX, for example. view.tree just describes regular JS classes — in a way that makes the relationships between components easier to see and avoids boilerplate.&lt;/p&gt;

&lt;p&gt;Plus — a nice separation of layers: view.tree is the abstraction over HTML, ts is the logic, css.ts is the styles. Less to get confused about. Here's a quick example:&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%2Fgithub.com%2Fuser-attachments%2Fassets%2Fb515f349-015a-4fef-a1ee-218eb4048c78" class="article-body-image-wrapper"&gt;&lt;img width="2426" height="764" alt="Pasted image 20260401223034" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fuser-attachments%2Fassets%2Fb515f349-015a-4fef-a1ee-218eb4048c78"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;mol.hyoo.ru — tree playground&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You can see a base component ( a div by default, but it can be anything ) and its compiled JS form. You can override class methods in your &lt;code&gt;.ts&lt;/code&gt;, or add new methods in either file.&lt;/p&gt;

&lt;p&gt;Basically, all the problems described above come down to a small community and weak marketing. Well — volunteers needed!)&lt;/p&gt;

&lt;p&gt;A more serious problem is npm integration. There are three ways to pull in a library right now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;through a CDN — painless, but no types,&lt;/li&gt;
&lt;li&gt;download the lib into your repo and commit it,&lt;/li&gt;
&lt;li&gt;use the community tool for reproducible builds ( I haven't tried it myself, ask in &lt;a href="https://t.me/mam_mol" rel="noopener noreferrer"&gt;@mam_mol_development&lt;/a&gt; ).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It just isn't native, it grates a bit. But the community keeps at it — there's ongoing work on the bundler.&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%2Fgithub.com%2Fuser-attachments%2Fassets%2F1071adff-549c-44c1-9626-c76fc31e97d2" class="article-body-image-wrapper"&gt;&lt;img width="800" height="800" alt="image" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fuser-attachments%2Fassets%2F1071adff-549c-44c1-9626-c76fc31e97d2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;People also write to me with this one a lot:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"There are no $mol jobs, and if there were, you couldn't find specialists."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Jobs, yes, almost none. But you can start a new project at your company or in a startup. And you don't need a "$mol specialist" — you need a TS developer who understands components and reactivity. The onboarding is 2–4 weeks, which is less than figuring out a stranger's React codebase with its Redux/Zustand/RTK/Tanstack zoo.&lt;/p&gt;

&lt;p&gt;There's also plenty of design work you'll do from scratch, even though $mol has plenty of ready-made components. But once you build it, you can reuse those components across apps like a unified UI kit. Designer disputes guaranteed — beauty requires sacrifice :)&lt;/p&gt;

&lt;p&gt;If design isn't a concern, you can throw together an interface very quickly from the available primitives — for an admin panel, for example.&lt;/p&gt;

&lt;p&gt;What really needs serious work is the documentation and getting started. I think ( and many people write the same ) this is the main entry barrier right now. Good docs are hard to write, but necessary so people can pick things up faster and don't leave disappointed. I took a shot at this myself — wrote a &lt;a href="https://github.com/hyoo-ru/mam/blob/master/bog/articles/getting-started.en.md" rel="noopener noreferrer"&gt;Getting Started&lt;/a&gt;, from cloning mam to a working app with offline and sync. How well it turned out, you can judge)&lt;/p&gt;

&lt;p&gt;About the toxic halo around $mol. A lot of people have run into hostile reactions under $mol posts — sometimes fair, sometimes not. The community itself is actually warm and friendly. Drop into &lt;a href="https://t.me/giper_dev" rel="noopener noreferrer"&gt;@giper_dev&lt;/a&gt; (Russian-speaking), or better, come hang out in person at &lt;a href="https://t.me/piterjs" rel="noopener noreferrer"&gt;@piterjs&lt;/a&gt;. In person it's hard to stay toxic anyway :) I also run free intro sessions on $mol — DM me.&lt;/p&gt;

&lt;p&gt;That's about it for the drawbacks. On to the upsides.&lt;/p&gt;

&lt;p&gt;I'll start from a tangent. I was listening to a React-focused podcast recently and remembered how much manual tuning modern React needs. For every component with a heavy render you reach for &lt;code&gt;useTransition&lt;/code&gt; or &lt;code&gt;useDeferredValue&lt;/code&gt; to keep the UI from lagging on thousands of rows. And even if you add virtualization, it's virtualization only for that one component. In $mol all rendering is fully virtual: components outside the viewport physically don't exist, that "tuning" is hidden under the hood.&lt;/p&gt;

&lt;p&gt;Compare the live Sierpinski Triangle demos by Karlovsky:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://nin-jin.github.io/sierpinski/stack.html" rel="noopener noreferrer"&gt;React&lt;/a&gt; — chokes during animation,&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://mol.js.org/perf/sierp/-/" rel="noopener noreferrer"&gt;$mol&lt;/a&gt; — no stutter.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Same scenario, same hardware. &lt;a href="https://github.com/nin-jin/sierpinski" rel="noopener noreferrer"&gt;Sources for both demos&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And here's the standard &lt;a href="https://nin-jin.github.io/js-framework-benchmark/webdriver-ts-results/table.html" rel="noopener noreferrer"&gt;js-framework-benchmark&lt;/a&gt;, where Karlovsky added $mol — virtual rendering tears everything else apart, because components outside the viewport simply don't exist.&lt;/p&gt;

&lt;p&gt;I had a burst of inspiration recently, wrote an earlier post about web components and put together a &lt;a href="https://github.com/b-on-g/todomvc-compare" rel="noopener noreferrer"&gt;benchmark&lt;/a&gt; that counts lines of code:&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%2Fgithub.com%2Fuser-attachments%2Fassets%2Fc8ef7068-125a-4d80-9607-9d902575ae1f" class="article-body-image-wrapper"&gt;&lt;img width="1766" height="672" alt="Pasted image 20260406235307" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fuser-attachments%2Fassets%2Fc8ef7068-125a-4d80-9607-9d902575ae1f"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now about reactivity. Most reactive systems have the same edge case: one invalid value in the computation graph can drag along its neighbors, even ones that formally don't depend on it. I'll show it on &lt;a href="https://svelte.dev/tutorial/svelte/numeric-inputs" rel="noopener noreferrer"&gt;Svelte&lt;/a&gt; because their own tutorial lets you try it hands-on.&lt;/p&gt;

&lt;p&gt;We break {a}, and {b} falls over:&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%2Fgithub.com%2Fuser-attachments%2Fassets%2F03258047-59b9-4931-a27f-e8dee0deb28f" class="article-body-image-wrapper"&gt;&lt;img width="1580" height="1658" alt="image" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fuser-attachments%2Fassets%2F03258047-59b9-4931-a27f-e8dee0deb28f"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;{b} doesn't depend on {a} in the code, but the effect cascades. You can catch the same thing in most top frameworks — it's usually fixed with manual &lt;code&gt;try/catch&lt;/code&gt; or by splitting things across components. When {a} comes back, the sum is recomputed.&lt;/p&gt;

&lt;p&gt;Tell me in the comments: if you split this across components, is the behavior the same? And how do other frameworks deal with it? I took the case from &lt;a href="https://youtu.be/6wYbYxBOuko?si=1yDEyRZB46Q5XJrz&amp;amp;t=515" rel="noopener noreferrer"&gt;this video&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now the same thing in &lt;a href="https://b-on-g.github.io/sum" rel="noopener noreferrer"&gt;$mol&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%2Fgithub.com%2Fuser-attachments%2Fassets%2Fc21d3b33-d5e0-49cb-b529-5ed0296f3456" class="article-body-image-wrapper"&gt;&lt;img width="2034" height="1758" alt="image" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fuser-attachments%2Fassets%2Fc21d3b33-d5e0-49cb-b529-5ed0296f3456"&gt;&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%2Fgithub.com%2Fuser-attachments%2Fassets%2F88f664f7-8c5a-4d37-bb34-a65bf2ab2fe8" class="article-body-image-wrapper"&gt;&lt;img width="482" height="376" alt="Pasted image 20260402000509" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fuser-attachments%2Fassets%2F88f664f7-8c5a-4d37-bb34-a65bf2ab2fe8"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;{b} is recomputed independently.&lt;/em&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%2Fgithub.com%2Fuser-attachments%2Fassets%2F03a68336-c4ef-462d-93ff-66da7b8325e4" class="article-body-image-wrapper"&gt;&lt;img width="420" height="376" alt="image" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fuser-attachments%2Fassets%2F03a68336-c4ef-462d-93ff-66da7b8325e4"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;When {a} comes back, the sum is recomputed too.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In $mol this problem is solved architecturally. Every getter is an isolated atom: if one fails, the others don't recompute and don't even know about it. No need to write try-catch, everything fixes itself without a reload. &lt;a href="https://page.hyoo.ru/#!=vuypgx_v55bpt" rel="noopener noreferrer"&gt;A deeper look at reactivity&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  OOP, but every method is an extension point
&lt;/h3&gt;

&lt;p&gt;In $mol, reactivity isn't at the variable level — it's at the class method level. A getter automatically becomes a computed reactive cell: it reads data → records dependencies → recomputes when they change. No &lt;code&gt;useState&lt;/code&gt;, &lt;code&gt;useMemo&lt;/code&gt;, &lt;code&gt;useEffect&lt;/code&gt;, or &lt;code&gt;$derived&lt;/code&gt; — that's hidden under the hood.&lt;/p&gt;

&lt;p&gt;Reusing a component means writing its name in view.tree. No copy-paste, no &lt;code&gt;npm install&lt;/code&gt;, even across repos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$my_dashboard $mol_page
    body /
        &amp;lt;= Chart $hyoo_crus_chart_pie
        &amp;lt;= Edit $bog_some_editor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works because $mol modules are addressed by class name, and the mam bundler pulls in what it needs. The line between "your" code and "someone else's" code disappears.&lt;/p&gt;

&lt;h3&gt;
  
  
  CSS in TS
&lt;/h3&gt;

&lt;p&gt;I'm not great at CSS — that's my personal pain, I don't want to memorize a pile of rules. In $mol, styles are just a TS object with typed properties. Don't know a property? Just tab through autocomplete and it works) An AI assistant handles it well too — it can self-correct, which is genuinely convenient.&lt;/p&gt;

&lt;h3&gt;
  
  
  Offline in one line
&lt;/h3&gt;

&lt;p&gt;In &lt;code&gt;app.meta.tree&lt;/code&gt; you write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;include &lt;span class="se"&gt;\/&lt;/span&gt;mol/offline/install
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it — the app installs as a PWA and runs offline. The manifest is assembled by the bundler.&lt;/p&gt;

&lt;h3&gt;
  
  
  Localization
&lt;/h3&gt;

&lt;p&gt;In view.tree you put &lt;code&gt;@&lt;/code&gt; before a string:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$my_page $mol_page
    title @ \Hello, World!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;view.tree is transpiled to JS, and this string becomes a method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/** title @ \Hello, World! */&lt;/span&gt;
&lt;span class="nf"&gt;title&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;$mol_locale&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$my_page_title&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The en locale is extracted from default values automatically, and the key is built from the component name + property name. That part is important — the identifier is human-readable and immediately points to where it's used, both in the filesystem and in the component. Drop a &lt;code&gt;page.locale=ru.json&lt;/code&gt; next to it with translations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"$my_page_title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Привет, мир!"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it.&lt;/p&gt;

&lt;p&gt;This works because view.tree is a strict DSL. The React default is i18next: you write &lt;code&gt;t('greeting.title')&lt;/code&gt;, you maintain the keys and dictionary yourself. To automate extraction, you add &lt;code&gt;i18next-parser&lt;/code&gt;, which greps the source with regexes and breaks on anything dynamic.&lt;/p&gt;

&lt;h3&gt;
  
  
  No config files
&lt;/h3&gt;

&lt;p&gt;A $mol project has no vite.config.ts, no per-app tsconfig. The mam bundler knows everything from conventions: filename — type, path from root — class name. No config wrangling.&lt;/p&gt;

&lt;p&gt;A new module = a folder with a pair of &lt;code&gt;.ts&lt;/code&gt;/&lt;code&gt;.view.tree&lt;/code&gt; files. mam generates &lt;code&gt;package.json&lt;/code&gt; itself if it doesn't exist.&lt;/p&gt;

&lt;h3&gt;
  
  
  Strict architecture
&lt;/h3&gt;

&lt;p&gt;In $mol, the class name is firmly tied to its filesystem path. The class &lt;code&gt;$my_app_button&lt;/code&gt; lives in &lt;code&gt;/my/app/button/button.ts&lt;/code&gt; — no other option. Want to name a class &lt;code&gt;Button&lt;/code&gt; and put it in &lt;code&gt;src/components/ui/button/index.ts&lt;/code&gt;? Not happening.&lt;/p&gt;

&lt;p&gt;It sounds like a constraint, but $mol just doesn't let you write spaghetti. The name is short and tells you where to look. From a class name in code, in git log, in a GitHub comment — you immediately know the file.&lt;/p&gt;

&lt;p&gt;On top of that is MVF (ModelView Fractal), the decomposition pattern behind &lt;code&gt;$mol_view&lt;/code&gt;. Each component is both a view for its parent and a model/controller for its children. Domain model lives separately: &lt;code&gt;$hyoo_talks_domain&lt;/code&gt; lives in &lt;code&gt;talks/domain/domain.ts&lt;/code&gt;, the view works with its objects, not raw data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// talks/talk.view.ts&lt;/span&gt;
&lt;span class="nf"&gt;domain&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="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$hyoo_talks_domain&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nf"&gt;chat&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nc"&gt;Chat&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;p&gt;Also, MAM itself isn't tied to the web. A module is a directory with sources in whatever — view.tree, GLSL shaders, CSS, JSON, files for other languages. You can teach the bundler any language; MAM only knows about the directory structure and inter-module dependencies.&lt;/p&gt;

&lt;h3&gt;
  
  
  The renderer isolates failures
&lt;/h3&gt;

&lt;p&gt;Every $mol component is its own error boundary. An exception in a getter, a timeout while loading, anything at all — a placeholder is shown for that one component, the rest of the UI stays alive.&lt;/p&gt;

&lt;p&gt;In React you'd wrap things in &lt;code&gt;&amp;lt;ErrorBoundary&amp;gt;&lt;/code&gt; and remember to do it. In $mol it's the default.&lt;/p&gt;

&lt;p&gt;Plus reactivity: when the value becomes valid again, the component re-renders on its own with no page reload. We already saw that in the Svelte example above.&lt;/p&gt;

&lt;h3&gt;
  
  
  One codebase, every platform
&lt;/h3&gt;

&lt;p&gt;The same $mol project builds for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Web&lt;/li&gt;
&lt;li&gt;Tauri (desktop on macOS/Windows/Linux, also iOS/Android)&lt;/li&gt;
&lt;li&gt;Chrome/Firefox extension (MV3)&lt;/li&gt;
&lt;li&gt;Telegram Mini App&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Tauri wrapper and extension manifest are already in the scaffold; you don't configure anything separately. My own VK Music (&lt;a href="https://b-on-g.github.io/vk/" rel="noopener noreferrer"&gt;bog/vk&lt;/a&gt;) ships both as an MV3 extension and as a gh-pages mirror — literally the same module.&lt;/p&gt;

&lt;p&gt;By the way, if you need a backend — there's Giper Baza: CRDT sync, tuned for $mol. That's a separate story, I'll write a dedicated post comparing it to other local-first solutions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why give it a try
&lt;/h3&gt;

&lt;p&gt;Not because of age — $mol is younger than React and Vue, same age as Angular. The point is elsewhere: in 10 years $mol has had one major release. React has had 19, Angular has had 21.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;$mol&lt;/th&gt;
&lt;th&gt;React&lt;/th&gt;
&lt;th&gt;Angular&lt;/th&gt;
&lt;th&gt;Vue&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;First release&lt;/td&gt;
&lt;td&gt;2016&lt;/td&gt;
&lt;td&gt;2013&lt;/td&gt;
&lt;td&gt;2016&lt;/td&gt;
&lt;td&gt;2014&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Major versions by 2026&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tree-shaking without setup&lt;/td&gt;
&lt;td&gt;✅ from day one&lt;/td&gt;
&lt;td&gt;⭕ removes unused&lt;/td&gt;
&lt;td&gt;⭕ inhibited, standalone since 2022&lt;/td&gt;
&lt;td&gt;⭕ removes unused&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Atoms / signals out of the box&lt;/td&gt;
&lt;td&gt;✅ from day one&lt;/td&gt;
&lt;td&gt;❌ only via MobX and friends&lt;/td&gt;
&lt;td&gt;⭕ added 2023&lt;/td&gt;
&lt;td&gt;✅ from day one&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;One failure doesn't kill the app&lt;/td&gt;
&lt;td&gt;✅ automatic&lt;/td&gt;
&lt;td&gt;⭕ ErrorBoundary since 2017&lt;/td&gt;
&lt;td&gt;❌ none&lt;/td&gt;
&lt;td&gt;⭕ errorCaptured manually since 2017&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Async suspension&lt;/td&gt;
&lt;td&gt;✅ automatic, any level&lt;/td&gt;
&lt;td&gt;⭕ Suspense, render-time only&lt;/td&gt;
&lt;td&gt;❌ via third-party RxLet, manual&lt;/td&gt;
&lt;td&gt;❌ mount-time only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Static typing&lt;/td&gt;
&lt;td&gt;✅ behavior + composition + styles (since 2020)&lt;/td&gt;
&lt;td&gt;❌ third-party typings&lt;/td&gt;
&lt;td&gt;✅ behavior only&lt;/td&gt;
&lt;td&gt;⭕ behavior only since 2020&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Inversion of control&lt;/td&gt;
&lt;td&gt;✅ typed contexts&lt;/td&gt;
&lt;td&gt;⭕ manual / renderProps&lt;/td&gt;
&lt;td&gt;✅ injections / contexts&lt;/td&gt;
&lt;td&gt;⭕ untyped contexts since 2017&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Behavior / composition / styling split&lt;/td&gt;
&lt;td&gt;✅ from day one&lt;/td&gt;
&lt;td&gt;❌ doesn't exist by design&lt;/td&gt;
&lt;td&gt;❌ partial&lt;/td&gt;
&lt;td&gt;❌ partial&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Behavior customization&lt;/td&gt;
&lt;td&gt;✅ inheritance or in-place&lt;/td&gt;
&lt;td&gt;⭕ manual extension points&lt;/td&gt;
&lt;td&gt;⭕ inheritance&lt;/td&gt;
&lt;td&gt;⭕ extension points since 2020&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Composition customization&lt;/td&gt;
&lt;td&gt;✅ inheritance or in-place&lt;/td&gt;
&lt;td&gt;⭕ manual extension points&lt;/td&gt;
&lt;td&gt;⭕ manual slots&lt;/td&gt;
&lt;td&gt;⭕ manual slots&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Styling customization&lt;/td&gt;
&lt;td&gt;✅ cascade via auto-generated attributes&lt;/td&gt;
&lt;td&gt;❌ manual extension points or classes&lt;/td&gt;
&lt;td&gt;❌ manual extension points or classes&lt;/td&gt;
&lt;td&gt;❌ manual extension points or classes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Visible-area-only rendering&lt;/td&gt;
&lt;td&gt;✅ lazy by default, fully virtual since 2020&lt;/td&gt;
&lt;td&gt;⭕ via third-party component&lt;/td&gt;
&lt;td&gt;⭕ via third-party component&lt;/td&gt;
&lt;td&gt;⭕ via third-party component&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Same code on client and server&lt;/td&gt;
&lt;td&gt;✅ from day one&lt;/td&gt;
&lt;td&gt;❌ different API, data prep required&lt;/td&gt;
&lt;td&gt;❓&lt;/td&gt;
&lt;td&gt;❓&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;There are no versions because there's no reason to keep copies around. Break an API — rename the module, and you can reuse up to 99% of the old code. Sounds wild but it works: no lockfile zoo, no "works on my machine", no weekly dependabot PR.&lt;/p&gt;

&lt;p&gt;If you recognized some of your own pain in there, drop by &lt;a href="https://t.me/giper_dev" rel="noopener noreferrer"&gt;@giper_dev&lt;/a&gt; (Russian-speaking) and we'll sort it out. Or just poke around the &lt;a href="https://mol.hyoo.ru/" rel="noopener noreferrer"&gt;playground&lt;/a&gt; — view.tree online, no install.&lt;/p&gt;

</description>
      <category>frontend</category>
      <category>webdev</category>
      <category>web</category>
      <category>svelte</category>
    </item>
    <item>
      <title>$mol Getting Started</title>
      <dc:creator>Kirill Novgorodtsev</dc:creator>
      <pubDate>Sun, 07 Jun 2026 15:26:33 +0000</pubDate>
      <link>https://dev.to/kirill_novgorodtsev_f9433/mol-getting-started-3d5i</link>
      <guid>https://dev.to/kirill_novgorodtsev_f9433/mol-getting-started-3d5i</guid>
      <description>&lt;p&gt;&lt;strong&gt;Apps that work offline, sync without a backend, and weigh ~140 KB brotli with everything bundled.&lt;/strong&gt; Reactivity, storage, and UI come from a single stack — you focus on the app, not on wiring libraries together.&lt;/p&gt;

&lt;p&gt;By the end of this guide you'll have a real $mol app with offline support, themes, and local storage — running locally and built with one command. About ten minutes if a terminal and editor are already at hand.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you'll get
&lt;/h2&gt;

&lt;p&gt;A running app that already includes the things you usually bolt on later:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;installable as a PWA, works offline,&lt;/li&gt;
&lt;li&gt;local storage with conflict-free sync (no backend code on your side),&lt;/li&gt;
&lt;li&gt;light and dark themes with a toggle,&lt;/li&gt;
&lt;li&gt;client-side routing,&lt;/li&gt;
&lt;li&gt;a GitHub Action for deploying to Pages.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of this comes from the scaffolder. After that, change one line and watch it recompute itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you'll need
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://nodejs.org/" rel="noopener noreferrer"&gt;Node.js&lt;/a&gt; 24+ and &lt;code&gt;git&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's the whole list. The bundle only includes modules your code references. No one maintains &lt;code&gt;dependencies&lt;/code&gt; by hand.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1. Bring up the workspace and dev server
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/hyoo-ru/mam.git ./mam &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;mam
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the same terminal and process, start the Hyper Base:&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%2Fgithub.com%2Fuser-attachments%2Fassets%2F025919b1-8aa5-46d8-b2bf-e24247f8da76" class="article-body-image-wrapper"&gt;&lt;img width="1534" height="1236" alt="image" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fuser-attachments%2Fassets%2F025919b1-8aa5-46d8-b2bf-e24247f8da76"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;+ giper/baza/app/run &lt;span class="nv"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;9090
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;mam&lt;/code&gt; works as a monorepo workspace of independent modules. Your app lives in it as a couple of files in its own namespace, and the bundle only contains modules your code actually reached. The dev server at &lt;code&gt;http://localhost:9080&lt;/code&gt; builds each bundle on demand.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2. Generate the app (one command)
&lt;/h2&gt;

&lt;p&gt;From the &lt;code&gt;mam&lt;/code&gt; folder, create the project. Pick your namespace and app name, for example &lt;code&gt;my/hello&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm create view-tree-lsp@latest my/hello &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;--no-docker&lt;/span&gt; &lt;span class="nt"&gt;--no-tauri&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The command creates a working app in &lt;code&gt;./my/hello/&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;my/hello/
├── app/
│   ├── index.html        # web entry point
│   ├── app.view.tree     # declarative component description
│   ├── app.view.ts       # behavior
│   ├── app.view.css.ts   # styles (typed CSS-in-TS)
│   ├── app.test.ts       # test
│   ├── app.meta.tree     # offline install
│   └── app.locale=en.json
├── store/
│   └── store.ts          # local storage
├── assets/logo.svg
└── .github/workflows/deploy.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The app is ready to run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3. Open it
&lt;/h2&gt;

&lt;p&gt;Go to &lt;strong&gt;&lt;code&gt;http://localhost:9080/my/hello/app/&lt;/code&gt;&lt;/strong&gt;. The first load takes a few seconds — the dev server builds JS and CSS for this module on the fly. After that, everything is incremental. You'll see an app with themes, a few screens, and working navigation, ready to live locally without a server.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The scaffolder also prints a test runner URL (&lt;code&gt;…/app/-/test.html&lt;/code&gt;) where you can watch the built-in test pass.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What you got (and why it matters)
&lt;/h2&gt;

&lt;p&gt;The scaffolder lays out a minimal &lt;em&gt;real&lt;/em&gt; $mol app, and that's on purpose. It already has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Local storage out of the box.&lt;/strong&gt; &lt;code&gt;store/store.ts&lt;/code&gt; is built on a CRDT: state persists locally and merges cleanly between devices. No sync code on your side, no backend.&lt;/li&gt;
&lt;li&gt;The line &lt;code&gt;include \/mol/offline/install&lt;/code&gt; in &lt;code&gt;app.meta.tree&lt;/code&gt; wires in the PWA module. The app installs to a phone and keeps working without a network. Offline is just another module.&lt;/li&gt;
&lt;li&gt;A light/dark theme toggle and URL-driven navigation (&lt;code&gt;$mol_state_arg&lt;/code&gt;) are already in place. Don't need them? Remove &lt;code&gt;Theme_toggle&lt;/code&gt; from view.tree — it's gone from both the DOM and the bundle.&lt;/li&gt;
&lt;li&gt;Styles are typed: &lt;code&gt;app.view.css.ts&lt;/code&gt; is CSS-in-TS, a typo fails at compile time.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Make it reactive
&lt;/h2&gt;

&lt;p&gt;Open &lt;code&gt;app/app.view.tree&lt;/code&gt;, find the welcome text. Add an input bound to a property, and a line that depends on it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;home &amp;lt;= Home $mol_page
    title \Home
    body /
        &amp;lt;= Name $mol_string
            hint \Name
            value? &amp;lt;=&amp;gt; name? \
        &amp;lt;= Greeting $mol_text
            text &amp;lt;= greeting \
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then in &lt;code&gt;app/app.view.ts&lt;/code&gt;, describe &lt;code&gt;greeting&lt;/code&gt; as a function of &lt;code&gt;name&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;greeting&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;name&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;name&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;name&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;`Hi, &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;!`&lt;/span&gt; &lt;span class="p"&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;Try typing. The greeting updates by itself. You didn't subscribe to the field, you didn't schedule a rerender: &lt;code&gt;greeting&lt;/code&gt; reads &lt;code&gt;this.name()&lt;/code&gt;, so $mol records the dependency and recomputes just that value when &lt;code&gt;name&lt;/code&gt; changes. The same reactive model runs through every layer, including data loading: a value that isn't ready yet "suspends" and slots in once it arrives. That's why async code reads like ordinary sync code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Set up the editor (a minute)
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;view.tree&lt;/code&gt; is indentation-sensitive, so editor support helps a lot:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;VS Code&lt;/strong&gt;: install the &lt;a href="https://marketplace.visualstudio.com/items?itemName=valikov.tree-language-service" rel="noopener noreferrer"&gt;view.tree language plugin&lt;/a&gt;; use the &lt;code&gt;.editorconfig&lt;/code&gt; plugin to get &lt;strong&gt;tabs for indentation&lt;/strong&gt; and &lt;strong&gt;LF line endings&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zed&lt;/strong&gt;: the &lt;a href="https://zed.dev/extensions/viewtree" rel="noopener noreferrer"&gt;view.tree extension&lt;/a&gt; adds highlighting, go-to-definition, and autocomplete.&lt;/li&gt;
&lt;li&gt;The Zed extension is built on the &lt;a href="https://www.npmjs.com/package/view-tree-lsp" rel="noopener noreferrer"&gt;view.tree LSP&lt;/a&gt; and a &lt;a href="https://github.com/Dev-cmyser/tree-sitter-viewtree" rel="noopener noreferrer"&gt;tree-sitter grammar&lt;/a&gt; — you can plug them into other editors too.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Deploy
&lt;/h2&gt;

&lt;p&gt;The scaffold already includes a GitHub Actions workflow: push to &lt;code&gt;main&lt;/code&gt;, it builds and publishes to GitHub Pages. Feature branches get their own preview URL.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;Generate the app, turn the welcome screen into something you'd actually use, and put that into the generated store so it survives a page reload and runs offline. When you need the full set of building blocks (inputs, lists, charts, pickers), check the &lt;a href="https://github.com/hyoo-ru/mam_mol" rel="noopener noreferrer"&gt;module catalog&lt;/a&gt; — every one with examples.&lt;/p&gt;

&lt;p&gt;If you get stuck, there's a skill for AI coding agents that knows &lt;code&gt;view.tree&lt;/code&gt; and can suggest how to do a specific thing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx skills add b-on-g/mol_skill &lt;span class="nt"&gt;--all&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
    </item>
    <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="799" 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>
