← назад к разделу

Интернет-магазин делают пять команд: каталог, поиск, корзина, оформление заказа, личный кабинет. Код лежит в одном репозитории и выкатывается одной сборкой. Команда корзины исправила опечатку, но выехать не может: у поиска упали тесты. Релиз раз в неделю, и каждая команда ждёт остальных.

Микрофронтенды решают именно эту проблему: страницу собирают из частей, которые каждая команда разрабатывает и выкатывает сама. Ниже — какими способами это делают, как устроен самый популярный из них, Module Federation, и почему это решение для организации, а не для кода.

хост: оболочка, роутинг, общий React каталогcatalog.shop.ru корзинаcart.shop.ru поискsearch.shop.ru команда А, релиз во вторник команда Б, релиз сегодня команда В, релиз в пятницу части приходят в браузер на лету, каждая со своего адреса

Оболочка знает только адреса частей; что внутри каждой и когда она обновилась, решает её команда.

Зачем делить фронтендспросят на собеседовании

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

Цена тоже большая. Появляются несколько сборок, версии общих библиотек, договорённости о стилях и событиях, загрузка частей по сети. Если над приложением работает одна команда в десять человек, всё это — расходы без выгоды: ей хватит модульного монолита с чёткими границами, как в Feature-Sliced Design. Микрофронтенды окупаются, когда команд несколько и они мешают друг другу выкатываться.

Как собрать страницу из частейспросят на собеседовании

Части можно соединить на разных этапах, и от этого зависит, насколько они независимы.

Сборка из npm-пакетов — самый простой вариант, но независимости он не даёт: чтобы обновить корзину, хост всё равно пересобирают и выкатывают. Это разделение кода, а не релизов.

Сборка на сервере: сервер или CDN склеивает HTML из фрагментов, которые отдают разные сервисы. Хорошо для страниц, где важна скорость первой отрисовки, но интерактивность каждой части приходится оживлять отдельно.

Сборка в браузере: оболочка на лету загружает код частей. Старейший способ — iframe: полная изоляция, но трудно сделать общую навигацию, размеры и единый вид. Чуть современнее — Web Components: часть регистрирует свой тег, например <cart-widget>, и хост просто вставляет его в разметку. А самый распространённый сегодня в React-проектах — Module Federation.

Module Federation: модули, загружаемые на летуспросят на собеседовании

Module Federation появился в webpack 5, сейчас есть и в Rspack, и плагином для Vite. Идея: сборка одного приложения может отдать наружу часть своих модулей, а другое приложение импортирует их во время работы, как обычный import, хотя код лежит на другом сервере.

Корзина объявляет, что отдаёт, и собирает файл-оглавление remoteEntry.js:

new ModuleFederationPlugin({
  name: "cart",
  filename: "remoteEntry.js",
  exposes: { "./CartWidget": "./src/CartWidget" },
  shared: { react: { singleton: true, requiredVersion: "^18.2.0" }, "react-dom": { singleton: true } },
});

Хост знает только адрес оглавления и загружает виджет лениво:

new ModuleFederationPlugin({
  name: "shell",
  remotes: { cart: "cart@https://cart.shop.ru/remoteEntry.js" },
  shared: { react: { singleton: true }, "react-dom": { singleton: true } },
});

const CartWidget = lazy(() => import("cart/CartWidget"));

Когда команда корзины выкатывает новую версию, хост ничего не пересобирает: при следующей загрузке страницы он скачает новый remoteEntry.js. Отсюда же главная грабля: сервер корзины может не ответить. Удалённый модуль всегда оборачивают в Suspense и предохранитель ошибок, чтобы падение корзины не уронило страницу.

Общие зависимости: одна копия Reactспросят на собеседовании

Если каждая часть принесёт свой React, страница будет весить вдвое больше, а хуки сломаются: React из одной копии не узнает компонент из другой и упадёт с ошибкой «Invalid hook call». Поэтому общие библиотеки объявляют в shared, и загрузчик решает, какую копию использовать.

С singleton: true копия всегда одна — та, что у хоста. Если её версия не подходит удалённому модулю, загрузчик только предупредит в консоли, а с strictVersion: true упадёт. Без singleton неподходящая версия приведёт вторую копию. Логика выбора в упрощённом виде:

живой пример

const hostShared = { react: "18.3.1" };

function satisfiesCaret(version, range) {
  const [major, minor, patch] = version.split(".").map(Number);
  const [rMajor, rMinor, rPatch] = range.slice(1).split(".").map(Number);
  if (major !== rMajor) return false;
  return minor > rMinor || (minor === rMinor && patch >= rPatch);
}

function resolve(remote, { range, own, singleton }) {
  const host = hostShared.react;
  if (satisfiesCaret(host, range)) return `${remote}: react ${host} от хоста`;
  if (singleton) return `${remote}: react ${host} от хоста, в консоли предупреждение — ждали ${range}`;
  return `${remote}: свой react ${own} — на странице две копии React`;
}

console.log(resolve("cart", { range: "^18.2.0", own: "18.2.0", singleton: true }));
console.log(resolve("search", { range: "^19.0.0", own: "19.1.0", singleton: true }));
console.log(resolve("search", { range: "^19.0.0", own: "19.1.0", singleton: false }));
Запустить

Запуск примеров доступен в платном доступе. Там этот же код выполняется прямо в статье: редактор, запуск и проверка рядом с абзацем. Три дня бесплатно →

Отсюда правило: мажорные версии React, роутера и дизайн-системы обновляют согласованно, всеми командами сразу. Это единственное место, где микрофронтенды не независимы.

Как частям общатьсяспросят на собеседовании

Корзина должна сообщить шапке, что товаров стало три. Общий Redux-стор на все части связывает команды так же крепко, как монолит: изменил форму состояния — сломал чужой код. Поэтому части общаются через узкие и явные каналы: адрес страницы, атрибуты Web Component и события в браузере.

Так не надо: импортировать стор корзины в шапку и читать его поля напрямую.

Так надо: договориться об имени события и форме данных, это и есть контракт между командами:

живой пример

const bus = new EventTarget();

bus.addEventListener("cart:changed", (event) => {
  console.log("шапка: в корзине", event.detail.count);
});

function addToCart(count) {
  bus.dispatchEvent(new CustomEvent("cart:changed", { detail: { count } }));
}

addToCart(1);
addToCart(2);
Запустить

Запуск примеров доступен в платном доступе. Там этот же код выполняется прямо в статье: редактор, запуск и проверка рядом с абзацем. Три дня бесплатно →

В браузере вместо своего EventTarget обычно берут window. Стили изолируют префиксами классов, CSS-модулями или Shadow DOM у Web Components, иначе правило одной команды перекрасит кнопки другой.

Коротко

  • Микрофронтенды — это независимые релизы частей интерфейса разными командами; для одной команды они дороже модульного монолита.
  • Способы соединения: npm-пакеты (без независимых релизов), склейка на сервере, iframe, Web Components, Module Federation.
  • Module Federation: удалённое приложение отдаёт модули через exposes и remoteEntry.js, хост подключает их через remotes и import на лету.
  • Удалённый модуль — всегда в lazy, Suspense и предохранителе ошибок: его сервер может не ответить.
  • React и другие библиотеки с состоянием — в shared с singleton: true; две копии React ломают хуки.
  • Части общаются через URL, атрибуты и события с оговорённым именем и формой, а не через общий стор.

Что почитать дальше