Здравствуйте, меня зовут Дмитрий Карловский и я… люблю плевать против ветра. Утираться и снова плевать. Хобби у меня такое. И всё, что я создаю, делаю я без оглядки на тенденции, стараясь решать проблемы системно, а не как привычно. Зачастую бывает, что основная сложность даже не в том, чтобы придумать решение, а в том, чтобы объяснить другим, что проблема вообще существует.
Знаю, я всех уже заколебал, но сегодня, хотелось быть рассказать про разработанный мной 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 (
<View>
<View style={{ backgroundColor: leftColor }} />
<View style={{ backgroundColor: rightColor }} />
<Text>{message}</Text>
</View>
)
}
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-свойства ещё могут как-то валидироваться, то селекторы могут быть любыми. Поэтому в процессе эволюции приложения остаётся много не работающих стилей. Среда разработки не подсказывает возможные имена в селекторах и не способна их переименовывать по всему проекту. А сборщик не в состоянии проверить, что селектор хоть на что-то сматчится.
В Реакте сейчас популярны решения в духе css-in-js, позволяющие генерировать css в коде компонента. Даже если используются такие решения из TS, они не сильно далеко ушли от обычного CSS: типизируются свойства, да поддерживается переименовывание. Полноценно проверить используется ли в компоненте такой-то вложенный компонент не представляется возможным, так как TSX принципиально возвращает абстрактный VDOM узел, из которого невозможно узнать что там внутри него используется.
В $mol же любой компонент — это обычный класс, где для каждого вложенного компонента для доступа к нему автоматически создаётся типизированный метод. $mol_style вытягивает из глобального пространства имён все компоненты, анализирует их структуру и использует эту информацию для типизации описываемых через json стилей. Это даёт беспрецедентную типизацию стилей.
Например, пусть у нас есть такие компоненты:
$my_app $mol_view
sub /
<= Sidebar $my_page
$my_page $mol_view
sub /
<= Head $mol_view
<= Body $mol_view
Теперь мы можем при стилизации приложения добавить, например, стили всем кнопкам в теле сайдбара:
$mol_style_define( $my_app , {
Sidebar: {
Body: {
$mol_button: {
color: $mol_theme.text,
},
},
},
} )
При старте приложения это описание будет транслировано в такой CSS:
[my_app_sidebar_body] [mol_button] {
display: none;
}
Так как компоненты автоматически генерируют BEM-like атрибуты типа my_app_sidebar_body
для стилизации, то селекторы получаются одновременно и человекочитаемые и гарантированно не конфликтуют друг с другом. Да-да, и никаких непонятных хешей в именах классов.
В $mol пока ещё много вручную написанного CSS, но мы постепенно переписываем его на $mol_style. Благодаря МАМ сборщику можно использовать оба подхода вперемешку.
Печальная новость для других фреймворков — прикрутить к ним такую крутотень без смены архитектуры скорее всего не получится. А вот генерировать семантичные классы хоть и сложно, но вполне возможно.
История успеха
Следите за руками. Вот такое приложение было создано всего за 2 часа: https://notes.hyoo.ru
Оно умеет в полнотекстовый поиск с подсветкой найденного и синтаксиса.
Оно эффективно использует пространство экрана, адаптируясь к нему благодаря буклетному дизайну.
Оно работает в оффлайне и может быть установлено как PWA приложение.
Оно поддерживает локализацию.
Оно грузит всего 50КБ неминифицированного кода.
Оно стартует меньше чем за четверть секунды.
Оно адаптирует цвета к предпочтениям пользователя.
И оно такое не одно: https://showcase.hyoo.ru/
Это вызов!
Как насчёт челенжа? Возьмите фреймворк, в котором вы хорошо разбираетесь. И попробуйте реализовать приложение для ведения заметок, максимально соответствующее следующему чеклисту:
- Комфортная работа как на мобилке так и на десктопе.
- Работа в оффлайне.
- Поддерживается многоязычность и реализовано минимум 2 языка.
- Есть список тем и список заметок.
- Заметки сортируются по времени последнего изменения.
- Можно назначать заметке существующие темы или создавать новые.
- Можно фильтровать заметки по теме или не фильтровать ни по какой.
- Заметки, созданные в теме, автоматически добавляются к текущей теме.
- Есть поиск тем с подсветкой вхождения.
- Есть полнотекстовый поиск заметок с подсветкой вхождения.
- Можно удалять заметки и темы.
- Все данные сохраняются в локальное хранилище.
- В содержимом заметки подсвечивается синтаксис.
- Опрятный внешний вид, который не стыдно пустить в прод.
- Тёмная и светлая темы, выбираемые на основе предпочтений пользователя.
- Быстрая загрузка (проверять будем онлайн лайтхаусом).
- На любую заметку и тему можно сохранить ссылку.
- Работают кнопки перемещения по истории.
У вас есть 2 часа. Результаты выложите куда-нибудь онлайн, чтобы мы могли сравнить у кого круче получится. Всё это не для того, чтобы вас унизить или потешить моё эго. Всё это, чтобы показать, что вы можете расходовать своё время гораздо эффективнее, если захотите. Но для этого придётся что-то менять. Много чего менять.
Куда ветер дует
У каждой компании свой ui-kit. Своя реализация кнопочек, чекбоксиков, модалочек, селектиков. Одни и те же компоненты только разных цветов из раза в раз реализуются разными командами. Причины тут 2:
-
Распиаренные технологии мешают переиспользовать чужой код. Изоляция стилей, приватные апи, жёсткие шаблоны — всё это не даёт кастомизировать готовые компоненты под проект и приходится реализовывать новые, такие же как существующие, но с перламутровыми пуговичками.
-
Нет стандартной библиотеки компонент. Там ui-kit с 20 компонентами, тут ui-kit с 15 компонентами, но другим апи. А для реального приложения нужно куда больше — вот и пилят вновь и вновь одно и то же.
Распиаренные технологии типа Реакта с Редаксом требуют писать много кода. А чтобы его писать и поддерживать требуется куча людей. Получаем в итоге раздутый штат, который месяцами может разрабатывать тривиальное приложение, получая на выходе неудовлетворительный результат.
И ладно там всякие банки и прочие около-IT-компании. Но с нашей ведущей IT-компанией — Яндексом — ситуация совсем печальная. Вот уж у кого, а у него есть и ресурсы и авторитет двигать индустрию вперёд. И какое-то время он даже пытался это сделать, разрабатывая свой BEM-стек. При всех его недостатках и недальновидности, в нём были возможности, которые редко встретишь в наколеночных поделках типа Реакта, но которые просто необходимы при разработке большой экосистемы проектов: возможность кастомизации готовых компонент под проект. Сейчас же в Яндексе набралась критическая масса реакт-разработчиков и идёт переход на Реакт. Печально наблюдать, как какая-то паршивая социальная сеточка диктует Яндексу как правильно писать фронтенд. Причём в самом FaceBook места использования их же Реакта надо ещё поискать.
Мне стыдно за индустрию, в которой я работаю. Где технологические решения принимаются на основе популярности, а не технического анализа. Где годами копошатся в своих песочницах, создавая одни и те же компоненты, вместо того, чтобы вместе развивать единую экосистему переиспользуемых высокоуровневых решений.
Разбор полётов
Давайте взглянем на страничку, демонстрирующую как всё круто устроено во фронтенде у Альфа-банка: https://digital.alfabank.ru/
Альфачи, ничего личного, но вы сами выложили свои разработки на публику, так что держите их объективную оценку. Без лицемерия и скидок.
Нас встречает 8 карточек-ссылок с короткими текстами и небольшой список из 5 пунктов, объясняющий зачем всё это нужно. Чтобы всё это показать, грузятся скриты на 190 КБ и стили на 90 КБ. Итого: почти 300 КБ, чтобы показать тривиальную абсолютно статичную страничку. На "Slow 3G" загрузка всего этого добра до собственно отображения занимает 12 секунд.
На самом деле скрипты для отображения вовсе не обязательны, так как весь нужный HTML приходит уже готовый с сервера. То есть применяется подход SSR+hydrate, когда сервер, прежде чем отдать страничку, рендерит её с помощью Реакта и выплёвывает HTML и state. А на клиенте, скрипты берут этот стейт и прирендеривают себя к существующему DOM. Это позволяет быстро показать страницу, но она будет совершенно мёртвая, пока скрипты не загрузятся и не проинициализируются.
Если скрипты заблокировать, то скорость загрузки вдруг уменьшится до 8 секунд. Всё дело в том, что параллельная загрузка нескольких скриптов забивает канал и, как следствие, замедляет загрузку стилей. И если без скриптов браузер может что-то показать, то без стилей — нет.
Давайте присмотримся, что же это за стили. Оказывается, самый большой CSS, который сильно тормозит отображение — это библиотека со ссылками на все возможные иконки. И это чтобы показать 3 невзрачных логотипа в подвале. Если заблокировать и этот CSS, то загрузка ускоряется до 5 секунд.
Ну ладно, это всего-лишь начальная страница, если провалиться глубже, например, в галерею компонент, все эти скрипты и стили будут взяты из кеша. Замечательно, но только загрузки HTML всё-равно приходится ждать 2 секунды, так как при использовании SSR нельзя закешировать html, который является точкой входа для остальных ресурсов, а значит и бутылочным горлышком.
Но через 2 секунды мы видим лишь шапку и боковое меню. Собственно галерея компонент — это отдельное SPA приложение весом в 600 КБ, которое грузится во фрейме ещё 16 секунд.
Ого, там наверное много всего? Да нет, всего 50 довольно простых компонент и полторы сотни их демонстраций в разных вариантах. Для сравнения — весь mol.js.org с 45 компонентами, их довольно комплексными демонстрациями и конфигуратором весит всего 110 КБ.
Ну да ладно, это же не продакшен. В реальных приложениях ведь будут не все компоненты, не во всех вариантах и без доп описания. Идём на https://alfabank.ru/
400 Кб скриптов и стилей. Общее время загрузки 17 секунд. Это всё только лишь для того, чтобы показать менюшку, галерею, 8 баннеров, подвал и навороченный калькулятор ипотеки аж из 6 полей.
Ок, со временем загрузки всё плохо. Может оно хоть работает адекватно? Берём первый попавшийся компонент — amount. Это довольно простая но весьма распространённая в банковской сфере штука, которая выводит число рублей в удобном для человека виде. Посмотрим, что он нам генерирует:
<div class="amount">
<h3 class="heading heading_size_m heading_margins_m heading_theme_alfa-on-white">
<span>
<span class="amount__major">1 233</span>
<div class="amount__minor-container">
<span class="amount__separator">,</span>
<span class="amount__minor">43</span>
</div>
<span class="amount__currency"> ₽</span>
</span>
</h3>
</div>
Целых 8 элементов. Дивы внутри спанов. Отступы пробелами. Жесть, в общем. И это делают не начинающие разработчики. Это результат деятельности команды профессионалов, который она не стесняется презентовать миру и даже хвастается этим.
Как это должно выглядеть у здорового человека:
<h3 class="amount">
<span class="amount__major">1 233</span>
<span class="amount__minor">,43 ₽</span>
</h3>
Ладно, генерирует оно парашу, но может хоть использовать это удобно, что позволит делать в два раза больше за то же время? Ну, давайте посмотрим как положить amount в button:
const FeeButton = ( { id , submit , size = 'm' , theme = 'dark' , className = '' } ) => (
<Button
onClick={ submit }
className={ "fee-button__submit " + className }
data-test-id={ id }
size={ size }
theme={ theme }
>
<Label
className="fee-button__prefix"
size={ size }
theme={ theme }
>
{ l10n( 'fee-button__prefix' ) }
</Label>
<Amount
className="fee-button__fee"
data-test-id={ id + '/fee' }
amount={ fee }
size={ size }
theme={ theme }
/>
</Button>
)
О господи, за что так много копипасты? Зачем прокидывать размер и тему в каждый компонент? Зачем руками собирать имена классов и идентификаторы для тестирования для каждого компонента? А как это бы выглядело, если бы альфачи бросили курить:
<Button id="pay-submit" onClick={ submit }>
<Label id="pay-prefix">{ payPrefix }</Label>
<Amount id="pay-fee" amount={ fee }/>
</Button>
Ну или, наоборот, закурили бы чего-то более забористого:
<= Pay_submit $alfa_button
click?event <=> submit?event null
sub /
<= Pay_prefix $alfa_label
text <= pay_prefix @ Pay
<= Fee $alfa_amount
amount <= fee $alfa_currency
Ну, зато jQuery реакт-разработчика легче найти на рынке труда. А то те, что уже наняты, не успевают производить достаточное количество однотипного кода. Ведь именно так сейчас обосновывают выбор Реакта для своих разработок. А не скоростью разработки, экономией бюджета или качеством результата.
Я думал пройтись и по другим известным "наборам компонент", но решил не раздувать статью описанием одних и тех же косяков:
- Как правило это Реакт в основе и куча кривых велосипедов к нему.
- Налепленные сбоку библиотеки с разношёрстным API разной степени качества.
- Всё это слеплено вместе через кучу костылей и гору копипасты.
- Огромные бандлы, загружающиеся десятки секунд.
- Долгая инициализация приложения, особенно на мобилках.
- Всё это либо тормозит в работе, либо требует кучу работы с напильником для оптимизации.
- Всё это плохо адаптируется к размерам экрана, либо требует кучу работы по адаптации.
Да что там, некоторые даже банальную кнопку не могут реализовать, чтобы она не выжирала ядро процессора невидимой анимацией.
Полный П
С $mol ситуация сейчас плачевная. Его пилят полтора землекопа на чистом энтузиазме. И мы понимаем, что так не может продолжаться вечно. Я пробовал смотреть в сторону иных фреймворков, но я не вижу достойных альтернатив, чтобы добиваться качественного результата минимумом усилий. Меня очень не радует необходимость бороться со всей индустрией и развивать маргинальное решение. Но я не вижу другого выхода.
Да, в $mol много шероховатостей. Но и прорывных идей в нём куда больше, чем в очередном убийце Реакта (реально, крутая штука, исправляющая родовую травму JSX — push семантику, заменяя её на pull, прямо как в $mol).
Под крыло
Я пробовал подкатывать ко крупным компаниям, у которых много проектов, которые должны выглядеть единообразно и по максимуму шарить код друг с другом. Это те условия, в которых $mol показывает себя особенно хорошо. Но встречал я лишь стеклянные глаза и отсутствие заинтересованности. Всех всё устраивает, проблемы не видят.
Идеальный вариант — какая-либо компания, захочет вырваться вперед, относительно конкурентов, и выпускать качественные продукты как пирожки. Для этого ей потребуется гораздо более эффективный инструмент, чем в среднем по больнице. Такой как $mol. И она могла бы взять его под своё крыло. Это реально могло бы всколыхнуть индустрию. И это возможно даже привело бы к появлению чего-то ещё более крутого. Но это мало вероятно, так как тут нужна не малая доля смелости и уверенности в своих силах, не свойственная нашему менталитету.
Сообщество
Из меня такой себе маркетолог. И даже в русском сообществе я не достиг успеха в продвижении $mol. Что уж говорить про международный уровень с моим-то не самым лучшим английским.
Я написал эту статью в надежде заинтересовать вас идеями и перспективами фреймворка. И если мне это удалось, то призываю вас принять участие в этой маленькой революции. Я не предлагаю, конечно, срочно бросать работу, забивать на семью и начинать неистово контрибьютить. Но мы будем рады даже самому маленькому вкладу. Вы могли бы:
- Рассказать другу/коллеге/начальнику об интересных фичах $mol. Как минимум это может подтолкнуть его к реализации чего-то подобного в других фреймворках. Как максимум — попробовать фреймворк в деле.
- Создать какой-то компонент или поправить существующий. Это вообще самый быстрый способ разобраться во фреймворке и прочувствовать его особенности. Он очень необычный и расширит ваш кругозор, что будет полезно даже если не будете в дальнейшем использовать. А обратная связь помогла бы нам сделать $mol ещё лучше.
- Дополнить документацию или написать туториал. Я уже написал несколько, но как автору, мне сложно понимать, чему стоит уделить большее внимание, и как понятней формулировать.
- Взять на себя развитие одного из реализованных на $mol приложений.
- Да даже просто написать разгромную статью, рассказывающую как в $mol всё плохо. Мы будем только рады хорошей критике. Да и моим рассказам о том, какой $mol классный, мало кто верит. Не ангажированный взгляд мог бы сформировать у аудитории более объективное мнение.
Гильдия Ответственных Разработчиков
Нет $mol разработчиков — нет заказов разработки на $mol. Нет заказов — нет стимула изучать $mol. Нет стимула изучать — нет разработчиков на $mol. Замкнутый круг, который надо как-то разрывать.
Из меня такой себе бизнесмен. Ни опыта, ни, скорее всего, навыков. Так что организовывать свою компанию по разработке на заказ мне и боязно, и не особо интересно. Кроме того, на мой взгляд, это не очень справедливо, что трудятся обычные программисты, а львиную долю прибыли забирают владельцы бизнеса.
У меня есть идея создания гильдии. Потенциальный заказчик приходит в гильдию и предлагает проект. Члены гильдии договариваются о цене и этапах разработки. После чего распределяют между собой ответственность за реализацию той или иной функциональности. Соответственно, фиксируются сроки и критерии приёмки. За каждым членом гильдии закрепляется куратор, который делает ревью. Если ответственный по какой-либо причине не может закончить проект, то куратор берёт проект на себя и доводит до конца. Соответственно, оплата делится пропорционально проделанной работе. Если проект завален, деньги возвращаются заказчику. Но по идее сеть кураторов должна такого не допускать.
В процессе разработки неизбежно будут появляться новые компоненты и улучшаться существующие, что будет совершенствовать общую библиотеку компонент. Права на не специфичный для заказчика код, остаются за гильдией, что позволит не велосипедить в дальнейших проектах, а использовать существующие наработки. Например, таким образом в рамках проекта может быть реализован "компонент типичной формы входа и авторизации". А в проекте он кастомизирован логотипом заказчика. Получается, что заказчик таким образом оплачивает улучшение компонентной базы $mol. Однако ему это всё-равно будет выгоднее, чем заказывать то же приложение у другой компании, которая как правило использует неэффективные технологии, нанимает таких себе программистов, а львиную долю выручки тратит далеко не на собственно разработку.
Обращаться к гильдии выгоднее, чем к фрилансерам, так как в гильдии у каждого есть страховка, и гильдии не выгодно делать задачи абы как. Соответственно, вступить в гильдию может не любой джуниор с улицы, а лишь тот, кому члены гильдии могли бы доверить очередной проект с уверенностью, что он способен довести его до конца. Лучший способ показать это — взять один из свободных $mol проектов под своё крыло и начать его развивать. Или создать свой. Или просто начать контрибьютить во фреймворк. Со своей же стороны мы постараемся оказывать посильную помощь в изучении технологий и реализации задач. Так что не бойтесь, один на один с документацией мы вас не оставим.
Ну и, понятное дело, разработчики — такие себе маркетологи. Так что, если порекомендуете нас заказчику, которому важно прежде всего качество результата, то можете рассчитывать на 10% от суммы сделки. Сочетание передовых технологи и большого опыта разработки, позволяет нам работать быстрее фрилансеров и качественнее инхаус разработчиков. А где эффективность — там выгода обеим сторонам процесса.
Контакты
- @mam_mol — обсудить MAM, $mol или просто быть в курсе новостей.
- @nin_jin — вступить в гильдию, заказать разработку или ещё как-либо посотрудничать.
Автор: nin-jin