Синопсис
Эта статья основана на опыте разработки, приложения системы конструирующей типовые Landing-page по заранее заданным в панели управления параметрами. В статье будет описано почему для разработки приложения был выбран Mithril, его свойства, преимущества и какие подводные камни можно встретить при использовании этого замечательного инструмента.
Кому может быть интересна данная статья?
Данная статья может заинтересовать тех кто изучает использование микро-фреймворков для решения определенных задач в которых нужен удобный поддерживаемый инструментарий, с легкой и практичной архитектурой, и не нагруженной общей оболочкой. К тому же Mithril как инструмент несмотря на малый размер обладает высокой производительностью — что также является не маловажным фактором при выборе нужного инструмента разработки. Интересно?
Почему именно Mitrhil? Мотивация использования
Коротко о самом Mithril — что это такое, и что в нем есть.
Mithril это MVC микро-фреймворк, модель отображения которого основана на Virtual DOM, который также добавляет производительности отрисовки для больших приложений, или компактных но сложных со стороны логики отрисовки виджетов. У mithril-a есть все необходимое для того чтобы называть себя полноценным фреймворком. Есть система работы с логикой — контроллеры, средства работы со связыванием данных модели, и полноценная система для работы с отрисовкой. Также он предоставляет утилиты позволяющие на низком уровне точечно управлять временем и типом перерисовки контента (например для сложных асинхронных операций), и компактные дополнения:
- Роутер — который позволяет привязать компоненты как страницы к определенному адресу в бразуере.
- Web Сервисы — методы позволяющие работать с запросами онлайн данных по http(s), в которую встроено поведение адаптации под особенности фреймворка.
Все эти компоненты и составляющие позволяют разрабатывать приложения на mithril которые покроют 90% потребностей во время написания кода.
Почему выбрали именно Mithril?
Во время проработки архитектуры, выбор был между клиентской и серверной организацией, учитывая что сама система по сути была минимально нагруженной данными, и все они были типовые, обработка всех запросов, дополнительные накладки в виде обработки шаблонов, их отдача, и время ожидания ответа клиентом — были крайне неприятными факторами, так как в наше время скорость загрузки и «мгновенной» читабельности контента крайне важна, а лишняя нагрузка на оборудования это лишняя издержка.
Поэтому вскоре мы отбросили вариант серверной реализации, и стали рассматривать варианты client-side инструментов, которые позволили бы нам решить малой кровью (и лишних потраченных нервных клеток) спектр заданных задач:
- Производительность инструмента.
- Удобная организация данных.
- Управление и скорость отрисоки.
- Поддержка браузерами: начиная с IE9, а также мобильные браузеры.
- Удобное и практичное управление логикой, без избыточных «прикладных» взаимодействий
- Максимальная компактность инструмента, без избыточного функционала.
- Выходя из прошлого утверждения — максимальная «ванильность» инструмента.
- Быстрое подключение к разработке новых разработчиков не знакомых с инструментом.
- Возможность адаптации в будущем с учетом SEO.
- Компонентно независимая разработка — без необходимости монолитных решений.
Среди рассматриваемых инструментов мы рассматривали множество библиотек вроде — Angular, Angular Light, Backbone, Knockout, React. Также рассматривали не только готовые фреймворки и комплексные инструменты но и отдельно системы шаблонов, библиотеки данных и т.д.
Причины по которым отсеивали прочие инструменты
Почему все эти варианты были отброшены во время анализа? Вот короткий обзор по списку в чем эти инструменты оказались не актуальны в выборе.
Angular и React не подходят сразу из-за своей избыточности, потому что 99% их функционала и организации в процессе не понадобятся.
Backbone — был отсеян по причине черезмерно избыточной логики, которая осталась атавизмом далеких времен, и разработка на нем поставила бы нас перед выбором практически прямого управления всем контентом, и в целом неявной структурой логики.
Knockout — отличная библиотека, но слишком большая для набора манипуляций которые в него вложены, их очень много и изначально они рассчитаны на большое количество кода для отрисовки в шаблонах (со связью с данными в логике и моделях). Управление же логикой так же как и в Backbone очень специфичное, хотя и позволяющее адекватно работать с данными и связыванием. В нашем приложении этот функционал был излишним — и было решено отказатся от этого варианта также.
Angular Light — микро аналог angular с заявленной похожей логикой, в том числе шаблонов, и «ленивыми» директивами и компонентами. От оригинальной логики и поведения директив из angular осталось не так много, упрощенная но похожая система шаблонов, система сравнения в дереве и инициализации директив — по тегу, классу, аттрибуту, и подобие $scope связывания. Хороший инструмент — но имеет довольно специфичную и не очень гибкую логику компонентного построения, и также были первоначальные трудности с пониманием ее точной работы. Этот вариант оставался запасным и рабочим до самого конца.
Mithril
Когда в прицел выбора инструмента попал mithril — это уже был вполне зрелый и состоявшийся проект набора инструментов для написания приложений без излишеств, но с полноценными функциональными возможностями. У этого инструмента не было тех атавизмов или излишней шаблонной логики как у Backbone или Knockout — по сути mithril являлся небольшой оболочкой над DOM элементами, большая часть событий которых была напрямую завязана на DOM методы, с небольшим добавлением сахарка в виде Virtual DOM и привязанного к объекту логики контроллера. При этом связывание данных, и большая часть излишеств были исключены из логики — что открывало максимальную гибкость для реализации. Он собрал в себе весь минимализм и практичность которая необходима при разработке емких и расширяемых приложений. В отличие от Angular Light в нем была более прозрачная логика компонентов, более практичная и унифицированная — что позволяло легко поддерживать код, и не думать о внезапных проблемах с тонкостями архитектуры.
Наводим красоту — сахарок, удобства, полезности
По умолчанию разработка компонентов на mithril-е это действительно процесс в котором правит абсолютное ничто, для большего удобства разработки можно использовать множество различных инструментов и библиотек по вашему вкусу, но считаю что есть базис который будет полезен всем кто начинает разрабатывать свои приложения на mithril.
Общие советы, Шаблоны, Классы и ООП
Шаблоны
По умолчанию в Mithril как и в React довольно неудобно организованно написание узлов шаблонов, даже несмотря на систему упрощения построения DOM element query:
// Объект контейнера с атрибутом "class" элемента.
m('div', { "class": "container-class" }, [
// .. и дочерним элементом заголовка, в в котором задан статичный аттрибут через упрощение шаблона. Со значением.
m('h1[withattr="attr"]', { onclick: function(event) { /* манипуляции для события */ } }, 'Header')
]);
Особенно эта особенность ощущается если вы привыкли к использованию обычного HTML, и не хотите заморачиваться с сложностями архитектуры отображения mithril. Для этого есть специальный трансформер — MSX, и оболочки для него — как например самая удобная на мой взгляд babel-plugin-mjsx для компилятора Babel.
Который позволяет использовать подобные конструкции которые нам так знакомы из React JSX:
<div class="container-class">
<h1 withattr="attr" onclick={ (event) => { /.../ } }>Header</h1>
</div>
По моему эту опцию (MSX) будет весьма удобно использовать таким комплектом:
gulp + browserify + babelify + msx.
Вот пример использования.
// Опции и конфигурация компилятора Babel с необходимыми нам опциями и флагами.
var Compiler = babelify.configure({
// Флаги компилятора интегрированные в babel core
optional: [
'es7.exponentiationOperator',
'es6.spec.blockScoping',
'es7.exportExtensions',
'es7.classProperties',
'es7.comprehensions',
'es7.decorators'
],
// Дополнительные кастомные плагины преобразований
plugins: [{
transformer: MSX, // Собрать MSX
position: "before" // Вызвать до основной компиляции
}]
});
gulp.task('babel-msx', function() {
browserify([ /* файлы для компиляции*/ ])
.transform(Compiler).bundle() // Интеграция промежуточного трансформера Browserify
.pipe(source('bundle.js'))
.pipe(gulp.dest('build/'));
});
Классы и ООП
В 2015 году особую популярность начал приобретать ES6, (или его расширенная черновая «сахарная» редакция ES2015 «ES7») и преобразующие его в ES5 компиляторы вроде Babel или Typescript: многим захотелось в полной мере начать использовать максимум его возможностей, включая поддержку классов, декораторов, и многое другое. Под новые реальности начали адаптироваться уже существующие фреймворки (например Angular 2.0), а также появляться — как например Aurelia, и поэтому использование всех возможностей, и гибкость инструментов к этим возможностям — для многих может являться важным фактором при разработке.
По умолчанию Mithril организован внутри обычного объекта, который может являться самостоятельным компонентом в архитектуре, и особые возможности организации для использования как классов — в них не реализованы, по соображениям того же минимализма, но это не значит что нельзя расширить или адаптироваться под Объектную разработку в этом инструменте.
В простейшем варианте — вам достаточно просто использовать объект компонента как экземпляр класса — например так:
/* Класс компонента */
class Component {
constructor(opts) {
/* связывание экземпляра с даными инициализации... */
this.opts = opts;
/* связать функции с областью экземпляра */
this.controller = this.controller.bind(this);
this.view = this.view.bind(this);
}
/* контроллер компонента */
controller(props) {
return {
paramOne: this.opts.paramOne
};
}
/* метод отрисовки компонента*/
view(ctrl, props, children) {
return (
<button onclick={this.method}
attr={ctrl.paramOne} />
);
}
};
/* инициализация компонента - целевым объектом компонента назначаем экземпляр */
m.mount(document.body, new Component({ paramOne: 'Hello!' }));
Это простейший способ использовать классы в Mithril — но тем не менее это все-же не идеальный вариант, в котором также стоит учитывать некоторые особенности, например не стоит хранить абсолютно все данные в экземпляре, и хранить их предположим в view — так вы не иллюзорно рискуете распрощаться с данными при последующей перерисовке — так что логику, и данные лучше держать раздельно. При этом хранить данные и манипуляции с ними в контроллере тоже не лучший выход — тогда вам придется создавать целое дерево зависимостей, в случае если придется передавать данные вглубь компонентной иерархии, что не добавит простоты и поддерживаемости и без того «ванильному» коду.
Можно например использовать при построении приложения Flux Store или любое другое хранилище которое вам удобнее всего использовать — и адаптировать его внутри компонента, тогда у вас будет четкая иерархия хранения данных, их обработки, и четкая граница абстрактных событийных операций внутри экземпляра — вроде индекса активированной вкладки, и т.д. События actions вы сможете привязать к рендеру, отрисовке, и даже создать импровизированное подобие живого цикла как в React со своими componentWillMount, componentDidMount и т.д.
Архитектурные особенности, подводные камни, личные заметки
Отрисовка
Mithril основан на Virtual DOM и это одна из его отличительных особенностей, которая дает ему его скорость и производительность, но учитывая что это минималистичный фреймворк — в его системе отрисовки есть особенности которые изначально могут немного удивить разработчиков, например тех кто уже имел некоторый опыт в React. Например в Mithril вы не можете управлять перерисовкой отдельно взятого узла или компонента отдельно от всех остальных компонентов — как вы могли бы это сделать например в React. И это накладывает некоторые трудности с пониманием — поэтому стоит разобратся как работает перерисовка в Mithril.
В пользовательском доступе доступны 2 вида перерисовки:
m.startComputation/m.endComputation — методы которые позволяют работать с организацией состояния при перерисовки, буквально — они обозначают отрезки первого снимка дерева VDOM, и второго снимка, после этого оба снимка сравниваются, и после найденные изменения образуют патч — который точечно изменяет данные. Это полезно например при загрузке асинхронных данных, в этом случае вы делаете первый снимок до начала операций, и делаете последний снимок уже после того как получили результат. Из особенностей стоит отметить — что методы глобально сравнивают все дерево целиком, даже если вы вызвали их в каком-либо определенном компоненте, поэтому стоит позаботится при разработке чтобы перерисовка не повлияла на другие компоненты в дереве. Вот простой пример использования:
function asyncGetAndRedraw() {
/* Создаем снимок до целевого действия */
m.startComputation();
/* Асинхронная операция */
SomeAsyncOperation({ ...args }).then(function promised(data) {
/* обновление данных (которые используются в представлении) */
setTarget = data;
/* создание снимка после целевых действий, сравнение с первым снимком, патч представления */
m.endComputation();
});
};
m.redraw — метод для жесткой отрисовки, при вызове этого метода не делается сравнение, метод целиком перерисовывает все дерево компонентов, следует относится к этому методу с особой осторожностью — потому что при перерисовке он не учитывать уже существующие состояния компонентов.
Использование обычного HTML в компонентах
Довольно опасная и не рекомендуемая операция, но есть моменты когда нужно вставить строку шаблонного чистого HTML, и с этим поможет метод m.trust, вот как это можно использовать.
m.render("body", [
m("div", m.trust("<p>Clean Html</p>"))
]);
В заключение
В этой небольшой статье было разобрано чем может быть интересен микро-фреймворк Mithril при разработке веб-приложений, какие у него есть преимущества, сравнительный анализ с другими инструментами (по личной оценке и нуждам), а также немного сахара и заметок по использованию этого фреймворка обывателями. Из вышеизложенного считаю стоит подчеркнуть что Mithril в первую очередь предназначен для написания компактных, ёмких веб-приложений (например виджетов) которым нужна максимальная эффективность и лаконичность при разработке — вкупе с производительностью. Хотя если вы тщательно продумаете архитектуру, допишите свои реализации, методы и парочку суперклассов и декораторов как оболочек — этот инструмент не подведет вас и на крупных проектах и веб-приложениях. В статье было изложено мое мнение и наблюдения по использованию mithril — если у вас тоже есть опыт использования этой системы или тонкости которые вы для себя открыли — было бы очень интересно о них узнать. Рассчитываю что статья была интересна и полезна к прочтению.
Дополнительно
Оффициалньый сайт.
Страница фреймворка на github
Полезные статьи про использование Mithril