12. Module Federation
Module Federation — революционная возможность Webpack 5, которая позволяет нескольким независимым приложениям (микрофронтендам) динамически загружать код друг от друга во время выполнения. Это фундаментально меняет архитектуру крупных frontend-приложений.
Проблема, которую решает Module Federation
Заголовок раздела «Проблема, которую решает Module Federation»Традиционный монолит vs Микрофронтенды:
Монолит: Микрофронтенды с MF:┌─────────────────────────┐ ┌─────────┐ ┌──────────┐ ┌──────────┐│ Огромный монорепо │ │ Shell │ │ Header │ │ Checkout ││ ┌─────┐┌──────┐┌──────┐│ │ App │ │ MFE │ │ MFE ││ │Team1││Team2 ││Team3 ││ │ │ │ │ │ ││ └─────┘└──────┘└──────┘│ │ host │ │ remote │ │ remote │└─────────────────────────┘ └────┬────┘ └────┬─────┘ └────┬─────┘ Один деплой для всех └─────────────┴─────────────┘ Независимые деплои!Ключевые концепции
Заголовок раздела «Ключевые концепции»- Host (Shell) — главное приложение, загружает ремоуты
- Remote — приложение, экспортирующее компоненты/модули
- Shared — общие зависимости (React, Redux) — загружаются один раз
Конфигурация Remote (дочернее приложение)
Заголовок раздела «Конфигурация Remote (дочернее приложение)»// webpack.config.js в remote приложении (header-app)const { ModuleFederationPlugin } = require('webpack').container;
module.exports = { output: { publicPath: 'http://localhost:3001/', // Важно! Абсолютный URL }, plugins: [ new ModuleFederationPlugin({ name: 'headerApp', // Уникальное имя remote filename: 'remoteEntry.js', // Точка входа для host
// Что экспортируем exposes: { './Header': './src/components/Header', './Navigation': './src/components/Navigation', './UserMenu': './src/components/UserMenu', },
// Общие зависимости shared: { react: { singleton: true, requiredVersion: '^18.0.0' }, 'react-dom': { singleton: true, requiredVersion: '^18.0.0' }, }, }), ],};Конфигурация Host (главное приложение)
Заголовок раздела «Конфигурация Host (главное приложение)»// webpack.config.js в host приложении (shell)const { ModuleFederationPlugin } = require('webpack').container;
module.exports = { plugins: [ new ModuleFederationPlugin({ name: 'shell',
// Откуда загружать ремоуты remotes: { headerApp: 'headerApp@http://localhost:3001/remoteEntry.js', checkoutApp: 'checkoutApp@http://localhost:3002/remoteEntry.js', productApp: 'productApp@http://localhost:3003/remoteEntry.js', },
shared: { react: { singleton: true, requiredVersion: '^18.0.0' }, 'react-dom': { singleton: true, requiredVersion: '^18.0.0' }, }, }), ],};Использование в коде Host
Заголовок раздела «Использование в коде Host»import { lazy, Suspense } from 'react';
// Динамический импорт из remoteconst Header = lazy(() => import('headerApp/Header'));const Checkout = lazy(() => import('checkoutApp/Checkout'));
function App() { return ( <div> <Suspense fallback={<div>Loading header...</div>}> <Header /> </Suspense>
<main> <Suspense fallback={<div>Loading checkout...</div>}> <Checkout /> </Suspense> </main> </div> );}TypeScript поддержка
Заголовок раздела «TypeScript поддержка»npm install --save-dev @module-federation/typescript// В конфиге webpack remote:plugins: [ new FederatedTypesPlugin({ disableTypeCompilation: false, }),]Динамические ремоуты
Заголовок раздела «Динамические ремоуты»Для runtime конфигурации (URL из API):
// Динамическая загрузка remoteasync function loadRemoteModule(remoteUrl, scope, module) { await __webpack_init_sharing__('default'); const container = window[scope]; await container.init(__webpack_share_scopes__.default); const factory = await window[scope].get(module); return factory();}
// Использование:const { Header } = await loadRemoteModule( 'http://dynamic-host.com/remoteEntry.js', 'dynamicApp', './Header');Ниже — интерактивная визуализация архитектуры Module Federation с host и remote приложениями.