Антон Ларичев

Введение
Микрофронтенды — это архитектурный подход, при котором фронтенд большого приложения разбивается на независимые модули, разрабатываемые и деплоящиеся автономно разными командами. Идея заимствована из микросервисов: вместо одного монолитного SPA получается набор кусочков, которые собираются вместе в браузере или на этапе сборки.
Module Federation — это механизм Webpack 5, позволяющий одному приложению (host) подгружать модули из другого (remote) во время выполнения. Это ключевая технология, сделавшая микрофронтенды действительно жизнеспособными: команды могут шарить React, библиотеки и компоненты без дублирования и без полного передеплоя.
В этой статье разберём, в каких случаях микрофронтенды оправданы, как настроить Module Federation, как организовать общие зависимости и каких ошибок избегать.
Когда микрофронтенды действительно нужны
Микрофронтенды решают организационные проблемы, а не технические. Применять их стоит, если:
- В продукте работает несколько независимых команд, и они блокируют друг друга в монорепо
- Разные части приложения имеют разные релизные циклы
- Стек технологий должен отличаться между секциями (например, легаси AngularJS и новый React)
- Размер фронтенда вырос настолько, что CI билдит его десятками минут
Если у вас одна команда из 5 человек и SPA на 200 компонентов — микрофронтенды только усложнят жизнь. Накладные расходы на инфраструктуру, версионирование и отладку перевесят выгоду.
Базовая настройка Module Federation
Рассмотрим минимальный пример: host-приложение shell подгружает компонент Header из remote-приложения header-app.
Конфиг remote-приложения:
// header-app/webpack.config.js
const { ModuleFederationPlugin } = require('webpack').container;
module.exports = {
entry: './src/index.js',
mode: 'production',
output: {
// Публичный путь должен совпадать с URL, по которому раздаётся remoteEntry.js
publicPath: 'https://cdn.example.com/header/',
},
plugins: [
new ModuleFederationPlugin({
// Имя remote, по нему host обращается к модулям
name: 'headerApp',
filename: 'remoteEntry.js',
exposes: {
// Что отдаём наружу: ключ — публичное имя, значение — путь в исходниках
'./Header': './src/Header',
},
shared: {
react: { singleton: true, requiredVersion: '^18.2.0' },
'react-dom': { singleton: true, requiredVersion: '^18.2.0' },
},
}),
],
};
Конфиг host-приложения:
// shell/webpack.config.js
const { ModuleFederationPlugin } = require('webpack').container;
module.exports = {
entry: './src/index.js',
mode: 'production',
plugins: [
new ModuleFederationPlugin({
name: 'shell',
// Карта remote-приложений: ключ — локальное имя, значение — имя@URL
remotes: {
headerApp: 'headerApp@https://cdn.example.com/header/remoteEntry.js',
},
shared: {
react: { singleton: true, requiredVersion: '^18.2.0' },
'react-dom': { singleton: true, requiredVersion: '^18.2.0' },
},
}),
],
};
Теперь в host можно использовать remote-компонент через динамический импорт:
// shell/src/App.jsx
import React, { Suspense, lazy } from 'react';
// Lazy-импорт нужен, потому что remoteEntry.js загружается асинхронно
const Header = lazy(() => import('headerApp/Header'));
export function App() {
return (
<div>
<Suspense fallback={<div>Загружаем хедер...</div>}>
<Header />
</Suspense>
<main>Контент основного приложения</main>
</div>
);
}
Общие зависимости и версионирование
Ключ к стабильным микрофронтендам — правильная настройка shared. Опция singleton: true гарантирует, что во всём приложении будет ровно один экземпляр React. Без этого получится два инстанса, и хуки перестанут работать с ошибкой про несоответствие версии.
shared: {
react: {
singleton: true,
// Если версии не совпадают, упасть с понятной ошибкой
strictVersion: true,
requiredVersion: '^18.2.0',
},
// Eager-загрузка нужна для зависимостей, которые требуются синхронно на старте
'@my-org/design-system': {
singleton: true,
eager: false,
},
}
Динамические remote-ы
Жёстко зашитый URL в конфиге — это анти-паттерн для прода. Лучше загружать список remote-ов из манифеста:
// Утилита для загрузки remote во время выполнения
async function loadRemote(scope, module, url) {
// Подгружаем remoteEntry.js на лету
await __webpack_init_sharing__('default');
const container = window[scope] || (await import(/* webpackIgnore: true */ url));
await container.init(__webpack_share_scopes__.default);
const factory = await container.get(module);
return factory();
}
Это позволяет менять URL-ы remote-приложений без передеплоя host-приложения.
Частые ошибки
- Дублирование React. Забыли
singleton: true— получаете загадочные ошибки в хуках. Всегда проверяйте через DevTools, что React в bundle один. - Несинхронизированные релизы. Если host рассчитывает на API remote-компонента, а тот выпустил breaking change — приложение ломается в проде. Введите контракты и semver между командами.
- Микрофронтенды ради микрофронтендов. Не дробите приложение, если нет реальной организационной причины. Сложность отладки и CI вырастает кратно.
- CORS на remoteEntry.js. Remote должен раздаваться с правильными CORS-заголовками, иначе host не сможет его загрузить.
- Жёстко зашитые URL-ы remote-ов. Делает невозможным деплой в разные окружения. Используйте манифест или env-переменные.
- Игнорирование изоляции стилей. Глобальные CSS из разных микрофронтендов конфликтуют. Используйте CSS Modules, scoped-стили или Shadow DOM.
Заключение
Модули Module Federation — мощный инструмент, но он требует зрелой инженерной культуры: контрактов между командами, мониторинга, версионирования shared-зависимостей. Если у вас несколько команд с разными релизными циклами, эта архитектура развяжет руки. В остальных случаях монолитный SPA с code splitting обычно эффективнее. Начинайте с простого: настройте один host и один remote, отладьте shared-зависимости, и только потом расширяйте на всё приложение.






Комментарии
0