1876 字
9 分钟
微前端
微前端
一、 模块联邦 (Module Federation) 深度解析
1. 核心概念:什么是模块联邦?核心场景是什么?解决了什么痛点?有何副作用?
- 概念: 模块联邦是 Webpack 5 推出的一项技术,允许不同的 Webpack 构建过程在**运行时(Runtime)**互相动态加载和共享 JS 模块。
- 场景: 跨项目的局部复用与系统级拆分集成。
- 解决的痛点: 解决了传统基于 HTML Entry 的微前端加载重、公共依赖(如 Vue、Element-UI)难以完美共享导致的内存冗余问题,同时极大地缩短了大单体应用的打包时间。
- 副作用: 默认零隔离。底层不提供任何 JS 和 CSS 沙箱,极易引发全局变量冲突和样式污染;强依赖网络,提供方网络波动会导致消费方加载失败。
2. 底层原理:模块联邦的底层机制是什么?宿主是如何加载远程模块的?
- 构建时: 提供方在配置中暴露模块(如
exports.ts),Webpack 会将其单独打包成 Chunk,并生成一个remoteEntry.js清单文件,内部封装了拉取代码和初始化共享作用域的方法。 - 运行时: 宿主在执行时,Webpack 拦截底层的加载逻辑,先去网络上请求提供方的
remoteEntry.js,通过清单查找到真实 JS 代码的 URL 路径,利用动态创建<script>标签(JSONP 机制)将代码下载并执行。
3. 组件共享:A 项目调用 B 项目的业务组件,完整链路是怎样的?
- 步骤一: B 项目配置
exposes: { './exports': './src/exports.ts' },构建产出remoteEntry.js和对应的 Chunk 文件。 - 步骤二: A 项目配置 remotes 指向 B 项目的
remoteEntry.js。 - 步骤三: A 项目执行
loadComponent(name, './exports')获取 B 项目暴露的模块。 - 步骤四: A 项目运行时按需拉取清单并下载 Chunk,最终获取到 B 项目导出的业务对象或函数。 4. 依赖管理与版本冲突
- 如何共享? 通过 MF 插件的
shared字段配置。 - 版本冲突解决: 开启
singleton: true并配合requiredVersion。 - 强行覆盖风险: 跨度极大时,强制共享单例会导致低版本代码错误调用高版本 API,引发运行时崩溃。
- 防范破坏性升级: 通过
package.json的resolutions字段强制锁死核心基础库版本,主应用统一下发共享库,微应用严禁私自暴漏。
二、 微前端主流方案横向对比
1. 宏观对比:MF、Qiankun、Micro APP、Wujie 的原理与优缺点,为何选择 MF?
- Qiankun: 基于 Single-SPA 和 HTML Entry。Proxy 沙箱会带来明显的性能损耗,存在极少数沙箱逃逸的安全隐患。
- Micro APP: 借鉴 Web Components,侵入性低,但早期版本存在稳定性风险。
- Wujie(无界): iframe + Web Components,隔离完美,但多了一层 iframe 嵌套。
- 为何选 MF: 业务追求极致的原生渲染性能。MF 抛弃了沉重的沙箱和多实例挂载,配合单 Vue 实例动态注册机制,能实现真正的无缝切换。
2. Qiankun 核心机制与 window 快照功能
- 机制: 劫持路由,通过 Fetch 拉取子应用 HTML 并在 Proxy 沙箱中执行。
- window 快照: 降级沙箱。挂载前深拷贝
window存快照,卸载时比对修改并记录,最后强制恢复window初始状态。缺点是无法支持多实例同时运行。
3. Wujie 原理与 iframe 弹窗痛点突破
- 原理: 子应用 JS 放入隐藏 iframe 执行,DOM 放入基座的 Shadow DOM 渲染。
- 弹窗痛点突破: 采用通信委托机制。子应用通过
postMessage向基座发送信号,基座在最外层的 Document 下全屏渲染弹窗组件,彻底解决遮罩截断和样式耦合问题。
三、 沙箱隔离与通信机制 (核心难点)
1. 沙箱隔离 (Sandbox)
-
JS 隔离: 接受共享
window,摒弃 Proxy 拦截器,依靠 ESLint 规范约束。 -
CSS 隔离(自研 AST 编译期物理隔离): 针对 MF 缺少的 CSS 沙箱,自研 Webpack 插件与 Loader:
- 利用
buildModule钩子精准收集所有被联邦暴露的 Vue 和 JS 模块。 - 利用
beforeResolve钩子给目标模块请求路径追加?mf参数打标。 - 通过自定义的
mf-vue-section-loader,在处理带?mf标签的 Vue 文件时,利用正则强行拦截<template>内容,在最外层包裹一个专属的类名容器(如<section class="mf-container">)。从底层构建链路上实现了样式的物理隔离。
- 利用
2. 状态共享与通信 (单一实例模式) 结合加载流程架构,状态共享和通信变得极其原生且高效:
- 全局数据存放在哪? 全局数据(Token、用户信息)存放在主应用的 Vuex Store 中。
- 高效的数据通信: 并没有使用复杂的 EventBus。微应用在暴露的
exports.ts中导出一个异步函数bootstrap(),该函数返回子应用的路由配置和 VuexstoreModule。主应用在加载后,直接调用config.store.registerModule将子应用的状态合并到主应用中。因此,跨应用通信就是普通的 Vuex commit/dispatch。 * 保持状态不丢失: 从 A 切到 B,主应用并不会刷新。因为所有微应用最终是由主应用唯一的 Vue 实例统一渲染的,所有状态都在同一个内存级别的 Store 中流转,完美保持 SPA 体验。
四、 工程化部署与架构设计
1. 模块拆分粒度
- 按业务系统/领域拆分(如 easm-app、report-app)。子应用对外暴露统一的
exports.ts接口,包含该领域的路由和状态配置。
2. 部署策略
- 独立部署架构。主应用与微应用各自打包发布,通过 Nginx 路径分发。
3. 产物分析
- 基座(大单体)的初始构建体积大幅缩减,因为海量业务逻辑被抽离成了远端按需加载的 Chunk。
4. 架构设计:手写微前端核心模块 基于实际的“动态注册”架构,核心模块设计如下:
- 路由劫持引擎: 在 Vue Router 的
beforeEach钩子中拦截未知路由(!registeredRoutes.includes(to.name!))。 - 动态加载器 (Loader): 根据环境拼接微应用列表 URL,执行
loadScript(chunk)加载remoteEntry.js,并提取核心模块loadComponent(name, './exports')。 - 合并注册引擎: 调用子应用
exports.bootstrap()拿到配置,利用addRoutes注册路由,利用registerModule注册状态。 - 沙箱引擎: 运行时的沙箱被剥离,替换为构建时的自定义 AST 样式隔离器。
五、 业务迁移演进与最终收益
1. 背景与收益
- 背景: 平台面临巨石应用危机,打包慢、耦合深。
- 收益: CI/CD 极速提升,业务线代码物理解耦并行开发。由于微应用被直接整合进主应用的单个 Vue 实例中渲染,去除了传统微前端频繁初始化/销毁 Vue 实例的开销,获得了极致的运行时性能。
2. 平滑迁移策略 (绞杀者模式)
- 搭建全新的主应用基座,配置 Nginx。逐步剥离大单体中的模块重构成微应用,新路径走基座加载,老路径代理回大单体,最终实现大单体的完全解体。
3. 基座改造:Webpack 4 升 5 的代价
- Node Polyfill 断层,需手动补齐垫片;Asset Modules 强制重构,废弃旧版 url-loader;清理替换不兼容 Webpack 5 缓存机制的旧版生态插件。