Почему $mol?

от автора

TLDR — у $mol ( смола\мола ) реактивность, локальное хранилище, оффлайн и темы приезжают одним пакетом. Минусы есть, но они почти все про коммьюнити и тулинг, не про сам код. С ними получается жить.

Давайте начну с них(минусов), так честнее. Порядок не по важности.

Нет CDN-версии $mol — такой, чтобы подключить одной строкой <script src=...> в HTML и сразу писать UI, как с Vue. Имя класса в $mol завязано на путь в файловой системе ($mol_button лежит в mol/button/), и сборщик строит бандл из этих путей. CDN-вариант теоретически можно сделать, но придётся переписывать сборщик а энтузиаст пока не нашёлся.

Мало “серьёзных” публичных кейсов. Корпоративные приложения есть, но скрыты NDA, и в паблике остаются демки и пет-проекты. Из того, что можно показать:

  • web.giper.dev — экосистема продуктов ( аля гугл ) от автора $mol.

  • b-on-g.github.io/blitz — мой реал-тайм квиз на $mol + Giper Baza. Делал долго, полировал.

Мало встроенного тулинга. Разрабатывать на $mol приятно, но только если ты уже “прошаренный”, для новичка тяжело, не хватает возможностей в IDE. Для VS Code есть официальные расширения, но без go-to-definition и подсказок по синтаксису.

Стектрейс ошибки выглядит просто ужасно)

image

image

Стектрейс ошибки в $mol

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

Что-то из тулинга я сделал сам:

  1. LSP — npm i -g view-tree-lsp@latest, плюс скаффолд приложения одной командой: npm create view-tree-lsp@latest bog/myapp -- --no-docker --no-tauri.

  2. tree-sitter-грамматика для view.tree.

  3. Расширение для Zed на их основе — там работает go-to-definition, подсветка и прочие радости. Там 30k скачиваний, наверное боты качают всё подряд для тестов)

Великий и ужасный view.tree ( формат для декларативного описания компонент ) я минусом не считаю. Сравните свой первый опыт с jsx, например. view.tree описывает обычные js-классы — так, чтобы было проще видеть связи между компонентами и не писать boilerplate.

Плюс — удобное разделение слоёв: view.tree это абстракция над html, ts это логика, css.ts это стили. Меньше путаницы. Покажу на примере:

Pasted image 20260401223034

Pasted image 20260401223034

mol.hyoo.ru — tree-песочница

Тут базовый компонент ( по умолчанию div, но может быть чем угодно ) и его интерпретация в js-код. Методы этого класса можно перебить в .ts, или добавить новые в обоих файлах.

По сути все проблемы, описанные выше, сводятся к маленькому коммьюнити и слабому маркетингу. Ну что ж, нужны добровольцы!)

Проблема посерьёзнее — это интеграция с npm. Сейчас есть три пути для подключения библиотеки:

  • через CDN — безболезненно, но без типов,

  • скачать либу к себе в репу и закоммитить,

  • воспользоваться тулингом для воспроизводимых сборок от сообщества ( сам не пробовал, пишите в @mam_mol_разработка ).

В общем, как-то не нативно, с отторжением. Но сообщество не оставляет попыток — сейчас ведутся работы над сборщиком.

image

image

Также часто мне писали:

“На $mol нет вакансий, а если будут — не найти специалистов.”

Вакансий да, почти нет. Но можно начать новый проект в своей компании или в стартапе. И не нужен “специалист по $mol” — нужен TS-разраб, понимающий компоненты и реактивность. Онбординг 2-4 недели, это меньше, чем разобраться в чужой React-кодбазе с её Redux/Zustand/RTK/Tanstack-зоопарком.

Ещё многое в плане дизайна придётся делать с нуля, даже при том, что в $mol куча готовых компонентов. Но написав это один раз, можно будет таскать компоненты из разных приложений, как из одного ui-кита. Споры с дизайнерами обеспечены, красота требует жертв 🙂

Если дизайн не нужен, можно очень быстро собрать интерфейс из готовых кирпичиков — для админки, например.

Вот что реально требует серьёзной доработки — это документация и getting started. Мне кажется ( да и многие пишут ) это главный барьер для входа сейчас. Хорошую доку сделать довольно тяжело, но необходимо, чтобы люди быстрее и проще могли познавать, а не уходить разочарованными. Я написал свой getting started, от клонирования mam до работающего приложения с офлайном и синхронизацией. Насколько получилось, судить вам)

По поводу токсичного ореола вокруг $mol. Многие сталкивались с хейтом под постами о $mol — иногда по делу, иногда без. У нас очень ламповое и дружное комьюнити, залетайте в @giper_dev, а лучше приходите пообщаться офлайн на @piterjs. Офлайн вообще идеально, вживую невозможно быть токсиком 🙂 Так же я провожу бесплатные вводные онлайн уроки по молу, пишите.

Кажется, с минусами закончили — переходим к плюсам.

Зайду издалека. Слушал недавно react-подлодку и вспомнил, сколько на современном React приходится тюнить руками. Под каждый компонент с тяжёлым рендером — useTransition или useDeferredValue, чтобы UI не лагал на тысячах строк. И даже если сделаешь виртуализацию, это будет виртуализация только для одного этого компонента. В $mol весь рендеринг полностью виртуальный: компонентов вне вьюпорта физически не существует, эта “настройка” спрятана под капотом.

Сравните живые демо треугольника Серпинского от Карловского:

  • React — тормозит на анимации,

  • $mol — без подёргиваний.

Один и тот же сценарий, одно и то же железо. Исходники обеих демок.

А вот стандартный js-framework-benchmark, где Карловский включил $mol — виртуальный рендеринг рвёт всех в щепки, потому что компонентов вне вьюпорта просто не существует.

Недавно напало вдохновение, накатал прошлую свою статью про веб-компоненты и сделал бенчмарк, где есть подсчёт количества строк кода:

Pasted image 20260406235307

Pasted image 20260406235307

Теперь про реактивность. В большинстве реактивных систем есть один и тот же edge-case: одно невалидное значение в графе вычислений может потянуть за собой соседей, которые от него формально не зависят. Покажу на Svelte, потому что их туториал даёт это потрогать руками.

Ломаем {a}, и падает {b}:

image

image

{b} в коде не зависит от {a}, но эффект каскадирует. Похожее можно поймать в большинстве топ-фреймворков, обычно решается ручным try/catch или разнесением по компонентам. При возврате {a} сумма пересчитывается.

Подскажите в комментах плиз: если разнести по разным компонентам, поведение то же? И как это решают в других фреймворках? Кейс взял из этого видео.

Теперь то же в $mol:

image

image
Pasted image 20260402000509

Pasted image 20260402000509

{b} пересчитывается независимо.

image

image

При возврате {a} пересчитывается и сумма.

В $mol эта проблема решена архитектурно. Каждый getter изолированный atom: если один падает, остальные не пересчитываются и не знают. try-catch писать не пришлось, всё само починилось без перезагрузки. Подробный разбор реактивности.

ОРП — каждый метод это точка расширения

В $mol реактивность не на уровне переменных, а на уровне методов класса. Метод-геттер автоматически становится вычисляемой реактивной ячейкой: читает данные → запоминает зависимости → пересчитывается при их изменении. Без useState, useMemo, useEffect и $derived — это спрятано под капотом.

Переиспользовать компонент — просто написать его имя в view.tree. Без копирования кода, без npm install, даже из чужой репы:

$my_dashboard $mol_pagebody /<= Chart $hyoo_crus_chart_pie<= Edit $bog_some_editor

Это работает потому, что $mol-модули адресуются по имени класса, а сборщик mam сам тянет, что нужно. Грань между «своим» и «чужим» кодом стирается.

CSS in TS

Я CSS знаю плохо, это моя боль, не хочу запоминать кучу правил. А в моле стили это просто TS-объект с типами на полях. Не знаешь свойство — тыкаешь по автодополнению, и работает) Нейронка может себя поправлять и корректировать, очень удобно.

Офлайн одной строкой

В app.meta.tree пишешь:

include \/mol/offline/install

И всё, приложение ставится как PWA и работает без сети. Манифест сборщик собирает сам.

Локализация

В view.tree ставишь @ перед строкой:

$my_page $mol_pagetitle @ \Hello, World!

view.tree транспилируется в js, и из этой строки получается метод:

/** title @ \Hello, World! */title() {return $mol_locale.text( "$my_page_title" )}

en-локаль вынимается из дефолтных значений автоматом, ключ собирается из имени компонента + имени свойства, что очень важно — идентификатор человекочитаемый и сразу указывает на место использования, как в файловой системе, так и в компоненте. Делаешь рядом page.locale=ru.json с переводами:

{    "$my_page_title": "Привет, мир!"}

И всё.

Так получается потому, что view.tree строгий dsl. По дефолту в реакте берут i18next: пишешь t('greeting.title'), ключи и словарь ведёшь сам. Для извлечения ставишь i18next-parser, он грепает исходники регекспами и ломается на динамике.

Нет конфигов

В $mol-проекте нет vite.config.ts, нет своего tsconfig у приложения. Сборщик mam знает всё по конвенциям: имя файла — тип, путь от корня — имя класса. Не нужно мучаться с конфигами.

Новый модуль = папка с парой .ts/.view.tree файлов. package.json mam генерирует сам, если его нет.

Строгая архитектура

В моле имя класса жёстко завязано на путь в файловой системе. Класс $my_app_button лежит в /my/app/button/button.ts, других вариантов нет. Захочешь назвать класс Button и положить в src/components/ui/button/index.ts? Не получится.

Кажется ограничением, но мол просто не даёт писать спагетти. Имя короткое и говорит, где смотреть. По имени класса в коде, в git log, в комменте на гитхабе ты сразу знаешь файл.

Поверх этого MVF (ModelView Fractal), паттерн декомпозиции $mol_view. Каждый компонент это и вью для родителя, и модель/контроллер для детей. Прикладная модель отдельно: $hyoo_talks_domain живёт в talks/domain/domain.ts, вью работает с её объектами, не с сырыми данными:

// talks/talk.view.tsdomain() {return this.$.$hyoo_talks_domain}chat( id ) {return this.domain().Chat( id )}

А ещё MAM не привязан к вебу. Модуль это директория с исходниками на чём угодно: view.tree, GLSL-шейдеры, CSS, JSON, файлы для других языков. Сборщику можно добавить любой язык, MAM знает только структуру директорий и зависимости между модулями.

Рендерилка изолирует падения

Любой $mol-компонент это сам себе error boundary. Exception в getter, таймаут в загрузке, что угодно — вместо этого компонента показывается заглушка. Остальной UI живой.

В реакте для того же надо обернуть всё в <ErrorBoundary> и помнить про это. В моле по дефолту.

Плюс реактивность: когда значение становится валидным, компонент сам перерендеривается без перезагрузки. Это уже было в примере с Svelte выше.

Одна кодовая база — все платформы

Один и тот же $mol-проект собирается под:

  • Web

  • Tauri (десктоп macOS/Windows/Linux, есть и iOS/Android)

  • Chrome/Firefox-расширение (MV3)

  • Telegram Mini App

Tauri-обвязка и манифест расширения уже есть в скаффолде, отдельно ничего не настраиваешь. У меня самого VK Music (bog/vk) собран и как MV3-расширение, и как зеркало на gh-pages — буквально один и тот же модуль.

Кстати, если нужен бэкенд — есть Гипер База: CRDT-синк, заточена под $mol. Это отдельная история, напишу про неё статью со сравнением с другими local-first решениями.

Почему стоит попробовать

Не из-за возраста, $mol младше React и Vue, ровесник Angular. Дело в другом: за 10 лет у $mol один мажорный релиз. У React за то же время 19 версий, у Angular 21.

$mol

React

Angular

Vue

Первый релиз

2016

2013

2016

2014

Мажорных версий на 2026

1

19

21

3

Tree-shaking без настройки

✅ изначально

⭕ исключение лишнего

⭕ препятствует, с 2022 standalone

⭕ исключение лишнего

Атомы / сигналы из коробки

✅ изначально

❌ только MobX и аналоги

⭕ с 2023

✅ изначально

Падение не валит приложение

✅ автоматом

⭕ ErrorBoundary с 2017

❌ нет

⭕ errorCaptured вручную с 2017

Приостановка на async

✅ автоматом на любом уровне

⭕ Suspense, только при рендере

❌ через RxLet вручную

❌ только при монтировании

Статическая типизация

✅ поведение + композиция + стили (с 2020)

❌ сторонние тайпинги

✅ только поведение

⭕ только поведение с 2020

Инверсия контроля

✅ типизированные контексты

⭕ ручные/renderProps

✅ инъекции/контексты

⭕ нетипизированные контексты с 2017

Разделение поведения/композиции/стилей

✅ изначально

❌ принципиально нет

❌ частичное

❌ частичное

Кастомизация поведения

✅ наследование или in-place

⭕ ручные точки расширения

⭕ наследование

⭕ точки расширения с 2020

Кастомизация композиции

✅ наследование или in-place

⭕ ручные точки

⭕ ручные слоты

⭕ ручные слоты

Кастомизация оформления

✅ каскад по авто-атрибутам

❌ ручные точки или классы

❌ ручные точки или классы

❌ ручные точки или классы

Рендеринг только видимой области

✅ автоматом ленивый, с 2020 полностью виртуальный

⭕ через сторонний компонент

⭕ через сторонний компонент

⭕ через сторонний компонент

Клиент + сервер одним кодом

✅ изначально

❌ другой API, готовить данные заранее

Версий нет, потому что нет нужды плодить копии. Сломал API — переименуй модуль, при этом можно переиспользовать до 99% старого кода. Звучит дико, но это работает: нет lockfile-зоопарка, нет «у меня собирается, у тебя нет», нет dependabot-PR каждую неделю.

Если узнал в чём-то свою боль, приходи в @giper_dev, там разберёмся. Или потыкай песочницу, view.tree онлайн, без установки.

ссылка на оригинал статьи https://habr.com/ru/articles/1044684/