Синопсис


Эта статья основана на опыте разработки, приложения системы конструирующей типовые Landing-page по заранее заданным в панели управления параметрами. В статье будет описано почему для разработки приложения был выбран Mithril, его свойства, преимущества и какие подводные камни можно встретить при использовании этого замечательного инструмента.

Кому может быть интересна данная статья?


Данная статья может заинтересовать тех кто изучает использование микро-фреймворков для решения определенных задач в которых нужен удобный поддерживаемый инструментарий, с легкой и практичной архитектурой, и не нагруженной общей оболочкой. К тому же Mithril как инструмент несмотря на малый размер обладает высокой производительностью — что также является не маловажным фактором при выборе нужного инструмента разработки. Интересно?

Почему именно Mitrhil? Мотивация использования



Коротко о самом Mithril — что это такое, и что в нем есть.


Mithril это MVC микро-фреймворк, модель отображения которого основана на Virtual DOM, который также добавляет производительности отрисовки для больших приложений, или компактных но сложных со стороны логики отрисовки виджетов. У mithril-a есть все необходимое для того чтобы называть себя полноценным фреймворком. Есть система работы с логикой — контроллеры, средства работы со связыванием данных модели, и полноценная система для работы с отрисовкой. Также он предоставляет утилиты позволяющие на низком уровне точечно управлять временем и типом перерисовки контента (например для сложных асинхронных операций), и компактные дополнения:
  • Роутер — который позволяет привязать компоненты как страницы к определенному адресу в бразуере.
  • Web Сервисы — методы позволяющие работать с запросами онлайн данных по http(s), в которую встроено поведение адаптации под особенности фреймворка.

Все эти компоненты и составляющие позволяют разрабатывать приложения на mithril которые покроют 90% потребностей во время написания кода.

Почему выбрали именно Mithril?


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

Поэтому вскоре мы отбросили вариант серверной реализации, и стали рассматривать варианты client-side инструментов, которые позволили бы нам решить малой кровью (и лишних потраченных нервных клеток) спектр заданных задач:
  1. Производительность инструмента.
  2. Удобная организация данных.
  3. Управление и скорость отрисоки.
  4. Поддержка браузерами: начиная с IE9, а также мобильные браузеры.
  5. Удобное и практичное управление логикой, без избыточных «прикладных» взаимодействий
  6. Максимальная компактность инструмента, без избыточного функционала.
  7. Выходя из прошлого утверждения — максимальная «ванильность» инструмента.
  8. Быстрое подключение к разработке новых разработчиков не знакомых с инструментом.
  9. Возможность адаптации в будущем с учетом SEO.
  10. Компонентно независимая разработка — без необходимости монолитных решений.


Среди рассматриваемых инструментов мы рассматривали множество библиотек вроде — 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

Комментарии (26)