微前端与架构
微前端(Micro Frontends)不是某个工具,而是一组让多个独立前端团队在同一个页面/产品里各自开发、独立部署的架构模式。它把后端的"服务拆分"思想搬到浏览器:运行时集成、独立发布、技术栈异构。本文先讲清楚它的真实动机与代价,再比较四种集成方案,最后落到 Module Federation、沙箱隔离与 Monorepo 的组织选择。
一、动机与代价:什么时候才值得上
| 收益 | 代价 |
|---|---|
| 团队自治:子应用独立开发、独立发布、独立回滚 | 集成复杂度:运行时加载、版本、样式/路由冲突 |
| 技术栈异构:老项目(jQuery/旧框架)与新框架并存迁移 | 性能负担:多份运行时、重复依赖、首屏链路变长 |
| 超大单体难以合作 → 按业务域切开 | 排障困难:跨应用问题需要联查 |
| 独立扩缩/灰度:只发某个域 | 规范成本:公共规范(组件/样式/埋点)必须强约束 |
决策原则:团队规模与业务边界才是触发条件——两三个人的团队、内聚的代码库,微前端只会引入负收益。它真正的适用场景是:几十人以上、按域划分的多个前端团队、需要不同节奏发布的大型后台/平台产品。先问"能不能用 Monorepo + 组件库解决",再考虑运行时微前端。
二、集成方案谱系:从轻到重
| 方案 | 原理 | 特点 | 适用 |
|---|---|---|---|
| iframe 嵌入 | 浏览器原生隔离 | 隔离最彻底、通信与体验差(跳转/高度/鉴权) | 强隔离的第三方/异域内容 |
| 路由分发(服务端拼装) | 网关按路径把整页路由到不同应用 | 每个 URL 完整独立应用 | 页面级拆分,不算严格微前端 |
| 运行时 JS 组合 | 主应用加载子应用入口 JS,子应用挂载到指定 DOM | 单页体验、共享壳 | 标准微前端形态(qiankun 等) |
| Module Federation | 构建期产物声明"远端模块",运行时按需加载并共享依赖 | 共享粒度到模块、依赖复用 | 依赖重度共享、组件级组合 |
| Web Components 桥 | 子应用包装成自定义元素 | 框架无关、但生态/通信麻烦 | 强隔离 + 框架无关场景 |
组合现实:qiankun 类(JS 入口)适合"壳 + 多个整页子应用";Module Federation 适合"运行时细粒度共享与依赖复用"——两者也可结合(壳用 JS 入口,内部依赖共享用 federation)。
三、Module Federation:构建期声明、运行时加载
Webpack 5 内建的模块联邦:构建时把一部分模块声明为 remote(可被外部消费的"远端"),并把 shared 依赖抽成交互版本:
js
// host 应用 webpack 配置
const { ModuleFederationPlugin } = require('webpack').container
new ModuleFederationPlugin({
name: 'hostApp',
remotes: {
cartApp: 'cartApp@http://cdn.example.com/cart/remoteEntry.js', // 引用远端
},
shared: { react: { singleton: true, requiredVersion: '^19.0.0' }, 'react-dom': { singleton: true } },
})js
// cart 子应用侧
new ModuleFederationPlugin({
name: 'cartApp',
exposes: { './MiniCart': './src/components/MiniCart.jsx' }, // 暴露模块
shared: { react: { singleton: true } },
})jsx
// host 中动态加载远端组件(懒加载,避免首屏拉整个 remote)
const MiniCart = React.lazy(() => import('cartApp/MiniCart'))
export function Shell() {
return <React.Suspense fallback={<Skeleton />}>
<MiniCart />
</React.Suspense>
}要点:
- shared + singleton:react/react-dom 等运行时只加载一份;版本策略(
requiredVersion)要前置约定,冲突会导致运行时报错; - remoteEntry.js 是契约入口,host 在运行时请求它解析远端模块表;
- 失败兜底:远端不可用时要有降级 UI(
Suspense+ 错误边界),不要整页白屏; - 打包器支持面:Webpack 5 / Rspack;Vite 侧可用社区插件(
@originjs/vite-plugin-federation等,注意其与 esbuild/依赖预构建的兼容坑)。
四、qiankun 类方案:壳 + 沙箱
js
// 主应用(qiankun 标准接入)
import { registerMicroApps, start } from 'qiankun'
registerMicroApps([
{
name: 'order',
entry: '//order.example.com', // 子应用 HTML 入口(JS 入口模式)
container: '#subapp-container', // 挂载点
activeRule: '/order', // 路由前缀命中时激活
props: { basePath: '/order', token: getToken() },
},
])
start()4.1 三件核心隔离
| 隔离面 | 机制 | 要点 |
|---|---|---|
| JS 沙箱 | 代理 window(proxy sandbox):子应用对 window 的读写被隔离在快照/代理内 | 全局污染、window.xxx 读写、定时器/监听在卸载时清理 |
| 样式隔离 | 默认给子应用样式加作用域(严格模式)或靠约定前缀 | 全局 reset/字体/第三方弹层样式仍易互相污染,推荐统一规范而非纯技术隔离 |
| 路由 | activeRule 前缀激活 + 子应用自身 hash/history 路由 | 跳转用主应用下发的方法,避免直接 window.location 把整个产品导航掉 |
4.2 通信:契约最小化
跨应用通信不要做成"随意共享 store"(会让边界消失):
- 优先消息事件:主应用 props 注入事件总线 /
CustomEvent,单向、少而清晰; - 共享状态尽量少:能靠 URL query/路由参数传参就不开全局状态;
- 必须跨应用共享的数据(用户信息)放主应用统一管理,子应用只读 props;
- 用明确的 schema/文档定义接口契约(方法名、参数、返回),跨团队不要靠口头约定。
五、Monorepo 组织:与微前端的正确关系
微前端解决"运行时如何合",Monorepo 解决"代码如何组织与共享"。两者互补而非替代——推荐先用 Monorepo 收敛代码与依赖,再按需把需要独立节奏的域拆成独立部署单元:
text
apps/
shell/ # 主应用(壳)
order/ # 订单子应用(可独立部署)
marketing/ # 营销子应用
packages/
ui/ # 共享组件库(版本化)
api-contract/ # 接口类型与契约(TS 共享)
config-eslint/ # 工程规范- pnpm workspace 管理依赖:重复依赖被 hoist 去重,子应用共享 react 时避免"双 react";
- Turborepo / Nx / Rush 做任务编排:缓存、按依赖图构建、只测受影响包;
- 版本策略:
packages/*独立版本号,子应用通过依赖版本升级而非直接改源码,界面更清晰; - 发布节奏解耦:Monorepo 里代码同仓,但 CI 可按
apps/order/**变更单独构建发布——"同仓协作 + 独立交付"是微前端与 Monorepo 结合的常见形态。
六、设计检查清单
- 壳只做导航/鉴权/布局/子应用编排,不承载业务;
- 子应用自包含:样式、路由、数据获取、错误兜底在自己内部闭环;
- 全局依赖(React/Vue 运行时、状态库)版本与单例策略在集成层显式声明;
- 首屏:子应用入口要能拆,别让第一个路由加载全部子应用 chunk;
- 加载状态/失败页由壳统一提供;
- 灰度/回滚:按子应用维度发布,回滚粒度也是子应用;
- 规范先行:样式命名、字体、断点、组件库、埋点 SDK——规范比技术隔离更重要。
七、常见坑速查
- 没有业务拆分就上微前端:代码边界不清,拆完耦合依旧,徒增集成层;
- 依赖没做 singleton/版本没约定:同一 React 加载两份,hooks 报错、context 不通——shared 配置与版本策略要锁死;
- 沙箱只隔离"看起来的全局":
requestAnimationFrame、setInterval、事件监听不清理,子应用卸载后仍泄漏——生命周期里成对清理; - 全局样式互踩:子应用 reset 覆盖壳、壳字体影响子应用——统一样式规范 + 必要时样式隔离层(见 样式方案);
- 跨应用 store 无节制共享:边界消失、一个子应用的 state 变化让整个壳重渲染——契约化最小通信(§4.2);
- 子应用直接改
window.location跳转:把整个产品导航走了——统一走壳下发的路由 API; - 把整个 remote 打进首屏:远端模块要懒加载,entry 也要能拆(首屏别拉整个 remoteEntry 对应的大 bundle);
- Monorepo + 微前端选型混着用但无节奏:所有包每次一起发 = 失去独立发布意义——按受影响范围做增量构建与发布门禁。
状态与参考
- 状态:已收录(2026-09-02,由前端领域规划清单「微前端与架构|模块联邦、Monorepo 组织」转正式)。
- 版本:Module Federation 基于 Webpack 5 内建(Rspack 亦支持);qiankun 示例为 JS 入口 + proxy sandbox 标准接入形态。文中配置为标准用法,可复制进对应脚手架的
webpack.config/入口文件运行;版本与维护状态以各项目官方仓库为准。 - 参考:Webpack Module Federation(概念与配置)、qiankun 文档、Module Federation 官方示例仓库、Turborepo、pnpm workspace。
- 阅读联动:运行时共享与打包产物结构见构建工具;多应用规范可对照 测试 的契约测试思路;团队内组件库沉淀见 样式方案 的设计令牌。
下一步
- [ ] 用 qiankun(或同类 JS 入口方案)跑通"壳 + 两个子应用"最小示例,验证 JS/样式隔离边界
- [ ] 用 Webpack 5 Module Federation 搭 host/remote 示例并实测 shared 单例与懒加载降级
- [ ] 写一篇"Monorepo(pnpm + Turborepo)接入微前端"的发布节奏与增量构建笔记
写作规范请参阅领域概览。