Микрофронтенды с Mithril

Mithril — это современный клиентский JavaScript-фреймворк, ориентированный на создание быстрых и лёгких веб-приложений. Его ключевое отличие от других фреймворков заключается в минимальном объёме и высокой производительности. Архитектура приложений на Mithril базируется на компонентах и виртуальном DOM, что позволяет эффективно управлять состоянием интерфейса и обновлять только изменившиеся элементы.

Компоненты — основной строительный блок Mithril-приложений. Каждый компонент — это объект с методом view, который возвращает описание UI в виде виртуальных узлов. Компоненты могут содержать локальное состояние и методы жизненного цикла (oninit, oncreate, onupdate, onremove).

const MyComponent = {
    oninit(vnode) {
        vnode.state.counter = 0;
    },
    view(vnode) {
        return m("button", {
            onclick: () => vnode.state.counter++
        }, `Нажато ${vnode.state.counter} раз`);
    }
};

Структура проекта на Mithril обычно минимальна: один основной файл входа, папка компонентов и, при необходимости, папка для моделей данных и утилит. Это упрощает работу с микрофронтендами, так как каждый микрокомпонент может быть автономным и подключаться к основной системе через стандартный интерфейс.


Микрофронтенды на Mithril

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

Ключевые аспекты при разработке микрофронтендов с Mithril:

  • Изоляция состояния: каждый микрофронтенд имеет собственное состояние и не зависит напрямую от глобального состояния приложения. Это снижает риск побочных эффектов и облегчает тестирование.
  • Локальные маршруты: Mithril предоставляет встроенный роутер, который можно использовать для управления состоянием конкретного микрофронтенда без вмешательства в основной роутинг.
  • Минимальные зависимости: Mithril не требует сторонних библиотек для управления состоянием или AJAX-запросами. Встроенные функции m.request и виртуальный DOM делают интеграцию лёгкой и автономной.

Пример микрофронтенда с локальным маршрутом:

const MicroApp = {
    oninit(vnode) {
        vnode.state.page = "home";
    },
    view(vnode) {
        return m("div", [
            m("nav", [
                m("button", {onclick: () => vnode.state.page = "home"}, "Главная"),
                m("button", {onclick: () => vnode.state.page = "about"}, "О нас")
            ]),
            vnode.state.page === "home" ? m("div", "Контент главной") : m("div", "Контент о нас")
        ]);
    }
};

m.mount(document.getElementById("micro-root"), MicroApp);

Коммуникация между микрофронтендами

При объединении нескольких микрофронтендов важно наладить обмен данными между ними. Mithril предоставляет несколько подходов:

  1. Глобальное состояние через простой объект:

    const store = { user: null };

    Каждый микрофронтенд может читать и изменять это состояние, сохраняя при этом изоляцию компонентов.

  2. Событийная шина: реализация через простой pub/sub:

    const EventBus = {
        events: {},
        emit(event, data) {
            (this.events[event] || []).forEach(fn => fn(data));
        },
        on(event, fn) {
            this.events[event] = this.events[event] || [];
            this.events[event].push(fn);
        }
    };
    
    EventBus.on("userUpdated", data => console.log("Пользователь обновлён", data));
    EventBus.emit("userUpdated", { name: "Alice" });
  3. Передача свойств через vnode.attrs: при монтировании одного компонента в другой можно передать данные напрямую.


Асинхронные данные и интеграция API

Mithril поддерживает работу с асинхронными запросами через функцию m.request. Это позволяет микрофронтендам загружать данные независимо от основной части приложения:

const DataComponent = {
    oninit(vnode) {
        vnode.state.items = [];
        m.request({ method: "GET", url: "/api/items" })
         .then(result => vnode.state.items = result);
    },
    view(vnode) {
        return m("ul", vnode.state.items.map(item => m("li", item.name)));
    }
};

Важно помнить, что m.request возвращает промис, и Mithril автоматически инициирует перерисовку после изменения состояния.


Оптимизация и производительность

Mithril разработан с акцентом на производительность. При работе с микрофронтендами важно соблюдать несколько правил:

  • Избегать частых глубоких изменений в глобальном состоянии. Лучше использовать локальные состояния компонентов.
  • Использовать key для списков элементов, чтобы виртуальный DOM мог эффективно определять изменения.
  • Минимизировать количество компонентов, которые ререндерятся одновременно. Применение условного рендеринга и разделение UI на независимые компоненты повышает производительность.
m("ul", items.map(item => m("li", { key: item.id }, item.name)));

Интеграция с другими микросервисами

Mithril можно использовать совместно с другими микрофронтенд-стеками, так как его API компактно и не конфликтует с внешними библиотеками. В больших системах можно:

  • Загружать отдельные микрофронтенды динамически через import() и монтировать их в определённые контейнеры DOM.
  • Использовать контейнеры с Shadow DOM для полной изоляции стилей и событий.
  • Управлять состоянием через глобальный объект или отдельный event bus, сохраняя независимость микрофронтендов.

Жизненный цикл компонентов

Понимание жизненного цикла компонентов Mithril критично при проектировании микрофронтендов:

  • oninit(vnode) — инициализация состояния и привязка данных.
  • oncreate(vnode) — доступ к DOM после монтирования.
  • onupdate(vnode) — вызов при обновлении DOM.
  • onremove(vnode) — освобождение ресурсов и очистка слушателей.

Эти методы позволяют создавать сложные микрофронтенды с асинхронной загрузкой данных, анимациями и интеграцией с внешними библиотеками.


Пример комплексного микрофронтенда

const ComplexMicroApp = {
    oninit(vnode) {
        vnode.state.items = [];
        vnode.state.loading = true;
        m.request({ method: "GET", url: "/api/items" })
         .then(result => {
             vnode.state.items = result;
             vnode.state.loading = false;
         });
    },
    view(vnode) {
        return m("div", [
            vnode.state.loading ? m("p", "Загрузка...") :
            m("ul", vnode.state.items.map(item => m("li", { key: item.id }, item.name))),
            m("button", { onclick: () => console.log("Действие кнопки") }, "Кнопка действия")
        ]);
    }
};

m.mount(document.getElementById("micro-root"), ComplexMicroApp);

Такой подход обеспечивает автономность микрофронтенда, поддерживает асинхронную загрузку данных, обработку событий и лёгкую интеграцию в крупные системы.