前言:两年前的文章《乾坤微前端》中曾探讨过乾坤(qiankun)微前端实现方案。然而,在两年后再次进行微前端方案调研时,技术现状已发生明显变化。
乾坤(qiankun)的 npm 包最新版本号仍停留在 3.0.0-rc.19,近两年未有更新。其官方文档截至 2025 年 9 月也长期未维护,且提供的示例仅支持 Vue 2,缺乏持续更新支持。因此,该方案在当前微前端架构选型中已不再推荐。
基于这一背景,重新梳理了近期活跃且具备发展潜力的微前端方案。

PS: KPI项目,几年不动
总体概览
Why not iframe
谈到微前端绕不开的话题就是为什么不适用 iframe 作为承载微前端子应用的容器,其实从浏览器原生的方案来说,iframe 不从体验角度上来看几乎是最可靠的微前端方案了,主应用通过 iframe 来加载子应用,iframe 自带的样式、环境隔离机制使得它具备天然的沙盒机制,但也是由于它的隔离性导致其并不适合作为加载子应用的加载器,iframe 的特性不仅会导致用户体验的下降,也会在研发在日常工作中造成较多困扰,以下总结了 iframe 作为子应用的一些劣势:
使用 Iframe 会大幅增加内存和计算资源,因为 iframe 内所承载的页面需要一个全新并且完整的文档环境
Iframe 与上层应用并非同一个文档上下文导致:
事件冒泡不穿透到主文档树上,焦点在子应用时,事件无法传递上一个文档流:
a.主应用劫持快捷键操作b.事件无法冒泡顶层,针对整个应用统一处理失效
跳转路径无法与上层文档同步,刷新丢失路由状态
Iframe 内元素会被限制在文档树中,视窗宽高限制问题
Iframe 登录态无法共享,子应用需要重新登录
Iframe 在禁用三方 cookie 时,iframe 平台服务不可用
Iframe 应用加载失败,内容发生错误主应用无法感知
难以计算出 iframe 作为页面一部分时的性能情况
无法预加载缓存 iframe 内容
无法共享基础库进一步减少包体积
事件通信繁琐且限制多
腾讯 无界(wujie)
技术方案
应用加载机制和 js 沙箱机制
将子应用的js注入主应用同域的iframe中运行,iframe是一个原生的window沙箱,内部有完整的history和location接口,子应用实例instance运行在iframe中,路由也彻底和主应用解耦,可以直接在业务组件里面启动应用。
路由同步机制
在iframe内部进行history.pushState,浏览器会自动的在joint session history中添加iframe的session-history,浏览器的前进、后退在不做任何处理的情况就可以直接作用于子应用
iframe 连接机制和 css 沙箱机制
采用webcomponent来实现页面的样式隔离,无界会创建一个wujie自定义元素,然后将子应用的完整结构渲染在内部
子应用的实例instance在iframe内运行,dom在主应用容器下的webcomponent内,通过代理 iframe的document到webcomponent,可以实现两者的互联。
通信机制
承载子应用的iframe和主应用是同域的,所以主、子应用天然就可以很好的进行通信,在无界我们提供三种通信方式
ps: iframe升级版,如果是十分简单的功能建议直接使用iframe即可。
京东 MicroApp
路由机制
主应用路由驱动。与无界(wujie)方案类似,都是基于 WebComponent 的容器方案。
主应用通过一个组件(如 <micro-app name='app1' url='...'>) 来渲染子应用。
当主应用的路由变化导致该组件渲染时,子应用被加载和渲染。
子应用内部的路由变化,会通过同步路由信息到主应用 URL 的方式保持一致性(例如,Vue 子应用的路由变化会反映为 http://main-app.com/#/vue-app/child-route)。
路由对开发者来说是“透明”的,主应用正常使用自己的路由,子应用也正常使用自己的路由,框架在底层通过代理和同步机制将它们关联起来,体验非常流畅。
数据同步机制
提供内置的全局通信对象。
提供了 window.microApp 对象。通过 microApp.dispatch({type: 'event'}) 发送数据,通过 microApp.addDataListener() 监听数据。同时也支持通过 <micro-app> 组件的 data 属性传值。
非常便捷,开箱即用,API 设计简单直观,降低了通信的复杂度。
single-spa
single-spa是一个目前主流的微前端技术方案,其主要实现思路:
预先注册子应用(激活路由、子应用资源、生命周期函数)
监听路由的变化,匹配到了激活的路由则加载子应用资源,顺序调用生命周期函数并最终渲染到容器
路由机制
中心化路由注册。主应用是一个“应用程序加载器”,它维护一个“应用注册表”,其中定义了每个子应用的激活条件(通常是 URL 前缀,如 activeWhen: '/react')。当 URL 变化时,single-spa 会检查哪个应用应该被挂载或卸载,并调度其生命周期函数。
它只负责路由的“匹配”和“调度”,不负责具体的“跳转”和“显示”。子应用需要导出特定的生命周期函数(bootstrap, mount, unmount)。主应用和子应用的路由是强关联的。
数据同步机制
不提供任何内置数据通信方式。这是一个“你必须自己管理状态”的框架。
通常需要自行采用:
自定义事件 (Custom Events):window.dispatchEvent 和 window.addEventListener。
状态管理库:在主应用初始化一个 Redux 或 Pinia store,并通过 props 传递给子应用,或者让所有应用共享一个全局状态。
Observable 模式:实现一个简单的发布订阅库。
最灵活,但也最繁琐,需要自己考虑数据隔离和冲突问题。
ps: 需要非常好的项目管理约定和管理
字节 Garfishjs
路由机制
一体化路由系统。提供了非常完善的路由解决方案,支持“驱动式”和“托管式”两种模式。
驱动式:主应用控制路由,子应用由 Garfish 根据配置的路由激活条件进行挂载。
托管式:子应用自带路由(如 React Router、Vue Router),Garfish 会劫持路由变化,让子应用的路由系统在主应用的路由上下文中无缝运行,实现了主子和子应用的路由解耦。
路由能力是它的核心优势之一,对复杂路由场景(如子应用内部跳转、主应用跳转子应用特定路由)支持得很好。
数据同步机制
内置通信桥接器 (@garfish/bridge)
Bridge 提供了一套规范,让子应用可以安全地与主应用通信。它抹平了不同框架(React, Vue等)之间的差异,提供类似 props 传递和回调函数的方式。
主应用通过 props 向子应用传递数据。
子应用通过 garfish.globalThis 上提供的方法(如 emit)触发事件,主应用监听。
官方提供的标准化方案,安全性和可维护性比自行实现的自定义事件更高。
Module Federation (MF)
路由机制
没有内置路由机制。MF 本身只是一个“模块共享”的协议和实现。它解决的是“如何远程加载另一个应用的模块并运行它”的问题。
要实现微前端路由,你必须结合其他方案,例如:
结合 single-spa:用 single-spa 做路由调度,用 MF 来加载应用模块。
结合 手动实现:在主应用中手动监听路由变化,动态加载远程组件并渲染。
它更像一个“乐高积木”,需要你自行组装成微前端。
数据同步机制
共享模块即状态。MF 的核心是“共享”,你可以将一个 Redux store 或者一个 Vuex store 作为“共享模块”暴露出去。
主应用和子应用都去消费这个相同的共享单例模块。对这个 store 的任何修改,在所有应用中都是即时生效的。
通信方式非常“原生”,就是模块导入。但需要谨慎设计共享状态的结构,避免循环依赖和不可预知的状态变更。
ps: 模块的共享机制非常简洁明了。
样例
包及版本说明
子应用向主应用暴露模块
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
import vueDevTools from 'vite-plugin-vue-devtools'
import federation from '@originjs/vite-plugin-federation'
// https://vite.dev/config/
export default defineConfig({
plugins: [
vue(),
vueJsx(),
vueDevTools(),
federation({
name: 'demo-one', // 子应用名称
filename: 'remoteEntry.js', // 远程入口文件
// 暴露给主应用的模块
exposes: {
'./PreviewDetail': './src/components/Preview/index.vue',
},
shared: ['vue', 'vue-router'], // 共享依赖,避免重复加载
}),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
},
},
})
主应用引入
import { defineConfig, loadEnv } from 'vite'
import federation from '@originjs/vite-plugin-federation'
const config = ({ mode }) => {
return {
plugins: [
federation({
name: 'host-app',
remotes: {
'demo-one': APP_PLUGINS_URL + '/portal/assets/remoteEntry.js'
// 'demo-one': 'http://localhost:8098/portal/assets/remoteEntry.js'
},
shared: {
vue: {
singleton: true,
requiredVersion: '^3.2.0'
},
'vue-router': {
singleton: true,
requiredVersion: '^4.0.0'
}
}
})
]
}
}
export default defineConfig(config)
主应用页面中引用
<template>
<PreviewDetail :data="data" :type="categories" />
</template>
<script setup>
import { defineAsyncComponent } from 'vue'
const PreviewDetail = defineAsyncComponent({
loader: () => import('demo-one/PreviewDetail'),
onError(error, retry, fail, attempts) {
if (attempts <= 3) {
retry()
} else {
console.error('加载远程组件失败:', error)
fail()
}
},
delay: 200,
timeout: 3000
})
</script>
<style scoped>
</style>参考资料
如何设计微前端中的主子路由调度:https://mp.weixin.qq.com/s/TAXP7ipDdtb2Jb-L3QHszA↗
如何取巧实现一个沙箱:https://mp.weixin.qq.com/s/Mg3fU0WvZUQnlWHdxc-b5A↗
微服务架构及其最重要的 10 个设计模式:https://www.infoq.cn/article/kdw69bdimlx6fsgz1bg3↗
single-spa:https://github.com/single-spa/single-spa↗
评论