万字长文带你了解微前端架构

引言:前端架构演进与挑战

区别传统的静态网站,现在的各种 Web 应用被称为富互联网应用。在线服务的激增使我们能够方便地观看喜欢的电影和直播、即时订购外卖以及各种生活便利。

然而这样的技术实现往往会把功能都集中在一个项目当中,随着模块越来越多,这种「单体应用」就面临了许多难题:

  • 多人协作困难:多个开发团队在同一个代码仓库中协作,容易产生代码冲突、依赖冲突。

  • 代码复杂度高:项目膨胀,维护成本上升,回归测试压力变大。

  • 版本升级困难:一个模块的小变动可能影响整个应用,发布频率受到限制。

为了解决这样的问题,后端提出了「微服务架构」的解决方案。而在前端,同样演进出了对应的方向------微前端


一、什么是微前端?

1.1 微前端的定义

微前端是受微服务启发的一种新兴架构。它背后的主要思想是将一个单体代码库分解成多个较小的部分,以便多个相对独立的团队进行分工协作,实现了复杂业务解耦独立部署团队自治 。不仅适用于 monorepo ,也适用于 polyrepo

1.2 微前端的发展背景

微前端并不是一开始就存在的,而是伴随以下几个变化逐步发展出来的:

  • 公司业务越来越复杂,单一前端团队无法高效支撑;
  • 项目重构或多个产品线需要共存,但技术栈不统一;
  • 希望多个子系统可以独立部署、独立升级;
  • 前后端分离普及后,前端层也需要更细的拆分和治理。

1.3 适合微前端的业务类型

适合场景 说明
多产品线并存的后台管理系统 每个产品线可作为子应用独立开发和部署
技术栈混用(React、Vue、Angular) 允许不同技术栈共存
渐进式重构旧系统 可逐步替换旧模块为新微前端模块
多团队协作开发大型项目 团队边界清晰,职责明确

1.4 应用微前端

在应用微前端之前需要考虑以下因素:

  • 业务领域描述
  • 自治代码库
  • 独立部署
  • 单一团队负责

在确定应用微前端架构以后,需要确定如何从技术视角考虑微前端,并且需要决定是在同一个视图中集成多个微前端,还是每个视图中集成一个微前端,即如图所示横向拆分和纵向拆分。

在横向拆分方案中,同一个视图上会有多个微前端。在出现以下几种情况时效果比较好:

  • 当子业务在多个视图中渲染时,子业务的复用性成为业务关键
  • 当SEO时项目的必选项,需要使用服务端渲染时
  • 当前端项目至少几十人协同开发,不得不把项目拆分成多个子域
  • 当面临一个定制化的多租户项目时

而在纵向拆分方案中,每个团队负责一个业务领域。当项目需要一致的用户界面变化和跨页面的流畅的用户体验时,纵向拆分显得非常有用。对于 SPA 来说,纵向拆分方式的开发体验最为契合。纵向拆分中 App shell 利用全局路由来加载不同的微前端。

下图显示传统的微服务架构,其前端整体结构使用 API 网关连接到后端微服务。

下图显示了具有不同微服务实现方式的微前端架构。

1.5 微前端实践原则

Sam Newman在《微服务设计》中强调了一些微服务的原则,看看如何应用到微前端。

  1. 围绕业务领域建模:弄清如何分治应用有利于后期添加新功能
  2. 自动化文化:确保稳固的持续集成、流水线部署以及快速反馈循环
  3. 隐藏实现细节:每个团队不需要依赖外部,专注于内部的实现细节
  4. 分布式治理:分散团队的决策权,在特定领域之内的操作不需要等待领导决策
  5. 独立部署:每个团队按照自己的计划独立部署应用
  6. 故障隔离:必须提供替代的内容或者隐藏应用的对应部分
  7. 高度可观察性:前端监控变得日益重要,投入资源准备随时解决故障而非完全预防故障

二、微前端核心原理

2.1 主子应用的架构模型

微前端架构采用一种类似"门户 + 插件"的结构,将整个页面按照职责边界划分成主应用和多个子应用。

  • 主应用(Host App / Shell)

    • 负责整体框架结构的搭建,包括:

      • 全局路由管理
      • 公共组件(导航栏、菜单、权限控制等)
      • 子应用的注册、加载、卸载
      • 应用间通信调度器
  • 子应用(Sub App / Micro App)

    • 每个子应用是一个独立的 SPA 或 MPA,可以:

      • 拥有自己的路由体系
      • 使用独立的技术栈(React/Vue/Angular)
      • 独立构建、测试和部署
      • 在运行时被主应用动态挂载

一个类比:

可以把主应用看作"容器",子应用看作"插件模块"。主应用控制"何时加载哪个模块",而模块本身专注于自己的逻辑实现。

graph TD A[基座应用] --> B{路由分发器} B -->|匹配 /order| C[订单微应用] B -->|匹配 /user| D[用户中心微应用] B -->|匹配 /dashboard| E[数据看板微应用] A --> F[共享依赖库] C --> F D --> F E --> F A --> G[通信总线] C -->|事件发布| G D -->|事件监听| G

2.2 核心技术原理

微前端架构的最大魔力在于:它让多个独立开发、构建、部署的前端项目,看起来像是一个统一的网页应用。用户完全感知不到页面是在多个代码库之间跳转,开发者也可以实现子应用的独立演进。

那么,从技术上来说,微前端是怎么把一个子应用"嵌入"主应用中的呢?

2.2.1 子应用的基本状态:本质是一个远程资源包

从主应用角度看,每一个子应用就是一个远程构建好的静态资源包,可能部署在如下路径:

js 复制代码
https://static.example.com/sub-app-user/
  ├── index.html
  ├── main.js
  ├── styles.css

这个资源包含了子应用的HTML模板、JS入口文件、CSS样式文件。

2.2.2 主应用挂载子应用的流程

主应用并不会将子应用打包进来,而是通过配置动态加载远程子应用,过程大致如下:

步骤1:注册子应用

在主应用中注册子应用的信息:

ts 复制代码
registerMicroApps([
  {
    name: 'userApp',
    // 子应用的资源入口
    entry: 'https://static.example.com/sub-app-user/',
    // 渲染子应用的 DOM 容器
    container: '#subapp-viewport',
    // 子应用激活的路由规则
    activeRule: '/user',
  }
]);
步骤2:当路由命中时,主应用动态加载子应用资源

主应用拦截到当前路由为 /user 后,会执行如下操作:

  1. 通过 fetchiframe 等方式加载子应用入口页面(如 index.html
  2. 解析该 HTML 文件中需要加载的 JS、CSS
  3. 插入 <script><link> 标签,将子应用的资源动态注入主应用页面中
  4. 查找子应用打包暴露的全局对象(如 window['userApp'])或模块导出,执行其 mount() 函数
步骤3:主应用调用子应用的 mount(),渲染子应用

一旦子应用的 JS 加载完毕,就会调用其生命周期:

js 复制代码
window['userApp'].mount({
  container: document.querySelector('#subapp-viewport'),
  props: { fromMainApp: true }
});

此时:

  • 子应用会将自己的路由系统、组件、页面渲染到主应用提供的 container
  • 子应用像一个"沙箱应用"一样运行在主页面里
步骤4:切换路由时调用 unmount()

当用户跳转到另一个子应用(如 /order)时,主应用会调用当前子应用的 unmount() 方法,做清理工作(如移除事件监听、清空 DOM 节点等),然后加载并挂载新的子应用。

2.2.3 从用户视角看:为何"无感知"?

这套机制之所以能实现"用户无感"的体验,核心在于:

  • 页面不刷新,子应用是局部加载并渲染到主页面中
  • 主应用统一管理路由、导航栏等公共区域,切换时不丢失上下文
  • 各个子应用在同一个 URL 路径规则下工作,地址栏保持统一
  • 主子应用共享样式、组件、状态(如通过主应用传递 props)

因此,从用户视角来看,整个网页就像是一个完整的一体化系统,但背后其实是多个不同项目在协作运行。

2.3 微前端集成方案

微前端的核心目标是让多个独立前端项目协同工作 ,而"集成方案"指的就是:主应用如何加载、渲染并控制子应用的运行

2.3.1 iframe集成

原理 :主应用通过 iframe 加载子应用的页面,每个子应用运行在自己的浏览器上下文中。

js 复制代码
<iframe src="https://sub-app.example.com" />
2.3.2 运行时HTML插入+JS挂载

原理

  • 主应用在运行时,通过 AJAX 或 <script> 加载子应用的资源(HTML/JS/CSS);
  • 将子应用渲染到主应用指定容器;
  • 调用子应用暴露的生命周期函数:bootstrap → mount → unmount
js 复制代码
registerMicroApps([
  {
    name: 'orderApp',
    entry: 'https://order.example.com/',
    container: '#subapp-container',
    activeRule: '/order',
  },
]);
2.3.3 Webpack Module Federation(构建时集成)

原理

  • 在构建阶段声明共享模块(remote、exposes);
  • 主应用可以像本地模块一样引入子应用暴露的内容;
  • 支持模块按需加载,避免冗余依赖
js 复制代码
// 主应用 Webpack config
remotes: {
  userApp: 'userApp@https://cdn.example.com/remoteEntry.js'
}
2.3.4 基于 Web Components

Web Components 是浏览器原生支持的一组规范,包括:

  • Custom Elements:自定义标签,如 <user-app></user-app>
  • Shadow DOM:为组件提供样式和 DOM 隔离
  • HTML Templates:定义可复用的 HTML 结构

在微前端中,我们可以将每个子应用封装成一个 Web Component,然后在主应用中直接使用自定义标签进行挂载。

js 复制代码
<script src="https://cdn.example.com/user-app/main.js"></script>
<user-app></user-app>

2.4 总结

实现方式 集成时机 是否支持异构 隔离性 性能 通信复杂度 实现复杂度
iframe 运行时 ⭐⭐⭐⭐⭐(进程级) ❌ 慢 ⭐⭐⭐⭐(需 postMessage) ⭐(非常简单)
HTML 插入 + JS 挂载 运行时 ⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐(eventBus/props) ⭐⭐⭐⭐
Webpack Module Federation 构建时 ❌(需统一工具链) ⭐⭐(无沙箱) ⭐⭐⭐⭐⭐(最佳) ⭐(共享模块) ⭐⭐⭐⭐⭐
Web Components 运行时 ✅(原生支持) ⭐⭐⭐⭐⭐(Shadow DOM) ⭐⭐⭐⭐ ⭐⭐(自定义事件/props) ⭐⭐(需封装生命周期)

三、主流微前端框架对比

目前,微前端领域已涌现出多种实现方案和框架。以下介绍四种具有代表性的微前端框架,它们代表了不同的实现范式适用场景

3.1 Single-SPA

3.1.1 框架发展历程

Single-SPA 是最早定义"微前端"形态的框架之一,由 Canopy 公司在 2016 年开源,首次将主-子应用模型抽象为标准化的生命周期机制,并且允许不同技术栈的子应用共存(React、Vue、Angular 等)。

该框架目前已进入稳定维护期,是许多微前端框架的理论基础(包括 qiankun、wujie)。

3.1.2 实现原理概述

Single-SPA 并不负责子应用资源加载,而是提供运行时框架与生命周期调度机制

  • 统一注册子应用信息(基于路由)
  • 在路由切换时执行子应用的 bootstrap → mount → unmount
  • 通过 SystemJS 或开发者自定义方式加载子应用 JS 入口
  • 不强制样式隔离、沙箱机制,需自行集成
3.1.3 优点
  • 技术中立:支持多技术栈共存(React/Vue/Angular)
  • 生命周期清晰:微前端应用运行流程全流程掌控
  • 社区成熟:作为微前端概念提出者,架构思想广泛应用
  • 插件化强:与 SystemJS 等组合更灵活
3.1.4 缺点
  • 较低层:需要自己处理资源加载、样式隔离、通信机制
  • 学习成本略高,开发体验不如 qiankun 等"全家桶"
  • 子应用需主动适配生命周期(非零侵入)

3.2 qiankun

3.2.1 框架发展历程

qiankun 是由阿里巴巴旗下 Ant Financial 团队于 2019 年开源,构建于 Single-SPA 之上,但进一步实现了运行时资源加载、样式隔离、沙箱机制、通信机制等一整套完整解决方案,被誉为"企业级微前端最佳实践"。

目前在国内外都有广泛使用(如阿里云、钉钉、支付宝)。

3.2.2 实现原理概述

qiankun 的核心技术架构:

  • 构建于 single-spa 之上(生命周期调度)
  • 使用 HTML Entry 加载子应用资源(HTML + JS + CSS)
  • 使用 沙箱(Proxy + Snapshot) 实现全局变量隔离
  • 使用 CSS Scope + strict style isolation 处理样式冲突
  • 提供 initGlobalState() 等通信机制(基于发布-订阅)

主应用注册子应用示例:

ts 复制代码
registerMicroApps([
  {
    name: 'userApp',
    entry: 'https://cdn.xxx.com/user/',
    container: '#container',
    activeRule: '/user',
  }
]);
3.2.3 优点
  • 开箱即用:涵盖资源加载、生命周期、样式隔离、通信等
  • 兼容技术栈:支持 Vue、React、Angular、纯 HTML
  • 开发体验好:主子应用接入成本低,文档完善
  • 支持 prefetch 预加载机制
3.2.4 缺点
  • 内部实现复杂(调试较困难)
  • 非全局沙箱(不适合高安全要求的系统)
  • 不适合 SSR 场景(浏览器为核心)

3.3 wujie

3.3.1 框架发展历程

Wujie 是腾讯开源的一款轻量级微前端框架,诞生于 2022 年,目标是解决 qiankun 在性能、安全、加载机制方面的部分痛点。

Wujie 使用原生 iframe 嵌套 +动态补丁挂载方式,实现近乎原生性能的微前端架构。

3.3.2 实现原理概述
  • 每个子应用通过 iframe 承载(避免全局变量污染)
  • 使用原生浏览器机制进行沙箱和样式隔离(借助 Shadow DOM)
  • 启动阶段将子应用 DOM 迁移到主页面中(DOM Patch)
  • 提供主子通信、生命周期调度能力

Wujie 使用示例:

ts 复制代码
<wujie-vue
  name="user"
  url="https://user-app.cdn.com"
  sync
  alive
></wujie-vue>
3.3.3 优点
  • 高性能:相比 qiankun 启动更快(DOM patch 优化)
  • 真正隔离:基于 iframe,避免全局污染
  • 轻量实现:不依赖第三方调度框架
  • 支持 keep-alive、DOM 缓存
3.3.4 缺点
  • 运行机制复杂,调试门槛略高
  • 使用 iframe 带来部分兼容性风险
  • 缺少 qiankun 社区活跃度

3.4 module-federation

3.4.1 框架发展历程

Webpack Module Federation 是 Webpack 5 在 2020 年引入的新特性,支持多个独立构建的项目共享模块、组件、逻辑,实现构建时的微前端

它并不是微前端框架,但被广泛应用于构建类 monorepo 的大型项目中。

3.4.2 实现原理概述
  • 每个子应用通过 Webpack 配置 exposesremotes
  • 主应用在运行时加载远程模块(不再是 HTML,而是 JS Module)
  • 自动管理共享依赖(如 React、Vue),避免重复打包
js 复制代码
// 主应用
remotes: {
  userApp: 'userApp@https://cdn.example.com/remoteEntry.js'
}

子应用暴露模块:

js 复制代码
exposes: {
  './UserPage': './src/pages/UserPage.vue',
}
3.4.3 优点
  • 性能极佳:构建时优化,打包依赖最小
  • 模块级复用:可共享组件、hook、工具库等
  • 自动依赖去重:支持 singleton 配置
3.4.4 缺点
  • 仅适用于 Webpack 5(Vite、Rollup 不支持)
  • 配置复杂,调试成本高
  • 不适用于技术栈异构或运行时动态加载场景
  • 不提供生命周期、沙箱、通信机制(需自行实现)

四、微前端常见挑战

虽然微前端架构在工程上带来了灵活性与模块化,但也引入了许多"从单体应用中从未遇到"的复杂问题。

4.1 微前端的组合

微前端架构下,每个子应用由独立团队开发、部署,最终需要在主应用中"拼接"为一个整体用户体验。这个过程就是"组合"(Composition)。

  • 如何定义主应用与子应用的分工边界?
  • 子应用如何以模块化方式挂载?
  • 如何避免主子应用之间强耦合?
常见解决方案
  • 统一规范接口 :主应用通过配置(如 nameentrycontaineractiveRule)管理子应用挂载
  • 生命周期抽象 :采用 single-spa、qiankun 等框架定义 bootstrapmountunmount
  • 可视化组合平台:部分大厂通过后台平台配置哪些子应用加载到哪些容器

4.2 微前端的路由

传统 SPA 由一个框架控制整个路由系统,而微前端场景中,主子应用都需要路由,各自有自己的页面、跳转逻辑。

  • 主子应用路由如何解耦?
  • 子应用如何感知当前 URL?
  • 如何避免路由冲突或丢失?
常见解决方案
  • 嵌套路由机制 :主应用控制整体路由前缀(如 /user/*),子应用控制自身内部路由
  • hash 路由隔离 :主、子应用分别使用 historyhash 路由(避免路由冲突)
  • 统一监听机制:通过主应用统一监听 URL 变化并通知子应用(如 qiankun 自动处理)

4.3 微前端的状态管理

当用户跨子应用跳转(例如从"订单模块"跳转到"个人中心"),有些状态是全局共享的,比如登录信息、主题设置、语言等。

  • 状态在哪一层维护?主应用?子应用?
  • 子应用之间如何共享状态?
  • 如何避免状态污染或丢失?
常见解决方案
  • 主应用持有全局状态:如登录态、权限信息,由主应用下发
  • 通过通信桥接共享状态 :例如 qiankun 的 initGlobalState 提供了响应式共享状态
  • 封装状态管理插件 :如使用 redux-subspacezustand context bridge 等专门适配微前端的方案

4.4 微前端之间的通信

微前端系统中,多个子应用由不同团队维护,需要在运行时进行事件通知或数据交互。例如,商品模块通知购物车模块"商品已加入"。

  • 主子、子子通信通道如何建立?
  • 事件如何订阅/解绑?生命周期如何管理?
  • 通信协议是否标准化?
常见解决方案
  • 事件总线机制 :主应用提供全局 EventBus(可封装在 window 或 shared module)
  • 框架自带通信系统 :如 qiankun 的 initGlobalState 提供发布-订阅模型
  • 使用浏览器原生通信机制 :如 CustomEventwindow.postMessage(iframe 场景)

4.5 微前端之间的样式隔离

多个子应用渲染到同一页面中时,CSS 极易发生污染,比如全局样式冲突、类名重叠、第三方组件样式覆盖等。

  • 子应用样式是否会污染主应用?
  • 如何防止多个子应用样式互相干扰?
  • 如何保证主题风格一致的同时避免全局污染?
常见解决方案
  • 命名空间隔离:使用 CSS Modules、BEM、Scoped CSS 避免类名冲突
  • Shadow DOM:Web Components / Wujie 默认支持样式隔离
  • 运行时样式处理 :如 qiankun 提供 strictStyleIsolationscopedCSS 配置,将样式作用域限定在容器内

参考资料

  1. AWS规范性指导-了解和实现微前端
  2. 《微前端设计与实现》------卢卡 梅扎利拉
  3. 领域驱动设计------Eric Evans
相关推荐
加班是不可能的,除非双倍日工资8 分钟前
css预编译器实现星空背景图
前端·css·vue3
wyiyiyi42 分钟前
【Web后端】Django、flask及其场景——以构建系统原型为例
前端·数据库·后端·python·django·flask
gnip1 小时前
vite和webpack打包结构控制
前端·javascript
excel1 小时前
在二维 Canvas 中模拟三角形绕 X、Y 轴旋转
前端
阿华的代码王国2 小时前
【Android】RecyclerView复用CheckBox的异常状态
android·xml·java·前端·后端
一条上岸小咸鱼2 小时前
Kotlin 基本数据类型(三):Booleans、Characters
android·前端·kotlin
Jimmy2 小时前
AI 代理是什么,其有助于我们实现更智能编程
前端·后端·ai编程
ZXT2 小时前
promise & async await总结
前端
Jerry说前后端2 小时前
RecyclerView 性能优化:从原理到实践的深度优化方案
android·前端·性能优化
画个太阳作晴天2 小时前
A12预装app
linux·服务器·前端