1876 字
9 分钟
微前端
2026-02-13
2026-02-20
统计加载中...

微前端#

一、 模块联邦 (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.jsonresolutions 字段强制锁死核心基础库版本,主应用统一下发共享库,微应用严禁私自暴漏。

二、 微前端主流方案横向对比#

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:

    1. 利用 buildModule 钩子精准收集所有被联邦暴露的 Vue 和 JS 模块。
    2. 利用 beforeResolve 钩子给目标模块请求路径追加 ?mf 参数打标。
    3. 通过自定义的 mf-vue-section-loader,在处理带 ?mf 标签的 Vue 文件时,利用正则强行拦截 <template> 内容,在最外层包裹一个专属的类名容器(如 <section class="mf-container">)。从底层构建链路上实现了样式的物理隔离。

2. 状态共享与通信 (单一实例模式) 结合加载流程架构,状态共享和通信变得极其原生且高效:

  • 全局数据存放在哪? 全局数据(Token、用户信息)存放在主应用的 Vuex Store 中。
  • 高效的数据通信: 并没有使用复杂的 EventBus。微应用在暴露的 exports.ts 中导出一个异步函数 bootstrap(),该函数返回子应用的路由配置和 Vuex storeModule。主应用在加载后,直接调用 config.store.registerModule 将子应用的状态合并到主应用中。因此,跨应用通信就是普通的 Vuex commit/dispatch。 * 保持状态不丢失: 从 A 切到 B,主应用并不会刷新。因为所有微应用最终是由主应用唯一的 Vue 实例统一渲染的,所有状态都在同一个内存级别的 Store 中流转,完美保持 SPA 体验。

四、 工程化部署与架构设计#

1. 模块拆分粒度

  • 业务系统/领域拆分(如 easm-app、report-app)。子应用对外暴露统一的 exports.ts 接口,包含该领域的路由和状态配置。

2. 部署策略

  • 独立部署架构。主应用与微应用各自打包发布,通过 Nginx 路径分发。

3. 产物分析

  • 基座(大单体)的初始构建体积大幅缩减,因为海量业务逻辑被抽离成了远端按需加载的 Chunk。

4. 架构设计:手写微前端核心模块 基于实际的“动态注册”架构,核心模块设计如下:

  1. 路由劫持引擎: 在 Vue Router 的 beforeEach 钩子中拦截未知路由(!registeredRoutes.includes(to.name!))。
  2. 动态加载器 (Loader): 根据环境拼接微应用列表 URL,执行 loadScript(chunk) 加载 remoteEntry.js,并提取核心模块 loadComponent(name, './exports')
  3. 合并注册引擎: 调用子应用 exports.bootstrap() 拿到配置,利用 addRoutes 注册路由,利用 registerModule 注册状态。
  4. 沙箱引擎: 运行时的沙箱被剥离,替换为构建时的自定义 AST 样式隔离器。

五、 业务迁移演进与最终收益#

1. 背景与收益

  • 背景: 平台面临巨石应用危机,打包慢、耦合深。
  • 收益: CI/CD 极速提升,业务线代码物理解耦并行开发。由于微应用被直接整合进主应用的单个 Vue 实例中渲染,去除了传统微前端频繁初始化/销毁 Vue 实例的开销,获得了极致的运行时性能。

2. 平滑迁移策略 (绞杀者模式)

  • 搭建全新的主应用基座,配置 Nginx。逐步剥离大单体中的模块重构成微应用,新路径走基座加载,老路径代理回大单体,最终实现大单体的完全解体。

3. 基座改造:Webpack 4 升 5 的代价

  • Node Polyfill 断层,需手动补齐垫片;Asset Modules 强制重构,废弃旧版 url-loader;清理替换不兼容 Webpack 5 缓存机制的旧版生态插件。