$mol: 4 года спустя
Здравствуйте, я люблю плевать против ветра. Утираться и снова плевать. Хобби у меня такое. И всё, что я создаю, делаю я без оглядки на тенденции, стараясь решать проблемы системно, а не как привычно. Зачастую бывает, что основная сложность даже не в том, чтобы придумать решение, а в том, чтобы объяснить другим, что проблема вообще существует.
Сегодня, хотелось быть рассказать про разработанный мной 4 года назад фреймворк, какой путь он прошёл, где он сейчас, и куда прокладывает новые пути. Пройдёмся мы и по конкурентам, и по крупным игрокам, и даже по мне самому. Так что никто не уйдёт не обиженным. Статья длинная. Мужайтесь.
Конец 2015
- AngularJS 1.4 — самый популярный фреймворк, до появления поддержки компонент оставалось ещё несколько месяцев.
- Angular 2 alpha — ещё пытаются поддерживать JS, вот-вот появится первая бета-версия.
- React 0.14 — его популярность ещё только набирает обороты.
- Polymer 1.2.3 — рендерит на мобилке по 1 компоненту в секунду.
- VueJS 1.0 — о нём ещё мало кто слышал.
Мы в компании пытаемся в мобильную разработку на заказ. Но это медленно и дорого, так как приходится синхронно разрабатывать 4 одинаковых приложения под разные платформы (iOS, Android, Windows, WEB). Пробуем веб технологии, но (фактически) десктопные фреймворки тормозят под мобилкой, а кроссплатформенная разработка — это медленно, так как нужна адаптивность к широкому спектру размеров экрана. Не говоря уж о том, что наивно реализованные приложения тормозят, а для оптимизации надо тратить дополнительные усилия.
В этих условиях возникает идея создания своего фреймворка под следующие требования:
- Быстрый старт приложения даже на мобилке. Для этого оно должно быть компактным, быстро инициализироваться и рендерить лишь видимое.
- Быстрая разработка сразу под все платформы и размеры экранов. Чтобы один человек мог выдавать качественный результат, не требующий оптимизаций, и содержащий минимум багов.
- Высокая степень переиспользуемости кода. Чтобы можно было шарить между приложениями не просто кнопочки и поля ввода, а целые разделы.
- Высокая степень настраиваемости. Чтобы можно было быстро и просто настроить под приложение любой аспект работы компонента.
Звучит как утопия? Конечно. Однако за моими плечами 10 лет разработки фронтенда, куча новых идей, и даже уже есть прототип:
Слева список демонстрационных компонент. По середине собственно демонстрация компонента в некотором состоянии. В данном случае демонстрируется простая электронная таблица заполненная кучей формул, с плавающими заголовками и виртуальным скроллингом. А справа — дерево состояний со всеми зависимостями между ними. Его я так и не доделал, ибо состояний слишком много, что делает такое отображение довольно бесполезным. Да, у нас уже был аналог сторибука, когда это ещё не было мейнстримом. Да что уж там, даже компонентный подход тогда был в новинку — все верстали страницами. Обратите внимание, что весит это приложение с 13 демками всего 24 кб. И оно там даже не минифицировано, ибо зачем, если оно и так уже меньше, чем один только голый React.
А тем временем мы влетели на крупные бабки недооценив трудоёмкость разработки на, казалось бы, богатом фреймворке от крупной корпорации с коммерческой поддержкой SAPUI5. Для тех, кто не в курсе, это такой аналог ExtJS.
Прихожу я к директору и говорю, что мы могли бы на порядок снизить издержки на разработку, если разработаем свой фреймворк, на базе моего прототипа. А это даст нам существенное конкурентное преимущество в борьбе за тендеры. И получив добро, я начинаю в фоне от остальных задач пилить новый фреймворк с нуля. На этот раз уже целиком и полностью на тайпскрипте.
Искренность
В мире многомегабайтных бандлов так популярна практика подлога, когда быстро показывается пререндеренная мёртвая страница, а к ней ещё несколько секунд догружаются скрипты. Мол, а вдруг пользователь не успеет кликнуть никуда. Успеет, особенно на медленном соединении. Мы же не пускаем пуль в глаза: если пользователь видит интерфейс, то он уже может с ним взаимодействовать. А чтобы пользователю не пришлось долго жать — просто обеспечиваем минимизацию размеров бандла.
Если произошла ошибка — показываем её пользователю там, где она произошла, а не стыдливое «ой, что-то пошло не так». И уж точно никаких белых экранов экран смерти — поведение по умолчанию в большинстве фреймворков.
Оптимистичный интерфейс врёт, говоря, что всё хорошо и можно закрывать вкладку. Пессимистичный — тормозит, ожидая ответа сервера на каждый чих. Мы же за реалистичный — пользователь видит, что идёт запрос или произошла ошибка.
Никаких скрытых процентов
Фреймворки часто подкупают новичков простотой создания простого приложения. Но эта простота часто оказывается обманчива. Так обычно, чтобы довести своё приложение до продакшен уровня, вам потребуется приложить существенно больше усилий: локализация, оптимизация, кастомизация, тестирование, сбор статистики, отладка, да и просто поддержка. Мы же ориентированы на уменьшение трудоёмкости работ на любой стадии проекта, а не только лишь на начальной. Поэтому всё, что можно, автоматизируется. И принимаемые соглашения этому только способствуют.
Кроссплатформенность
Не формальная типа «скрипт не падает, а дальше сами», а реальная. Серверный рендеринг никогда не был проблемой для $mol. Компоненты адекватно адаптируются как к большим (эффективно заполняя всё пространство), так и к маленьким экранам (не рендеря то, что в экран не влезло).
Прагматичность
Используя привычные подходы можно сделать лишь кривой клон уже существующей штуки. Никогда не сделаешь что-то лучше, пока не выйдешь из зоны комфорта. Поэтому мы всегда спрашиваем себя «а как тут было бы лучше для конечной цели?». И часто оказывается, что лучше делать совсем не так, как привыкли.
Конец 2016. Мы выпускаем первый публичный релиз под лицензией MIT в надежде, что сообщество поддержит наше амбициозное начинание. Находясь в больнице, я пишу огромную статью »$mol: reactive micromodular ui-framework» стараясь раскрыть как можно больше заложенных во фреймворке идей, но так и не затронул там даже половины.
Встретили его, мягко выражаясь, прохладно. Кому-то не понравился синтаксис «шаблонов», хотя это и ни разу не шаблоны. Кому-то не угодило именование через подчёркивание, которое имеет меньше всего проблем.
Следующий год мы наращивали функциональность. Иногда удавалось выцепить разработчиков с других проектов. Но самым полезным оказался Артур, которого мы наняли специально для разработки фреймворка. Это человек, которого, наверно, увольняли ото всюду, где он работал. Причина этому — идеализм и весьма экспрессивная речь. Не смотря на свои 20 лет, он отлично разбирался во фронтенде, получше многих «синьоров». По началу он тоже крайне скептично относился к нашей разработке, говорил, что мы всё делаем не правильно. Но чем больше я погружал его в проблематику, тем чаще он приходил к тем же выводам, что и я. И вскоре ненависть сменилась искренней любовью. Порой мы могли по пол дня спорить с Артуторм о правильном поведении, но именно ему я мог доверить разработку любых модулей, зная, что он справится не хуже меня. Тот самый язык view.tree мы пытались полностью перепроектировать с нуля, чтобы он вызывал меньше отторжения своей необычностью. Но результат оказался почти таким же, как и исходный синтаксис. Всё потому, что он максимально подходит для решения поставленных перед ним задач. А делать его похожим на что-то более привычное — лишаться всех его преимуществ.
Я активно писал статьи про идеи, заложенные в $mol, а так же выступал на конференциях. На одной из таких случайно подслушал обсуждение, где одна дама живописно описывала одного разработчика и злорадно так радовалась его скорому увольнению. Это оказался тот самый Артур. Мир тесен. В IT много планктона, делающего таски от забора до обеда. Но мало людей, способных сделать большее, чем повторять одно и то же за всеми. И таких котов надо уметь пасти. Дайте им интересную задачу и они вам горы свернут. Но окружающие посредственности постараются таких задавить. Любимая их методология — Скрам, где у каждого бомжа есть такое же право голоса, как и у профильного специалиста, а пресловутый коммитмент скатывается либо к голосованию большинством, либо к изживанию этого специалиста из проекта.
Конец 2017. У компании финансовые трудности и люди разбегаются. Я предлагаю директору отпустить некоторых, чтобы поднять зарплату ключевым разработчикам, но нет. Снова попадаю в больницу и тут мне предлагают вакансию с ЗП в 2 раза больше, от которого я не могу отказаться. Взяв месяц на поиск преемника, я возвращаюсь к работе и вижу, что от нашего департамента осталось 2 человека, один из которых последний работает, а второй уже подписал заявление. Руководить больше не кем. Так наш департамент фактически исчез. А вскоре и компанию поглотила другая.
Так $mol отправился в свободное плаванье. Соответственно, скорость его развития драматически замедлилась, но не остановилась. Вокруг $mol образовалось небольшое сообщество людей, не довольных текущим состоянием мира фронтенда. Вкратце, работа над фреймворком шла так:
Друзей нет, девушки тоже. Всю свою жизнь я чувствую себя одиноко. И тут я решаю заняться своей социализацией. (Знаете как классно социализироваться в 33 года? Спойлер: всё тщетно). Так что в свободное время я где-то пропадал. А на основной работе я чинил кривой дизайн Ангуляра: прикрутил к нему гибкие темы с поддержкой браузеров без css-variables, запилил лучший вирт-скролл (и на тот момент единственный рабочий, ибо компоненты быстро ломались с новыми версиями Ангуляра), переписал mobx-angular с поддержкой Suspense API (вскоре это апи презентовали и для Реакта, но спустя 2 года так ещё и не зарелизили)… Всё это закрытые разработки, которые никогда не окажутся в оупенсорсе, к сожалению. Но вы не расстраивайтесь, все эти фичи есть в $mol да ещё и в более удобном виде. Я пытался преренести некоторые идеи из $mol в Ангуляр, раз уж мне не удалось убедить начальника попробовать хотя бы на небольшом проекте нормальный фреймворк.
Всё это не доставляло особого удовольствия. Простое, казалось бы, приложение, которое на $mol можно было бы реализовать за несколько дней да ещё и в более качественном виде, мы пилили на Ангуляре несколько месяцев и получалось так себе — огромный бандл, медленная работа, медленные тесты, просто тонны кода и, конечно, куча кастомных компонент, так как существующие либо не настраиваются толком, либо вообще не работают. Это всё здорово угнетало и последней каплей стал момент, когда пара инициативных ребят по скраму продавили NgRx (Это такой redux на стримах). Я изматерился, но сделал пулреквест с заменой mobx на это чудо, и свалил.
Сейчас я работаю над интересным, ещё более амбициозным проектом, о котором пока ещё я не могу распространяться. Но очень хочется, ведь там уже реализовано много прорывных идей. Ну, а в свободное время мы с Сергеем пилим $mol. Например, недавно он героически отрефакторил самую быструю библиотеку для рисования графиков — $mol_graph. Это один из самых сложных $mol-компонент, которому можно просто скормить кучу данных, а он сам разберётся как их быстро и наглядно показать.
Что только недавно появилось в популярных фреймворках, что было в $mol изначально:
Автоматическое отслеживание зависимостей
Это такая штука, позволяющая писать простой и лаконичный код вычисляющий одни состояние на основе других. При этом перевычисление зависимого состояния происходит лишь при изменении тех состояний, к которым было обращение во время предыдущего вычисления. Это обеспечивает автоматическую динамическую перестройку потоков данных для минимизации лишних вычислений. Говоря простым языком — отпадает необходимость обновлять весь мир, чтобы понять, что надо поправить одну циферку в углу экрана.
Из коробки среди популярных фреймворков отслеживание зависимостей есть только в Vue. Но и там оно пока ещё гвоздями прибит к визуальным компонентам. В третьей версии вроде как обещают выделить этот механизм в отдельную абстракцию, аналогичную MobX, что позволит использовать полноценную реактивность во всём приложении, а не только в компонентах. Сам MobX довольно не плох, но прикручивается к другим фреймворкам через специальные адаптеры. А так как остальные фреймворки не проектировались для такого поведения, то адаптеры эти представляют из себя тот ещё сборник костылей, скрученных синей изолентой.
$mol же изначально проектировался под автоматическое отслеживание зависимостей. И, как и всё в $mol, механизм реактивных состояний выделен в отдельный небольшой ортогональный остальной функциональности модуль $mol_mem.
Абстракция от асинхронности
Идея этой штуки простая — позволить писать простой лаконичный быстрый синхронный код, который может прерываться на асинхронных операциях и перезапускаться по их завершению.
В Реакте это называется Suspense API и всё ещё является экспериментальной фичей. В третьей версии Vue обещают что-то аналогичное. MobX, как ни странно, боятся адаптировать для саспенса.
$mol же полностью основан на идее саспенса. В нём вы почти не встретите ни promises, ни callbacks, ни async/await. Всё потому, что любое реактивное свойство по умолчанию поддерживает возможность приостановки вычислений. Это, конечно, накладывает требование писать идемпотентный код, но его лаконичность и скорость всё компенсирует. Вы только взгляните, как просто загрузить данные с сервера, благодаря саспенсу:
@ $mol_mem
user_name() : string {
return $mol_fetch.json( '/profile' ).name
}
ТайпСкрипт
Сейчас этого персонажа уже никому представлять не нужно, но 4 года назад всё было не так — приходилось доказывать, что за TS будущее. Один из первых написанных на TS фреймворков — Angular 2 — по началу даже пытался поддержать возможность работы с ним как из TS, так и из JS. Но довольно быстро его пользователи поняли, что писать на JS вообще и под Angular в особенности — больно. Vue 3 переписывают сейчас на TS. React и большинство библиотек в его экосистеме написаны на JS, не смотря на усилия MicroSoft по вкорячиванию JSX в TypeScript. Да, для них, конечно, есть тайпинги, но пишутся они сторонними людьми с запозданием да ещё и пяткой левой ноги. А API JS библиотек зачастую такие, что адекватные тайпинги к ним написать сложно.
$mol изначально писался на TS, по максимуму используя его возможности. Для него не нужно писать тайпинги. Декларативные описания компонент транслируются в TS, что обеспечивает их статическую типизацию практически бесплатно. А с недавних пор статически типизированы и стили, но об этом позже. Ангуляр же дошёл до похожего подхода с шаблонами лишь сравнительно недавно.
Однако, на этом поприще есть ещё ряд нерешённых проблем, с которыми нам нужна помощь. Например, хотелось бы иметь лучшую интеграцию view.tree описаний в TS, чтобы работали подсказки, рефакторинги и ошибки компилятора указывали в оригинальные исходники, а не сгенерированные.
Нулевая конфигурация
В те далёкие времена фреймворки поставлялись ещё сами по себе. Всю инфраструктуру для разработки приложения на их основе каждый настраивал самостоятельно: сборка продакшен билда, сборка отладочного билда, сборка локализаций, запуск тестов, старт локального веб-сервера для разработки… всё это делали кто во что горазд. Утилиты использовались разве что для генерации нового проекта. И то, появились они не от хорошей жизни, а потому, что для настройки инфраструктуры приходилось делать очень много телодвижений. Шутка ли, появились даже отдельные специалисты по настройке WebPack.
$mol же изначально проектировался так, чтобы стартовать новый проект было проще простого — создал директорию, в ней файл с исходником приложения и всё. Так как инфраструктура отделена от кода, то одну и ту же инфраструктуру можно использовать для многих проектов и централизованно её менять. При этом между проектами могут быть зависимости. И всё это на уровне исходных кодов, без изнурительных «подняли версию, опубликовали в репозитории пакетов, подняли зависимости в зависимых пакетах, и так по кругу». Да, у нас уже была своя «lerna» за год до её появления. Называется эта штука MAM и вообще говоря, к $mol она не привязана. Разрабатывать в рамках MAM архитектуры можно на чём угодно.
При этом MAM умеет и бандлы собирать, и сервер запускать, который при открытии приложения собирает нужные для этого бандлы и гарантирует, что бандл будет актуален последним изменениям в исходниках, а внутри не будет лишних модулей.
Короче, MAM предоставляет всю необходимую инфраструктуру, чтобы разрабатывать качественные приложения, без написания кучи однотипных конфигов. Похожая идеология у parcel, которому скармливаешь html и он всё делает самостоятельно. Правда он очень ограничен своей идеологией быть не более, чем опциональным оптимизатором. Например, тайпскрипт он умеет собирать лишь без тайпчека. Какой толк от такого тайпскрипта — мне не ведомо.
MAM же куда более гибок. В нём легко поддержать любые типы исходников. А при необходимости вы всегда можете форкнуть его и настроить под себя как душе угодно. Это похоже на eject в распространённый CLI для популярных фреймворков. С той лишь разницей, что эджектнутые конфиги вам придётся поддерживать уже полностью самостоятельно. А форк вы легко сможете обновлять стандартными средствами гита. Но я бы не рекомендовал вносить улучшения лишь в свои разрозненные форки, ведь лучше совместно улучшать единую систему сборки.
Перетряс дерева зависимостей
В отличие от PHP в JS автоматическое разрешение зависимостей не завезли. Поэтому программистом приходится явным образом импортировать зависимости. Делать это для каждой мелкой функции — утомительное занятие, поэтому программисты группируют группируют зависимости, чтобы подключать их из одного файла, а не из 10. Но если собирать это дело в лоб получается, что в бандл попадает много лишнего, что не используется, но импортировалось в тот же файл, где находится то, что используется. Бандлы стали расти как на дрожжах. И чтобы совладать с этой проблемой придумали tree shaking, который пытается вырезать всё лишнее, но ему не всегда это удаётся, так как код должен быть написан специальным образом. Поэтому сейчас такие фреймворки как Angular и Vue постепенно переписывают, чтобы бандлы могли хорошо тришейкаться. Ну, а React всё ещё остаётся большим и неделимым куском кода.
Если присмотреться, то проблема-то не в том, как вырезать лишнее, а в том, что лишнее вообще включается в сборку. Именно поэтому в архитектуре MAM нет никаких импортов, экспортов, реэкспортов и прочего хлама. Сборщик смотрит не на импорты, а на фактическое использование. И если его обнаруживает — включает зависимость в бандл. Зависимость включается целиком, но это не страшно, так как все модули в $mol очень маленькие, и каждый из них выполняет лишь одну функцию. А так как не надо перелинковывать все файлы вручную, писать код по множество мелких файлов так же просто, как и делать это в одном файле.
Тришейкинга в $mol нет, но это именно та штука, которая поможет остальным фреймворкам когда-нибудь приблизиться по размеру бандлов к нему. Первая ласточка — Svelte, который вкомпиливает свои хелперы по мере необходимости. К сожалению, код компонент на нём после компиляции довольно много весит, что нивелирует эффект от тришейкинга на больших приложениях.
Инверсия Контроля
Эта концепция позволяет настраивать поведение компонента извне, позволяя менять реализации, используемых внутри него абстракций. Например, можно подменить один класс другим. Или один сервис иным. С этим до сих пор всё плохо. Из популярных фреймворков только Ангуляр поддерживает IoC посредством Dependency Injection. Но реализация DI там довольно сложная, многословная, медленная и просто невыносимая в отладке.
В $mol же IoC реализован довольно элегантно через Ambient Context — это предельно простая штука, не требующая писать много кода. Для примера, гляньте как просто работать с контекстами:
namespace $ {
export function $my_hello(
this : $mol_ambient_context
) {
this.console.log( this.$my_name )
}
export let $my_name = 'Anonymous'
}
namespace $ {
$mol_ambient({}).$my_hello() // logs 'Anonymous'
let custom_context = $mol_ambient({
$my_name : 'Jin' ,
})
custom_context.$my_hello() // logs 'Jin'
}
Стойкость к ошибкам
В React 16 появилась новая возможность — Error Boundaries. Она позволяет реагировать на исключительные ситуации в работе компонента и, например, отображать вместо него сообщение об ошибке, а не ронять всё приложение. До этого в любой неожиданной ситуации ваше приложение превращалось в тыкву, отображая лишь белый экран. Чтобы осознать эту довольно типичную проблему разработчикам самого популярного ныне фреймворка потребовалось 15 версий. И до сих пор белая тыква на белом фоне — это поведение по умолчанию, пока вы вручную не напишите перехват ошибок и отображение заглушки.
Похожая ситуация и с остальными фреймворками. В более менее популярных эта функциональность появилась в последние год-два. Остальные же так и выдают белый экран. Вы только вдумайтесь во всю глупость ситуации: из-за малозначительной ошибки где-то в подвале страницы, пользователь видит полностью белый экран и всё. Надо ли говорить, что в $mol такого никогда не было? В случае ошибки в рендеринге компонента — именно этот компонент и помечается упавшим. Происходит это автоматически и не требует особых телодвижений.
Кроме того, частный случай исключительной ситуации — это отсутствие данных, пока мы их ещё не загрузили. В этом случае кидается Promise. И компоненты, которые не могут отрендериться из-за ожидания завершения асинхронной операции, тоже автоматически помечаются индикатором ожидания. В остальных же фреймворках до сих пор расставлять «спиннеры» приходится собственноручно.
Прошло уже 4 года, а $mol всё ещё не современен. Он из будущего. Давайте посмотрим, чему ещё только предстоит появиться в популярных фреймворках, что в $mol уже есть.
Ориентация на компоненты
Идея компонент в том, чтобы собирать интерфейс из переиспользуемых кусочков приложения. Компоненты поменьше собираются в компоненты по крупнее и так далее до компонента всего приложения. Раньше компоненты были диковинкой, но в последние годы возможность создания компонент появилась во всех фреймворках. Однако, ориентация на компоненты предполагает не просто возможность их создания, а использование их как основного кирпичика, исключая html-вёрстку как таковую.
К сожалению, большинство фреймворков всё ещё пытаются усидеть на двух стульях, предлагая строить интерфейс из двух разных сущностей: html-элементы и компоненты, мимикрирующие под html-элементы. В $mol же нет никакой html-вёрстки — есть только компоненты, которые можно настраивать и комбинировать, собирая любой интерфейс. Ближе всех к $mol пока что подобрался React Native, где уже нет никаких html-элементов, а есть только базовые компоненты типа View, Text и тп. Однако синтаксическое подражание html-у всё ещё осталось в качестве карго-культа. Вы только сравните два куска кода на TSX и на обычном TS:
function MyApp(
{
leftColor = 'red',
rightColor = 'blue',
message = 'Hello World!',
} : {
leftColor? : string,
rightColor? : string,
message? : string,
}
) {
return (
{message}
)
}
function MyApp(
{
leftColor = 'red',
rightColor = 'blue',
message = 'Hello World!',
} : {
leftColor? : string,
rightColor? : string,
message? : string,
}
) {
return (
View({
kids: [
View({ style: { backgroundColor: leftColor } }),
View({ style: { backgroundColor: rightColor } }),
Text({ kids: [ message ] }),
]
})
)
}
Мой прогноз: вскоре все фреймворки перейдут целиком на компоненты и тогда придёт, наконец, понимание, что HTML ничего не даёт для описания интерфейсов. Следующая волна фреймворков будет уже без груза HTML на шее, а интерфейсы будут собираться либо на TS, либо как в $mol — на специальных, заточенных для этого DSL, типа view.tree:
$my_app $mol_view
sub /
<= Left $mol_view
style *
backgroundColor <= left_color \blue
<= Right $mol_view
style *
backgroundColor <= right_color \red
<= Message $mol_view
sub /
<= message \Hello World!
Настраиваемость
Редко какой компонент может быть использован везде как есть. Часто требуются те или иные его вариации. Даже для тривиального компонента типа кнопки предусмотреть заранее все варианты мало кому удаётся. А даже если и удаётся — это куча кода и развесистый противоречивый API. А мажорные версии чуть более сложных компонент растут как на дрожжах.
Отчасти всё это связано со всеобщим заблуждением, что инкапсуляция — это сокрытие, приводящем к тотальному огораживанию внутренностей от потребителя. Доходит до такого маразма, что стили изолируются так, что визуализацию компонента невозможно поправить так, чтобы он вписывался в общий дизайн приложения. Отчасти именно с этим связано то, что каждая компания создаёт свой компонент «кнопка», не говоря уж про остальные.
Со стилями проблема достаточно очевидна и как-то её всё же пытаются решить, применяя костыли различной степени кривизны. Вот с поведением всё сложнее. Особенно в свете засилия так называемых «функциональных компонент» (привет, React), которые и не компоненты вовсе, а просто извращённая форма шаблонов. Для настройки поведения зачастую делают десятки параметров, через которые поведением можно управлять. Но как на зло, автор компонента обязательно не предусмотрит один-другой так нужный вам параметр.
Ну да ладно, при использовании классов поведение более-менее можно переопределять, через наследование. Но вот с композицией компонент всё совсем плохо. Обычно компоненты соединяются друг с другом через шаблоны, которые довольно статичны и являются вещью в себе. В лучшем случае во фреймворке есть поддержка «слотов» (привет, Vue), но эти слоты должны быть заранее расставлены везде автором компонента.
$mol же предлагает инкапсуляцию без сокрытия. Вы по прежнему не обязаны знать из чего там состоит компонент, чтобы им пользоваться. Но вы можете настроить любой аспект его поведения, даже если его автор не особо об этом и подумал. Да что там, весь набор стандартных моловских компонент писался именно что почти без оглядки на возможность их настройки, что делает их код предельно простым и ясным. Всё дело в том, что view.tree
описание транслируется в самый обычный тайпскриптовый класс. Это не только даёт статическую типизацию, но и возможность наследования и переопределения любого свойства предка. Для примера, смотрите как просто создать своё приложение на базе приложения из предыдущего раздела, но с другими цветами и дополнительным блоком в середине:
$my_app2 $my_app
left_color \brown
right_color \green
sub /
<= Left
<= Right
<= Bid $mol_view
sub /
<= bid @ \Smile if you see it!
<= Message
Такого уровня настраиваемости вы пока что нигде не встретите. Появление чего-то подобного в других фреймворках — это дело не ближайшего будущего, к сожалению.
Квантование
В Реакте это называется TimeSlicing — штука позволяющая, разбивать рендеринг компонент на кусочки, не блокируя основной поток на долгое время. Но всё, что не касается рендеринга не будет «нарезаться». И до сих пор эта шутка экспериментальная.
Есть малоизвестный фреймворк Catberry, построенный вокруг идеи прогрессивного рендеринга, то есть появления интерфейса по частям. Опять же, всё что не касается рендеринга — мимо. Кроме того, прогрессивный рендеринг, хоть и даёт хорошую отзывчивость, но всё же существенно замедляет рендеринг приложения, так как после появления очередных изменений в DOM браузер начинает пересчёт стилей, раскладки, отрисовки и тп вещей, которые порой не укладываются в один кадр (16 мс).
В $mol же мы какое-то время жили с полным квантованием — квантоваться могли любые вычисления. И нам даже не пришлось для этого менять API, благодаря изначально правильно выбранным абстракциям. Собственно, програмый интерфейс $mol получился на столько удачным, что за 4 года ни разу не потребовал существенных изменений, не смотря на существенные рефакторинги под капотом.
Впрочем, от квантования мы пока что отказались в пользу виртуализации — она позволяет добиться хорошей отзывчивости, без существенного замедления полного рендеринга. Возможно в будущем удастся подружить виртуализацию с квантованием, добившись тем самым беспрецедентной скорости работы.
Мой прогноз: копошение вокруг квантования приведёт к появлению в яваскриптовом рантайме концепции волокна, позволяющей замораживать и размораживать исполнение задач в любое время. А уже квантование на этой основе реализуется просто элементарно.
Ленивость
Ленивость имеет много воплощений. Это не не загружать то, что не будет обрабатываться. Это и не вычислять то, что не будет отрендерено. Это и не рендерить то, что не будет показано. Основа ленивости — семантика затягивания (pull). Суть её в том, чтобы ничего не делать, пока результат твоей работы никому не нужен. На этой идее построена вся архитектура $mol.
Однако, большинство фреймворков не умеют в «затягивание». Редким исключением является Vue, где модель реактивности похожа на $mol_atom, только без абстракции от асинхронности. Тем не менее даже там ленивый рендеринг приходится реализовывать руками. Выглядит он обычно так: в скроллящуюся область помещается плоский список узлов фиксированной высоты. Это самая простая форма виртуализации называется обычно «виртуальной прокруткой». К сожалению, она далеко не везде применима, а если и применима, то только вручную.
В $mol же изначально была ленивость методом «дорендера снизу». То есть показывается только та часть страницы, что попадает в видимую область. По мере скролла вниз дорендериваются дальнейшие части страницы. А при скролле вверх, наоборот, удаляются.
Дорендер работает с любой конфигурацией компонент и не требует фиксации их размеров. Без ориентированности на компоненты такого добиться сложно, так как абстрактные html-элементы ничего не знают о том, какая часть их содержимого невидима, без собственно рендеринга этого содержимого. Когда же интерфейс строится из компонент, то появляется дополнительная информация о минимальных размерах и раскладке блоков.
Например, вертикальная раскладка реализуется через компонент $mol_list, который умеет правильно вычислять свои минимальные размеры на основе минимальных размеров своего содержимого и рендерить только ту часть содержимого, которая гарантированно накроет видимую область.
А недавно мы выкатили полную виртуализацию — добавление и удаление компонент происходит не только сверху, но и снизу, что гарантирует, что число элементов в DOM не будет расти неограниченно. Обычно их в видимую область помещается не больше тысячи. А браузеру эту тысячу пережевать относительно легко. К сожалению, «дорендер сверху» для адекватной работы требует от браузера поддержки «overflow-anchor», поэтому в сафари он отключается, оставляя лишь «дорендер снизу».
Работает полная виртуализация пока ещё не без косяков, но думаю скоро нам удастся совладать со всеми проблемами. Касательно остальных фреймворков прогноз менее радужный. Например, к Реакту прикрутить полную виртуализацию будет крайне сложно. Для остальных фреймворков возможно удастся сделать что-то похожее на уровне библиотеки компонент, которые умеют друг с другом взаимодействовать. Фреймворки новой волны думаю уже будут основаны на идее полной виртуализации, позволяющей существенно экономить ресурсы и делать по настоящему шустрые приложения.
CSS-in-TS
Большинство фреймворков всё ещё по старинке предлагают руками прописывать элементам классы, а через CSS вешать на них стили. И если css-свойства ещё могут как-то валидироваться, то селекторы могут быть любыми. Поэтому в процессе эволюции приложения остаётся мно