Архитектура Module Federation в Webpack основана на разделении приложения на независимые сборки, способные загружать код друг друга во время выполнения. В этой модели термин remote обозначает приложение или сборку, которая экспортирует модули для использования другими приложениями (host).
Remote-сборка публикует специальный файл remoteEntry.js,
содержащий метаданные, реестр модулей и логику загрузки. Host-приложение
подключает этот файл и получает доступ к экспортируемым сущностям так,
будто они являются частью локального кода.
Remote-приложение конфигурируется через
ModuleFederationPlugin, где определяется имя контейнера и
список экспортируемых модулей.
new ModuleFederationPlugin({
name: 'app1',
filename: 'remoteEntry.js',
exposes: {
'./Button': './src/components/Button',
'./utils': './src/utils/index'
},
shared: {
react: { singleton: true },
'react-dom': { singleton: true }
}
});
name
filename
remoteEntry.jsexposes
shared
Host-приложение объявляет удалённые источники через секцию
remotes.
new ModuleFederationPlugin({
name: 'host',
remotes: {
app1: 'app1@http://localhost:3001/remoteEntry.js'
},
shared: {
react: { singleton: true },
'react-dom': { singleton: true }
}
});
<name>@<url>
name — идентификатор remote-контейнераurl — путь до remoteEntry.jsПри обращении к remote-модулю Webpack выполняет несколько шагов:
remoteEntry.js через
<script> или dynamic importПример импорта:
import('app1/Button').then((module) => {
const Button = module.default;
});
Статическая конфигурация подходит не всегда. В реальных системах часто требуется динамическая подгрузка remote URL.
remotes: {
app1: `app1@${process.env.APP1_URL}/remoteEntry.js`
}
Более гибкий вариант — использование promise-based remotes:
remotes: {
app1: `promise new Promise((resolve) => {
const url = window.RUNTIME_CONFIG.app1Url;
const script = document.createElement('script');
script.src = url;
script.onl oad = () => {
resolve(window.app1);
};
document.head.appendChild(script);
})`
}
После загрузки remoteEntry.js контейнер должен быть
инициализирован:
await __webpack_init_sharing__('default');
await container.init(__webpack_share_scopes__.default);
Где:
__webpack_init_sharing__ — инициализация shared
scope__webpack_share_scopes__ — глобальный реестр
зависимостейRemote и host могут использовать одинаковые библиотеки. Чтобы избежать конфликтов:
shared: {
react: {
singleton: true,
requiredVersion: '^18.0.0'
}
}
singleton: true — только один экземпляр библиотекиrequiredVersion — контроль совместимости версийeager: true — немедленная загрузкаНеправильная настройка shared приводит к:
Remote-система не требует строгой версии API, но поддерживает стратегию:
app1@https://cdn.example.com/app1/v1/remoteEntry.js
app1@https://cdn.example.com/app1/v2/remoteEntry.js
exposes: {
'./ButtonV1': './v1/Button',
'./ButtonV2': './v2/Button'
}
Remote может быть недоступен, и это требует обработки:
import('app1/Button')
.then((m) => m.default)
.catch(() => {
return FallbackButton;
});
Так как remoteEntry загружается как внешний скрипт, сервер обязан отдавать корректные заголовки:
Access-Control-Allow-Origin: *
Без этого браузер блокирует загрузку контейнера.
RemoteEntry часто кэшируется агрессивно через CDN:
Cache-Control: max-age=31536000, immutable
Проблема возникает при обновлении API, поэтому используют:
remoteEntry.[hash].js)Remote подключается только при необходимости:
const loadWidget = async () => {
const module = await import('app1/Widget');
return module.default;
};
Это снижает:
TypeScript не знает структуру remote, поэтому требуется декларация:
declare module 'app1/Button' {
const Button: React.ComponentType<any>;
export default Button;
}
Для крупных систем создаются отдельные d.ts пакеты,
синхронизируемые с exposes.
Remote становится частью распределённой системы, где важен контракт:
Нарушение контракта приводит к runtime-ошибкам, которые не выявляются на этапе сборки.
Remote-сборка обычно разворачивается отдельно:
Host не пересобирается при изменении remote при условии стабильного интерфейса.
Host может подключать несколько remotes:
remotes: {
app1: 'app1@https://cdn/a/remoteEntry.js',
app2: 'app2@https://cdn/b/remoteEntry.js',
uiKit: 'uiKit@https://cdn/ui/remoteEntry.js'
}
Это позволяет строить композицию микрофронтендов, где каждая часть системы изолирована и независимо обновляется.