微前端架构实战:Module Federation、qiankun 与样式/JS 沙箱隔离

微前端架构实战:Module Federation、qiankun 与样式/JS 沙箱隔离
神经蛙单体前端一旦膨胀到几十万行、十几个团队协作,技术栈升级要全员陪跑、一次发版全站停机、一个模块出错拖垮整站——这些痛点的根因是”构建时被强行打包成一个应用”。微前端的核心思路是:把一个应用拆成多个可独立开发、独立部署、运行时再组合的子应用。本文不聊某个框架的内部响应式(那部分 Vue3/React 已经写过),只聊”多应用如何共存”这件事。
一、为什么需要微前端
传统 SPA 在规模变大后会出现三类典型困境:
- 巨石仓库:所有页面、所有团队挤在一个仓库里,依赖版本被强行统一,任何改动都要全量回归。
- 发布耦合:改一行也要走全站发版流程,无法做到”哪个团队改了哪个模块,就只发哪个模块”。
- 技术栈锁死:想从 Vue2 升级到 Vue3,或者引入 React,几乎不可能渐进式迁移,只能一次性重写。
微前端(Micro-Frontends)把”应用”拆成多个子应用,每个子应用拥有独立的技术栈、独立仓库、独立 CI/CD,在运行时由”基座(主应用)”把它们拼回一个完整的站点。
微前端 ≠ 组件库,也 ≠ 模块化打包。组件库解决的是”同一个应用内的复用”,微前端解决的是”不同团队、不同技术栈、不同生命周期的应用如何共存于一个页面”。
二、微前端必须解决的四个核心问题
| 核心问题 | 含义 | 不解决的后果 |
|---|---|---|
| 技术栈无关 | 子应用可用 Vue/React/NG/原生任意栈 | 又回到统一技术栈的死胡同 |
| 独立开发部署 | 子应用单独构建、单独发版 | 发版依旧全员耦合 |
| 运行时集成 | 在浏览器里拼装,而非构建时打包 | 无法独立加载、按需加载 |
| 样式与 JS 隔离 | 子应用之间互不影响全局环境 | 样式串台、全局变量互相覆盖 |
其中,运行时集成和隔离是工程上最难的两块,也是后文的重点。
三、主流集成方案对比
| 方案 | 隔离性 | 通信成本 | 独立部署 | 适用场景 |
|---|---|---|---|---|
| iframe | 天然最强(完全隔离) | 高(postMessage) | 强 | 弱耦合、第三方嵌入 |
| Web Components | 中(Shadow DOM 隔离样式) | 中(自定义事件) | 强 | 标准优先、组件级嵌入 |
| Module Federation | 弱(同运行时) | 低(直接 import) | 强 | 同技术栈拆包、模块共享 |
| qiankun / 基座模式 | 中(沙箱模拟隔离) | 低(全局状态) | 强 | 多技术栈存量系统整合 |
3.1 iframe:最朴素的隔离
iframe 最大的优点是完全隔离——每个子应用跑在自己的 browsing context 里,JS 全局对象、DOM、样式互不可见,天然不会样式串台。缺点是:
- 通信只能走
postMessage,开发体验差; - 路由同步、弹窗遮罩(
position: fixed被限制在 iframe 内)、浏览器前进后退都很难处理; - 性能开销大,多个 iframe 各加载一套运行时。
适合”嵌入一个相对独立的外部系统”,不适合做精细的页面拼装。
3.2 Module Federation:构建时的”模块共享”
Webpack 5 的 Module Federation(MF)让不同构建产物之间可以互相 import 远程模块,是”同技术栈拆包”的利器:
1 | // 子应用 webpack.config.js(暴露方) |
1 | // 基座 webpack.config.js(消费方) |
1 | // 基座里像导入本地模块一样用远程组件 |
MF 的优点是没有沙箱开销、加载即模块,但前提是各应用技术栈相近(至少都要是 Webpack/Rspack 体系),且隔离靠约定而非强制。
3.3 qiankun:基座 + 子应用
qiankun 是对 single-spa 的封装,提供”基座注册子应用 + HTML Entry 加载 + JS/CSS 沙箱”的一体化方案,对存量多技术栈系统最友好:
1 | // 基座:注册子应用 |
四、应用隔离:微前端的命门
多应用共存,最怕两件事:样式串台和JS 全局变量互相覆盖。
4.1 CSS 隔离
方案 A:Shadow DOM(最彻底)
把子应用挂到 Shadow Root 下,内部样式天然不外泄、外部样式也进不来。缺点是对依赖全局样式的第三方组件库(如 Ant Design 的弹窗 getPopupContainer)支持很不友好,实际落地成本高。
方案 B:运行时样式改写(qiankun 采用)
qiankun 加载子应用时,会重写 <style> 内容,给每条选择器加 data-qiankun-xxx 作用域前缀,相当于运行时版 CSS Modules:
1 | start({ |
方案 C:约定式命名空间
团队约定所有子应用样式加前缀(BEM 或 app-name-*),靠规范而非技术强制。成本低,但依赖人工纪律,容易漏。
4.2 JS 沙箱
子应用常会往 window 上挂全局变量、定时器、事件监听,若不做隔离,多个子应用之间会相互污染。
快照沙箱(legacy):子应用挂载前记录 window 快照,卸载时还原。实现简单,但不支持同时挂载多个子应用。
Proxy 沙箱(主流):为每个子应用创建一个假的 window(基于 Proxy),所有全局读写都落到这个假对象上,真实 window 毫发无损:
1 | function createProxySandbox() { |
qiankun 的 ProxySandbox 正是这个思路——单例模式下多个子应用共享一份 fakeWindow(串行运行),多例模式(qiankun 默认 strict 之外)下每个子应用独立 fakeWindow(可并行)。卸载子应用时把它的 fakeWindow、定时器、事件监听一并清理即可。
用 with(proxy) { eval(code) } 可以让子应用代码里的”裸写全局变量”也走 Proxy 拦截——这正是 qiankun 执行子应用入口脚本时做的事。
五、应用通信
| 方式 | 适用 | 缺点 |
|---|---|---|
| props 下发 | 基座 → 子应用单向传参 | 反向需回调 |
initGlobalState |
跨应用共享状态 | 需约定 state 结构 |
| 自定义事件 / 发布订阅 | 松耦合通知 | 易变成隐式依赖 |
qiankun 内置的全局状态最常用:
1 | // 基座 |
六、路由与加载
- activeRule:基座根据 URL 前缀决定激活哪个子应用(如
/vue激活 Vue 子应用)。 - HTML Entry vs JS Entry:HTML Entry 直接给子应用线上地址,基座拉取 HTML 再解析出 JS/CSS,对子应用侵入最小;JS Entry 需要子应用额外导出生命周期,耦合更重。
- 按需加载 + 预加载:非激活子应用可等路由命中再加载;对高频访问的子应用,基座空闲时用
requestIdleCallback预取remoteEntry,首屏切换更顺滑。
七、部署与版本治理
微前端真正的收益在”独立部署”:每个子应用构建产物独立发到 CDN,基座只改一份路由配置就能上下线子应用。但要注意:
- 基座与子应用版本解耦:子应用发版不应要求基座同步发版;基座只依赖子应用暴露的契约(路由、生命周期、通信 state)。
- 灰度与回滚:子应用按
entry指向不同环境(灰度域名),出问题切回旧remoteEntry即可,不影响基座。 - 依赖共享:MF 的
shared要约定好主版本,避免 React 双实例导致hooks报错。
八、什么时候不该上微前端
微前端不是银弹,它带来显著的复杂度成本:
- 团队 < 3 个、应用 < 5 个,单体 SPA + Monorepo 往往更省心;
- 没有独立部署诉求,纯粹为了”炫技”上微前端,性价比极低;
- 强实时协同、动画连贯性要求极高的场景,多运行时切换会有白屏/闪烁。
能用 Monorepo + 模块联邦解决”拆包”的,就别急着引入完整基座模式。
九、10 条生产避坑清单
- 子应用必须导出生命周期(
bootstrap/mount/unmount),否则基座无法调度。 - 公共依赖走 shared / CDN 单例,避免 React/Vue 双实例导致 hooks 失效。
- JS 沙箱记得卸载清理定时器、
window事件、全局变量,否则内存泄漏与串台。 - 样式隔离优先用运行时改写,Shadow DOM 对第三方弹窗组件库不友好。
- 子应用路由用相对基路径(如
base: '/vue'),避免与基座路由冲突。 - 通信 state 结构要版本化,字段变更要考虑旧子应用兼容。
- 预加载要有节制的做,别在首屏阶段抢占带宽。
- 部署解耦:子应用发版不要求基座同步发版,否则失去核心收益。
- 统一错误边界,单个子应用崩溃不应让整站白屏(基座兜底)。
- 约定全局变量命名空间,即使有沙箱,也别在真实
window上裸挂业务对象。
微前端把”团队协作的复杂度”从构建时转移到了运行时——它用一层基座和沙箱,换来了技术栈自由与独立部署。选型时记住:先问”要不要独立部署、要不要多技术栈”,再决定用 iframe / Module Federation / qiankun。能靠 Monorepo + 模块联邦解决的,就不必上完整基座。















