Module Federation Plugin — механизм, позволяющий разделять код между независимыми приложениями во время выполнения, а не сборки. Это основа архитектуры микрофронтендов, где отдельные приложения могут динамически загружать модули друг друга, оставаясь автономными по сборке и деплою.
Module Federation решает задачу разделения приложения на независимые части, которые:
Ключевые сущности:
Минимальная настройка выполняется через
webpack.config.js:
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: "app_shell",
remotes: {
app_remote: "app_remote@http://localhost:3001/remoteEntry.js",
},
shared: {},
}),
],
};
Здесь:
name — имя текущего контейнераremotes — подключаемые удалённые приложенияremoteEntry.js — точка входа федерацииRemote-приложение экспортирует модули:
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: "app_remote",
filename: "remoteEntry.js",
exposes: {
"./Button": "./src/components/Button",
"./utils": "./src/utils/index",
},
shared: {},
}),
],
};
name — имя remote-контейнераfilename — файл манифеста федерацииexposes — публичные модулиВ host-приложении импорт выглядит как динамический:
import("app_remote/Button").then((module) => {
const Button = module.default;
});
Или в React:
const RemoteButton = React.lazy(() => import("app_remote/Button"));
Shared позволяет избежать дублирования библиотек:
shared: {
react: {
singleton: true,
requiredVersion: "^18.0.0",
},
"react-dom": {
singleton: true,
},
}
Если react не помечен как singleton,
возможны:
Правильная настройка:
shared: {
react: {
singleton: true,
strictVersion: true,
},
}
Module Federation поддерживает согласование версий:
shared: {
lodash: {
requiredVersion: "^4.17.0",
},
}
Если версии несовместимы, Webpack:
Remote можно определять во время выполнения:
__webpack_init_sharing__("default");
const container = window.app_remote;
await container.init(__webpack_share_scopes__.default);
const factory = await container.get("./Button");
const Module = factory();
Это позволяет:
При сборке создаются:
remoteEntry.js — манифест модулейПроцесс загрузки:
remoteEntry.jsgetКаждый remote работает в своём scope:
Важно понимать:
Module Federation работает с ленивой загрузкой:
Оптимизация:
remotes: {
app_remote: "app_remote@https://cdn.example.com/remoteEntry.js",
}
Использование CDN снижает latency.
Типичная проблема — недоступный remote:
const loadRemote = async () => {
try {
return await import("app_remote/Button");
} catch (e) {
return import("./FallbackButton");
}
};
Для типизации используются декларации:
declare module "app_remote/Button" {
const Button: React.ComponentType;
export default Button;
}
Можно также генерировать типы через federation plugins.
output: {
publicPath: "auto",
}
Без этого возможны ошибки загрузки chunk-файлов.
Риски:
Меры:
Можно управлять remote через переменные:
const remoteUrl = process.env.REMOTE_URL;
remotes: {
app_remote: `app_remote@${remoteUrl}/remoteEntry.js`,
}
Рекомендуемые настройки:
new ModuleFederationPlugin({
name: "shell",
remotes: {
remote_app: "remote_app@https://cdn.site.com/remoteEntry.js",
},
shared: {
react: { singleton: true },
"react-dom": { singleton: true },
},
});
При использовании federation важно:
remoteEntry.jsHost всегда получает:
Однако: