Mithril — JavaScript-фреймворк размером около десяти килобайт, которого достаточно для полноценного одностраничного приложения. В нём нет реактивных сигналов, компилятора и сотни зависимостей: есть функция m(), виртуальный DOM, роутер и HTTP-клиент. В 2026 году это разумный выбор для админок, внутренних инструментов и небольших сайтов, где важен контроль над кодом. Ниже — практика: установка, компоненты, роутинг, запросы, сборка и типичные ошибки.

Почему Mithril

Mithril занимает нишу между ванильным JavaScript и крупными фреймворками. Он не навязывает структуру каталогов и не требует JSX: представление описывается вызовами m("div", {class: "card"}, ...). Виртуальный DOM пересчитывается после каждого события, а diff применяет только реальные изменения.

  • Один пакет без обязательных зависимостей.
  • Роутер, HTTP-клиент и компоненты в комплекте.
  • Понятный жизненный цикл и предсказуемый redraw.
  • Быстрый старт без сборщика, но с поддержкой esbuild и Vite.
  • Тесты через собственный раннер ospec.

Ограничения тоже честные: нет серверного рендеринга из коробки, нет готовой экосистемы компонентов и меньше готовых решений для сложных форм. Для большого продукта с десятком команд это может стать проблемой.

Первое приложение

Достаточно пакета mithril и любого статического сервера. Для сборки возьмём esbuild: он ставится одним пакетом и собирает бандл за доли секунды. Если проект на TypeScript, добавьте типы из @types/mithril.

npm init -y
npm install mithril
npm install --save-dev esbuild ospec
mkdir -p src dist

Минимальное приложение — компонент с методом view. Функция m.mount привязывает его к элементу и берёт на себя перерисовку.

import m from "mithril";

const App = {
  view() {
    return m("main", [
      m("h1", "Список задач"),
      m("p", "Каркас готов"),
    ]);
  },
};

m.mount(document.getElementById("app"), App);

Сборка командой npx esbuild src/main.js --bundle --minify --outfile=dist/app.js даёт один файл. Для разработки добавьте --watch и --sourcemap, а HTML подключите к dist/app.js. Никакой магии: браузер получает обычный скрипт.

Компоненты и состояние

Состояние хранят в замыкании компонента или в vnode.state. Замыкание удобнее для переиспользуемых компонентов: у каждого экземпляра свои данные. Метод oninit вызывается до первого view и получает атрибуты.

function Counter() {
  let count = 0;
  return {
    oninit(vnode) {
      count = vnode.attrs.start ?? 0;
    },
    view() {
      return m("div", [
        m("span", `Значение: ${count}`),
        m("button", {
          onclick: () => { count++; },
        }, "+1"),
      ]);
    },
  };
}

m.mount(document.getElementById("app"), {
  view: () => m(Counter, { start: 3 }),
});

После обработчика Mithril сам вызывает redraw. Если состояние меняется в асинхронном колбэке вне m.request, вызовите m.redraw() вручную. Не мутируйте массив в цикле без необходимости: diff сравнивает vnode по ключам и типу.

Роутинг

Роутер связывает URL с компонентом. Карта маршрутов задаётся объектом, а параметры приходят через m.route.param. Переход выполняется через m.route.set, а ссылки лучше делать компонентом m.route.Link, чтобы не терять историю и среднюю кнопку мыши.

import m from "mithril";

const Home = { view: () => m("h2", "Главная") };
const Task = {
  view: () => m("h2", `Задача ${m.route.param("id")}`),
};

m.route(document.getElementById("app"), "/", {
  "/": Home,
  "/task/:id": Task,
});

Для ленивой загрузки используйте резолвер с onmatch: он может вернуть промис и загрузить модуль по требованию. Маршрут с ... в конце, например /:path..., ловит остаток пути. Не забудьте про m.route.prefix, если приложение живёт в подкаталоге.

Запросы к API

m.request возвращает промис и автоматически делает redraw после завершения. Параметр background: true отключает перерисовку для фоновых опросов. Ошибки приходят с кодом и телом ответа, поэтому их нужно разбирать, а не только показывать «что-то пошло не так».

const api = {
  list: () => m.request({ method: "GET", url: "/api/tasks" }),
  create: (title) => m.request({
    method: "POST",
    url: "/api/tasks",
    body: { title },
  }),
};

async function load() {
  try {
    const tasks = await api.list();
    console.log(tasks);
  } catch (e) {
    console.error(e.code, e.response);
  }
}

Для отмены сохраните xhr через опцию config и вызовите abort(). Таймаут задаётся полем timeout, а куки — withCredentials. Не храните токен в localStorage без необходимости: HTTP-only cookie безопаснее при правильной настройке CORS.

Списки, ключи и жизненный цикл

Список vnode должен иметь уникальный key. Без него diff сопоставляет элементы по позиции и может переиспользовать DOM неверно, особенно после удаления из середины. Ключ ставят на корневой элемент строки, а не на дочерние.

function TaskList() {
  let items = [{ id: 1, title: "Первая" }];
  return {
    view: () => m("ul", items.map((item) =>
      m("li", { key: item.id }, item.title)
    )),
  };
}

Хуки oncreate и onremove полезны для интеграции с картами, редакторами и сторонними виджетами. onbeforeremove позволяет проиграть анимацию: верните промис, и элемент удалится после его завершения. onbeforeupdate с возвратом false пропускает перерисовку поддерева.

Сборка и тесты

В package.json достаточно двух скриптов: сборка и тесты. ospec запускается в Node и не требует браузера, если тестировать чистые функции и компоненты через вызов view.

{
  "scripts": {
    "build": "esbuild src/main.js --bundle --minify --outfile=dist/app.js",
    "test": "ospec test/*.js"
  }
}

Тест компонента проверяет структуру vnode, а не реальный DOM. Для интеграционных сценариев понадобится jsdom или Playwright. Не смешивайте эти уровни: быстрые тесты на логику и отдельный набор на критичные пользовательские пути.

Подводные камни

Первая ошибка — думать, что Mithril реактивен как Vue. Здесь нет вычисляемых свойств: view читает текущие данные и вызывается заново. Вторая — забыть ключи в списках. Третья — вставлять HTML через m.trust без санитизации: это прямой путь к XSS. Обычный текст Mithril экранирует сам.

Четвёртая — тяжёлые вычисления внутри view: они выполняются на каждую перерисовку. Выносите сортировку и фильтрацию в обработчики или кэшируйте результат. Пятая — глобальное состояние без плана: для небольшого приложения хватит модуля с объектом, но при росте добавьте явные функции доступа.

Итог

Mithril даёт компактный и предсказуемый фундамент для интерфейса: hyperscript, компоненты, роутер и HTTP-клиент в одном пакете. Начните с m.mount и простого view, затем добавьте маршруты и m.request. Следите за ключами списков, не доверяйте m.trust и выносите тяжёлую логику из view. Для админки или внутреннего инструмента этого достаточно, а для большого продукта заранее оцените экосистему и серверный рендеринг.

Насколько публикация полезна?

Нажмите на звезду, чтобы оценить!

Средняя оценка 0 / 5. Количество оценок: 0

Оценок пока нет. Поставьте оценку первым.

Поделиться: Telegram ВКонтакте