В архитектуре Module Federation каждое приложение получает одну из двух ключевых ролей: Host или Remote. Эти роли определяют, кто потребляет код, а кто его предоставляет, но в реальных системах граница между ними часто размывается, поскольку одно приложение может одновременно выступать в обеих ролях.
Host-приложение — это контейнер, который загружает и использует удалённые модули из других сборок. Remote-приложение — это сборка, которая экспортирует свои модули для использования внешними приложениями.
Ключевая идея заключается в том, что зависимости между приложениями становятся динамическими и исполняемыми в рантайме, а не фиксируются на этапе сборки.
Remote-приложение в Module Federation — это поставщик функциональности. Оно определяет набор модулей, которые могут быть импортированы извне.
В конфигурации Webpack Remote описывается через
ModuleFederationPlugin, где указывается имя приложения и
список экспонируемых модулей:
new ModuleFederationPlugin({
name: 'auth_app',
filename: 'remoteEntry.js',
exposes: {
'./LoginForm': './src/LoginForm',
'./AuthService': './src/AuthService'
},
shared: {
react: { singleton: true },
'react-dom': { singleton: true }
}
});
1. Экспорт модулей через exposes Каждый модуль явно объявляется как доступный извне. Это предотвращает случайное раскрытие внутренней структуры приложения.
2. Генерация remoteEntry файла Файл
remoteEntry.js является точкой входа, через которую Host
получает доступ к модулям Remote-приложения.
3. Изоляция внутренней структуры Remote контролирует, какие части системы доступны. Внутренние зависимости и архитектура остаются скрытыми.
4. Управление зависимостями через shared Remote может объявлять общие зависимости, чтобы избежать дублирования библиотек.
Host-приложение является потребителем функциональности. Оно не знает заранее, где физически находится код, но знает контракт доступа к нему.
Пример конфигурации Host:
new ModuleFederationPlugin({
name: 'shell_app',
remotes: {
auth_app: 'auth_app@http://localhost:3001/remoteEntry.js'
},
shared: {
react: { singleton: true },
'react-dom': { singleton: true }
}
});
1. Динамическая загрузка модулей Host получает код
Remote во время выполнения через загрузку
remoteEntry.js.
2. Абстракция источника кода Host работает с модулями как с локальными, хотя они физически находятся в другой сборке.
3. Управление несколькими Remote Одно Host-приложение может подключать множество Remote одновременно, формируя распределённую систему.
const LoginForm = React.lazy(() => import('auth_app/LoginForm'));
4. Ленивое подключение функциональности Модули могут загружаться только при необходимости, снижая стартовый вес приложения.
Связь между Host и Remote строится через три слоя:
При старте Host загружает remoteEntry.js, который
регистрирует доступные модули в глобальном контейнере федерации.
Каждое приложение в федерации имеет контейнер, который хранит фабрики модулей. Host обращается к этим фабрикам для получения кода.
Если Host и Remote используют одну и ту же библиотеку (например
React), Webpack пытается синхронизировать версии через механизм
shared.
Хотя роли Host и Remote логически разделены, поведение сильно зависит от конфигурации shared-зависимостей.
shared: {
react: {
singleton: true,
requiredVersion: '^18.0.0'
}
}
Singleton-режим гарантирует, что в рантайме будет использован только один экземпляр библиотеки. Это особенно важно для React, где наличие двух копий приводит к ошибкам контекста.
Приложение может одновременно:
Пример гибридной конфигурации:
new ModuleFederationPlugin({
name: 'profile_app',
filename: 'remoteEntry.js',
remotes: {
auth_app: 'auth_app@http://localhost:3001/remoteEntry.js'
},
exposes: {
'./ProfilePage': './src/ProfilePage'
},
shared: {
react: { singleton: true },
'react-dom': { singleton: true }
}
});
Такой подход позволяет строить многоуровневые системы, где один Remote становится зависимостью другого Host.
В сложных системах формируются цепочки:
Каждый уровень может:
Это создаёт иерархию, в которой приложение становится узлом распределённой графовой структуры.
Одним из ключевых преимуществ архитектуры является возможность менять Remote без пересборки Host.
remotes: {
auth_app: 'auth_app@https://cdn.example.com/remoteEntry.js'
}
Host не зависит от конкретного окружения. Достаточно изменить URL, чтобы переключить реализацию.
Если Host и Remote используют разные версии библиотек без корректного shared-конфига, возникает дублирование и конфликты состояния.
Если remoteEntry.js недоступен, Host теряет возможность
загрузки модулей. В таких случаях требуется fallback-логика.
Если Remote изменяет экспорт без обратной совместимости, Host может получить runtime-ошибки.
Несмотря на динамичность, взаимодействие Host и Remote требует строгого контрактного подхода.
Контракт включает:
Нарушение контракта приводит к runtime-деградации, которая не выявляется на этапе сборки Host.
Разделение на Host и Remote позволяет:
При этом архитектурная сложность переносится с уровня сборки на уровень исполнения, где управление становится более гибким, но требует дисциплины в проектировании контрактов и зависимостей.