当单页应用膨胀到几十万行、十几个团队共用同一个仓库时,微前端(Micro-Frontend)就成了拆分解耦的自然选择。本文聚焦目前最主流、也是 webpack 5 / Vite 原生支持最好的方案——Module Federation(模块联邦),用可运行的代码带你从零搭出一个能独立开发、独立部署、运行时集成的微前端系统,并彻底讲清依赖共享、样式隔离、路由通信三大难点。
一、为什么需要微前端
传统 SPA 在业务早期很高效,但随着团队扩张会出现几个典型痛点:一是构建越来越慢,一次改动要等全量编译,本地热更新也要几十秒;二是技术栈被锁死,想升级 React 大版本或试点 Vue 都要全员同步,谁都不敢轻易动;三是发布互相牵连,一个模块出问题就要整体回滚,迭代节奏被拖垮;四是新人上手成本高,要在本地拉起整个仓库、装齐全部依赖才能改一个小页面。微前端的核心思想是把”巨石应用”按业务边界拆成多个独立仓库、独立构建、独立部署的子应用,运行时再组合成一个完整页面,让每个团队拥有自己的技术选型与发布节奏。它与前端工程化与 Monorepo 是互补关系:Monorepo 解决”单仓多包”的开发体验,微前端解决”多团队多产物”的运行隔离,两者并不互斥,很多团队会先 Monorepo 收敛依赖,再按需切到微前端。
二、主流方案对比
实现微前端不止一种思路,选型时要权衡隔离强度与接入成本:
| 方案 | 隔离性 | 接入成本 | 适用场景 |
|---|---|---|---|
| iframe | 最强(独立上下文) | 低 | 历史系统嵌入、强隔离需求 |
| qiankun / single-spa | 中(JS 沙箱) | 中 | 多框架共存、统一基座 |
| Web Components | 中(Shadow DOM) | 中高 | 跨框架组件复用 |
| Module Federation | 弱(同运行时) | 低(构建期) | 同技术栈、追求构建复用 |
Module Federation 的卖点是在构建期就把”远程模块”当本地依赖引用,不需要 iframe 的通信墙,也不需要基座运行时加载器。代价是它假设各子应用共享同一套前端运行时(如都跑在webpack/Vite 体系里)。
三、Module Federation 原理速览
核心只有两个角色:Host(宿主/容器)负责拼装页面,Remote(远程)暴露可被他人引用的模块。双方通过一份”远程入口”(remoteEntry.js)互相发现:Host 在运行时按需拉取 Remote 导出的组件,浏览器只加载一次共享依赖。理解这一点后,剩下都是配置问题。
四、实战:用 Webpack 搭建第一个微前端
4.1 远程应用(Remote)暴露组件
在子应用的 webpack 配置里声明要暴露的模块:
// webpack.config.js (remote-app)
const { ModuleFederationPlugin } = require("webpack").container;
module.exports = {
entry: "./src/index.js",
plugins: [
new ModuleFederationPlugin({
name: "remoteApp",
filename: "remoteEntry.js",
exposes: {
"./Cart": "./src/components/Cart.jsx",
},
shared: ["react", "react-dom"],
}),
],
};
4.2 宿主应用(Host)消费远程组件
宿主声明远程地址,然后像导入本地包一样使用:
// webpack.config.js (host-app)
new ModuleFederationPlugin({
name: "hostApp",
remotes: {
remoteApp: "remoteApp@http://localhost:3001/remoteEntry.js",
},
shared: ["react", "react-dom"],
});
// 使用:React.lazy 动态加载远程组件
const Cart = React.lazy(() => import("remoteApp/Cart"));
function App() {
return <Suspense fallback={<div>加载购物车…</div>}><Cart /></Suspense>;
}
五、实战:用 Vite 搭建 Module Federation
Vite 生态使用 @module-federation/vite 插件,配置思路一致但更贴近 ESM:
// vite.config.ts (remote)
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import federation from "@module-federation/vite";
export default defineConfig({
plugins: [
react(),
federation({
name: "remoteApp",
filename: "remoteEntry.js",
exposes: { "./Cart": "./src/components/Cart.tsx" },
shared: { react: { singleton: true }, "react-dom": { singleton: true } },
}),
],
});
宿主侧通过 remotes 指向远程入口即可。与 前端构建工具演进(Webpack→Vite→Rspack) 一文结合阅读,可以更好理解为什么新项目更倾向 Vite/Rspack 这类基于原生 ESM 的方案。
六、依赖共享与版本冲突治理
多个子应用都依赖 React,若各自打包一份会产生重复加载甚至”双 React”导致 hooks 报错。解决方式是统一 shared 配置:
shared: {
react: { singleton: true, requiredVersion: "^18.0.0", strictVersion: false },
"react-dom": { singleton: true, requiredVersion: "^18.0.0" },
}
| 配置项 | 作用 |
|---|---|
| singleton: true | 全局只保留一份实例,避免多 React 冲突 |
| requiredVersion | 声明期望版本,运行时按语义化择偶 |
| strictVersion: false | 版本不完全匹配时允许降级,提升容错 |
| eager: true | 同步打包进主包,适合极小组件 |
七、样式隔离与 JS 沙箱
Module Federation 本身不提供样式隔离,多个子应用全局 CSS 容易互相污染。常用手段:一是采用CSS Modules / Scoped CSS,把样式限制在组件作用域;二是借助 Shadow DOM 包裹子应用根节点;三是约定 BEM 命名前缀。JS 沙箱方面,若子应用需要强隔离,可引入 qiankun 的沙箱或基于 Proxy 的自研快照方案,但会牺牲一部分 MF 的”零运行时”优势。
八、路由与跨应用通信
路由分发有两种模式:基座统一路由(主应用持有路由表,按路径挂载对应子应用),或子应用自治(各自用 own router,基座只提供挂载点)。通信则推荐轻量方案:
// 共享一个事件总线(挂载到 window 或独立 store)
const bus = new EventTarget();
// 子应用 A 发布
bus.dispatchEvent(new CustomEvent("cart:changed", { detail: { count: 3 } }));
// 子应用 B 订阅
bus.addEventListener("cart:changed", (e) => renderBadge(e.detail.count));
对于复杂状态,建议把共享状态抽到独立 store(如 zustand / Pinia)并作为 singleton 共享,避免事件风暴。关于前端性能优化与 Lighthouse 调优 中提到的按需加载,也适用于微前端——子应用应默认懒加载,首屏只拉取必要模块。
九、生产落地检查清单
| 检查项 | 建议做法 |
|---|---|
| 共享依赖 | React/Vue 等设 singleton,统一版本 |
| 远程入口 | 部署到稳定 CDN,带内容哈希缓存 |
| 容错降级 | 远程加载失败显示兜底组件,不白屏 |
| 版本灰度 | 远程入口支持多版本共存与回滚 |
| 性能预算 | 监控远程包体积,超阈值报警 |
| CI/CD | 子应用独立流水线,参考 GitHub Actions 实战 |
十、常见误区与排障
误区 1:Invalid hook call(双 React)。 几乎都是 shared 没设 singleton,两个 React 实例并存。统一 singleton:true 即可。
误区 2:远程组件白屏。 先确认 remoteEntry.js 可访问、remotes 地址正确,再用浏览器的 Network 看是否被缓存旧入口。
误区 3:本地能跑、线上 404。 线上路径常带子目录或 HTTPS,remotes 地址要用绝对 URL 并和部署一致。
误区 4:无限循环加载。 宿主与远程互相暴露又互相引用,需在架构上明确边界,避免循环依赖。
十一、结语
微前端不是银弹,它与 Astro 内容优先架构、SvelteKit 全栈方案 一样,都是”按场景选架构”的工具。当你的痛点是多团队、多产物、独立部署时,Module Federation 能以极低的接入成本带来清晰的边界;如果只是一个小团队的中型 SPA,先做好模块化与 Monorepo 工程化 往往更划算。动手前,先用本文的清单评估收益,再决定要不要上。




