Webpack 5引入的Module Federation(模块联邦)提供了一种原生级别的微前端实现方案。不同独立构建的应用可以在运行时共享和加载彼此的模块,无需借助iframe或npm包发布流程。宿主应用(Host)可以动态加载远程应用(Remote)暴露的组件,多个应用间还能共享React、Vue等运行时依赖避免重复加载。本文从架构设计到代码实现,完整演示Module Federation的搭建过程。
Module Federation核心概念与架构设计
Module Federation涉及三个核心概念:Host(消费方)是运行时加载远程模块的应用;Remote(提供方)是暴露模块供其他应用消费的应用,一个应用可以同时是Host和Remote;Shared(共享依赖)是多个应用间公共的第三方库,如react、react-dom,Shared模块根据版本协商自动选择加载方。典型架构场景是一个主门户应用作为Host,多个业务子应用作为Remote,各应用独立开发、独立部署、独立运行。
Remote应用配置与模块暴露
// remote-app/webpack.config.js
const { ModuleFederationPlugin } = require("webpack").container;
const deps = require("./package.json").dependencies;
module.exports = {
mode: "development",
entry: "./src/index.js",
devServer: {
port: 3001,
headers: { "Access-Control-Allow-Origin": "*" },
},
output: { publicPath: "//localhost:3001/" },
module: {
rules: [{
test: /\.jsx?$/,
exclude: /node_modules/,
use: { loader: "babel-loader", options: { presets: ["@babel/preset-react"] } },
}],
},
plugins: [
new ModuleFederationPlugin({
name: "remoteApp",
filename: "remoteEntry.js",
exposes: {
"./Widget": "./src/components/Widget",
"./Dashboard": "./src/pages/Dashboard",
},
shared: {
react: { singleton: true, requiredVersion: deps.react },
"react-dom": { singleton: true, requiredVersion: deps["react-dom"] },
},
}),
],
};
filename指定远程入口文件名,宿主通过加载这个JS文件获取Remote暴露的模块清单。exposes的key是远程模块的访问路径,value是本地源码路径。shared中singleton:true确保React只有一个运行实例,避免多实例导致的Hooks报错。Remote应用需要异步边界入口文件确保共享依赖在模块加载前完成初始化:
// remote-app/src/index.js
import("./bootstrap");
// remote-app/src/bootstrap.js
import React from "react";
import { createRoot } from "react-dom/client";
import App from "./App";
createRoot(document.getElementById("root")).render(<App />);
Host应用配置与远程模块加载
// host-app/webpack.config.js
new ModuleFederationPlugin({
name: "hostApp",
remotes: {
remoteApp: "remoteApp@//localhost:3001/remoteEntry.js",
},
shared: {
react: { singleton: true, requiredVersion: deps.react },
"react-dom": { singleton: true, requiredVersion: deps["react-dom"] },
},
})
remotes配置中的key与Remote的name对应,value包含Remote名称和remoteEntry.js的URL。运行时Webpack通过动态script标签加载remoteEntry.js,然后通过全局变量remoteApp获取模块工厂函数。使用React.lazy和Suspense加载远程组件:
// host-app/src/bootstrap.js
import React, { Suspense } from "react";
import { createRoot } from "react-dom/client";
const RemoteWidget = React.lazy(() => import("remoteApp/Widget"));
const RemoteDashboard = React.lazy(() => import("remoteApp/Dashboard"));
function App() {
return (
<div>
<h1>Host Application</h1>
<Suspense fallback={<div>Loading Widget...</div>}>
<RemoteWidget />
</Suspense>
<Suspense fallback={<div>Loading Dashboard...</div>}>
<RemoteDashboard />
</Suspense>
</div>
);
}
createRoot(document.getElementById("root")).render(<App />);
import(“remoteApp/Widget”)会被Webpack解析为对远程模块的加载请求。首次加载时浏览器先请求remoteEntry.js获取模块映射表,再按需加载对应chunk文件,后续加载相同模块直接命中缓存。
动态远程加载与运行时配置URL
固定URL的远程配置在编译时确定,生产环境常需根据环境动态指定Remote地址。使用Promise形式的动态远程加载:
new ModuleFederationPlugin({
name: "hostApp",
remotes: {
remoteApp: `promise new Promise((resolve, reject) => {
const script = document.createElement("script");
script.src = window.__REMOTE_URLS__.remoteApp;
script.onload = () => {
const container = window.remoteApp;
container.init(__webpack_share_scopes__.default);
resolve(container);
};
script.onerror = reject;
document.head.appendChild(script);
})`,
},
shared: {
react: { singleton: true },
"react-dom": { singleton: true },
},
})
配合全局变量或配置中心,运行时读取各Remote的CDN地址实现按环境灵活切换。
共享依赖版本协商与降级策略
shared: {
react: {
singleton: true,
requiredVersion: "18.2.0",
eager: false,
import: false,
},
antd: {
singleton: false,
requiredVersion: "^5.0.0",
},
}
版本协商规则:Host和Remote都声明了shared依赖时,运行时比较版本号,选择满足双方requiredVersion的版本加载。若版本不兼容且singleton为true,使用先加载的版本并发出控制台警告。singleton为false时各自加载自己的版本,适用于UI组件库等允许多版本并存的场景。eager:true会将共享依赖打包进初始bundle而非异步chunk,适合Host自身也直接使用的依赖但会增加初始加载体积。
生产环境部署与CDN静态资源路径处理
生产环境部署在CDN时output.publicPath必须正确指向CDN地址:
// src/index.js - 动态设置publicPath
__webpack_public_path__ = window.__PUBLIC_PATH__ || "//cdn.example.com/remote-app/";
import("./bootstrap");
# Nginx CORS配置
location / {
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods "GET, OPTIONS";
try_files $uri $uri/ /index.html;
}
远程加载失败通过ErrorBoundary捕获,提供降级UI防止白屏:
class RemoteErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError() {
return { hasError: true };
}
componentDidCatch(error) {
console.error("Remote module load failed:", error);
}
render() {
if (this.state.hasError) {
return <div>模块加载失败,请刷新重试</div>;
}
return this.props.children;
}
}
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/webpackmodulefederation-wei-qian-duan-jia-gou-da-jian-yu/