概念
🌐 微前端(Micro Frontends)概念详解
✅ 什么是微前端?
微前端是一种将前端应用拆分成多个独立、自治、可单独开发和部署的“小前端应用”的架构理念,借鉴了微服务在后端的思想。
通俗地说:
把一个大前端项目拆分成多个小应用,每个小应用可以独立开发、测试、部署、运行,最终集成在一个主应用中。
🧩 举个例子:
假设你在做一个电商系统,包含:
传统方式是一个大 SPA 项目,全放在一个仓库里。但微前端下:
- 商品展示 → 一个独立的前端项目
- 购物车 → 一个独立的项目
- 用户中心 → 一个独立项目
- 这些模块运行时被主应用“动态加载”和“组合展示”。
💡 微前端的核心理念:
|
|
概念 |
解释 |
技术栈无关 |
每个子应用可以使用不同的框架,比如 React + Vue 混用 |
独立开发 |
子应用独立代码仓库、独立部署、独立生命周期 |
运行时集成 |
子应用按需加载,不需要打包到主应用中 |
状态隔离 |
每个子应用有自己的作用域,防止全局变量、样式等冲突 |
主从架构 |
主应用负责容器、路由分发,子应用负责各自的业务展示 |
🛠 常用微前端实现方式:
|
|
|
方案 |
原理 |
优缺点 |
iframe |
子应用运行在 iframe 中 |
简单但隔离过强,通信困难,体验差 |
JavaScript 方式(如 qiankun) |
主应用动态加载子应用资源,通过 JS 沙箱和 DOM 控制集成 |
灵活、性能好、适合企业场景,推荐使用 |
模块联邦(Module Federation) |
Webpack 5 提供的功能,构建时共享模块 |
更适合技术统一的团队,构建时集成而非运行时 |
✅ 微前端的优点:
- ✨ 提升开发效率(多个团队并行开发)
- 🛠 模块解耦,独立部署
- 🔄 方便技术栈迁移(逐步替换 Vue2 → Vue3)
- 🧪 子应用独立测试,更容易定位问题
⚠️ 微前端的挑战:
- 全局状态管理复杂
- 路由管理需要协调
- 样式/脚本冲突需要隔离
- 性能要优化(多个子应用同时加载时)
📌 总结一句话:
微前端就像把“大房子”拆成多个“独立小房间”,每个房间可以由不同团队装修(甚至风格不同),但最终拼成一个完整的“家”。
为什么不用 iframe(摘选自 qiankun 技术团队)?
Why Not Iframe?
为什么不用 iframe,这几乎是所有微前端方案第一个会被 challenge 的问题。但是大部分微前端方案又不约而同放弃了 iframe 方案,自然是有原因的,并不是为了 "炫技" 或者刻意追求 "特立独行"。
如果不考虑体验问题,iframe 几乎是最完美的微前端解决方案了。
iframe 最大的特性就是提供了浏览器原生的硬隔离方案,不论是样式隔离、js 隔离这类问题统统都能被完美解决。但他的最大问题也在于他的隔离性无法被突破,导致应用间上下文无法被共享,随之带来的开发体验、产品体验的问题。
- url 不同步。浏览器刷新 iframe url 状态丢失、后退前进按钮无法使用。
- UI 不同步,DOM 结构不共享。想象一下屏幕右下角 1/4 的 iframe 里来一个带遮罩层的弹框,同时我们要求这个弹框要浏览器居中显示,还要浏览器 resize 时自动居中..
- 全局上下文完全隔离,内存变量不共享,frame 内外系统的通信、数据同步等需求,主应用的 cookie 要透传到根域名都不同的子应用中实现免登效果。
- 慢。每次子应用进入都是一次浏览器上下文重建、资源重新加载的过程
其中有的问题比较好解决(问题1),有的问题我们可以睁一只眼闭一只眼(问题4),但有的问题我们则很难解决(问题3)甚至无法解决(问题2),而这些无法解决的问题恰恰又会给产品带来非常严重的体验问题, 最终导致我们舍弃了 iframe 方案。
阿里开源的微前端框架,基于 single-spa 开发,对 single-spa 进行了封装。
核心通过 import-html-entry 动态加载子应用的 HTML、CSS、JS 等资源,和 JS 沙箱的机制实现
主应用中注册子应用
使用 registerMicroApps Api来基于路由地址加载子应用
import {
registerMicroApps,
start
}
from 'qiankun';
registerMicroApps([{
name: 'sub-app-1-name',
entry: '//localhost:3000',
container: '#yourContainer',
activeRule: '/yourActiveRule',
}], {
beforeLoad: (app) => customLog(`子应用beforeLoad:$ {
app.name
}`),
beforeMount: (app) => customLog(`子应用beforeMount:$ {
app.name
}`),
afterMount: (app) => customLog(`子应用afterMount:$ {
app.name
}`),
beforeUnmount: (app) => customLog(`子应用beforeUnmount:$ {
app.name
}`),
afterUnmount: (app) => customLog(`子应用afterUnmount:$ {
app.name
}`),
});
start();
子应用配置
- 子应用需要在自己的入口 js (通常就是你配置的 webpack 的 entry js) 导出 bootstrap、mount、unmount 三个生命周期钩子,以供主应用在适当的时机调用。
import {
createApp
}
from "vue";
let instance = null;
function render(props = {}) {
instance = createApp(App);
instance.mount("#sub-app");
}
/**
* bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。
* 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。
*/
export async
function bootstrap() {
console.log('app bootstraped');
}
/**
* 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
*/
export async
function mount(props) {
render(props);
}
/**
* 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
*/
export async
function unmount(props) {
instance.unmount();
instance.$el.innerHTML = "";
instance = null;
}
/**
* 可选生命周期钩子,仅使用 loadMicroApp 方式加载微应用时生效
*/
export async
function update(props) {
console.log('update props', props);
}
- 子应用的打包工具需要增加如下配置:
// webpack v5:
const packageName = require('./package.json').name;
module.exports = {
output: {
// 将打包后的代码暴露为一个库,命名为 ${packageName}-[name]
library: `$ {
packageName
} - [name]`,
// "umd"格式,使模块能在不同环境中使用(CommonJS/AMD/全局变量)
libraryTarget: 'umd',
// 设置加载异步chunk时的全局变量名,防止多个微应用间冲突
chunkLoadingGlobal: `webpackJsonp_$ {
packageName
}`,
},
};
// webpack v4:
const packageName = require('./package.json').name;
module.exports = {
output: {
library: `$ {
packageName
} - [name]`,
libraryTarget: 'umd',
jsonpFunction: `webpackJsonp_$ {
packageName
}`,
},
};
UMD(Universal Module Definition,通用模块定义)是一种兼容多种环境的JavaScript模块格式:
- 它的主要特点是"一次编写,到处运行",可同时支持:
-
- CommonJS(Node.js环境)
- AMD(如RequireJS)
- 全局变量(浏览器环境)
- UMD格式的代码会首先检测当前运行环境,然后采用对应的模块系统:
-
- 如果发现存在CommonJS环境,就使用module.exports导出
- 如果发现存在AMD环境,就使用define定义模块
- 如果以上都不存在,则将模块挂载到全局对象(如window)上
- 在微前端架构中,UMD格式非常重要,因为它使子应用能够被主应用灵活加载,无论主应用使用哪种模块系统,都能正确识别和使用子应用。
- 新增 public-path.js 文件,用于修改运行时的 publicPath
以 vue 项目为例:
- 在 src 目录新增 public-path.js
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
- 入口文件 main.js 修改,为了避免根 id #app 与其他的 DOM 冲突,需要限制查找范围
function render(props = {}) {
const {
container
} = props;
instance = createApp(App);
instance.mount(container ? container.querySelector("#app") : "#app");
}
同时主应用中应传递用于渲染子应用的容器元素或者id:
registerMicroApps([{
name: 'sub-app-1-name',
entry: '//localhost:3000',
container: '#yourContainer',
activeRule: '/yourActiveRule',
props: {
container: "#sub-app-1-box",
},
}]);
- 子应用需要配置允许跨域访问
生产环境需要配置 nginx,开发时可以通过配置开发代理实现:
// webpack.config.js
devServer: {
headers: {
"Access-Control-Allow-Origin": "*",
},
}
访问子应用静态资源
静态资源访问问题
以图片为例:
-
资源命名冲突:
-
跨域资源请求失败:
-
样式引用路径错误:
-
子应用间资源污染:
解决方法
小尺寸图片转为 base64 字符串,大尺寸图片通过配置代理(nginx)的方式解决:
// 子应用 webpack.config.js
module.exports = {
module: {
rules: [{
test: /\.(png|jpe?g|gif|svg|webp)$/i,
type: "asset",
parser: {
dataUrlCondition: {
maxSize: 1024 * 1024,
// 1MB
},
},
generator: {
filename: "sub1-images/[name].[hash:8][ext]",
publicPath: "/",
},
},
]
}
}
// 主应用 vite.config.js
import {
defineConfig
}
from "vite";
export
default defineConfig({
server:
{
proxy:
{
"/sub1-images":
{
target:
"http://localhost:3000",
},
"/sub2-images": {
target: "http://localhost:3001",
},
},
},
});
数据通信
通过在主应用中调用 initGlobalState Api 来实现,它会返回三个方法:
- onGlobalStateChange: (callback: OnGlobalStateChangeCallback, fireImmediately?: boolean) => void, 在当前应用监听全局状态,有变更触发 callback,fireImmediately = true 立即触发 callback
- setGlobalState: (state: Record) => boolean, 按一级属性设置全局状态,微应用中只能修改已存在的一级属性
- offGlobalStateChange: () => boolean,移除当前应用的状态监听,微应用 umount 时会默认调用
并且将这三个方法默认通过 props 传递给子应用,子应用只需接收到后 provide 到全局就行
路由跳转
主应用中控制切换不同子应用
通过路由配置,注意,展示子应用的路由匹配规则必须带通配符以匹配所有可能的路径,否则子应用内部路由切换时,路由地址改变,会导致页面无法加载:
// router.js
{
path: "/sub-app-1/:subpath(.*)*",
name: "sub-app-1",
meta: {
title: "展示子应用1",
},
component: () = >import("@/views/subApp1/index.vue"),
},
{
path: "/sub-app-2/:subpath(.*)*",
name: "sub-app-2",
meta: {
title: "展示子应用2",
},
component: () = >import("@/views/subApp2/index.vue"),
}
registerMicroApps([{
name: "sub-app-1",
// app name registered
entry: "//localhost:3000",
container: "#sub-app-1-box",
activeRule: "/sub-app-1",
props: {
container: "#sub-app-1-box",
},
},
{
name: "sub-app-2",
// app name registered
entry: "//localhost:3001",
container: "#sub-app-2-box",
activeRule: "/sub-app-2",
props: {
container: "#sub-app-2-box",
},
},
], );
子应用控制主应用路由跳转,切换到其他子应用
在主应用中注册子应用时,将主应用的路由实例传递给子应用,子应用接收后,provide 到全局中
// 主应用 main.js
import routes from "./router";
const router = createRouter({
history: createWebHistory("/"),
routes,
});
registerMicroApps([{
name: "sub-app-1",
// app name registered
entry: "//localhost:3000",
container: "#sub-app-1-box",
activeRule: "/sub-app-1",
props: {
container: "#sub-app-1-box",
router
},
},
]);
// 子应用 main.js
function render(props = {}) {
const {
container,
router: parentRouter,
setGlobalState
} = props;
router = createRouter({
history: createWebHistory(window.__POWERED_BY_QIANKUN__ ? "/sub-app-1": "/"),
routes,
});
instance = createApp(App);
instance.use(router);
instance.provide("setGlobalState", setGlobalState);
instance.provide("parentRouter", parentRouter);
instance.mount(container ? container.querySelector("#app") : "#app");
}
样式隔离
调用 strat 时,如下配置,qiankun 会改写子应用所添加的样式为所有样式规则增加一个特殊的选择器规则来限定其影响范围
start({
sandbox: {
experimentalStyleIsolation: true,
},
});
此时主应用通过 !important 可以影响到子应用样式
京东开源的微前端框架,借鉴了 WebComponent 的思想,通过js沙箱、样式隔离、元素隔离、路由隔离模拟实现了 ShadowDom 的隔离特性,并结合CustomElement将微前端封装成一个类 WebComponent 组件,从而实现微前端的组件化渲染。
主应用中注册子应用
Micro App 以组件的形式注册子应用:
// main.js
import microApp from "@micro-zoe/micro-app"microApp.start({
lifeCycles: {
created(e, appName) {
customLog(`子应用$ {
appName
}被创建`);
},
beforemount(e, appName) {
customLog(`子应用$ {
appName
}即将渲染`);
},
mounted(e, appName) {
customLog(`子应用$ {
appName
}已经渲染完成`);
},
unmount(e, appName) {
customLog(`子应用$ {
appName
}已经卸载`);
},
error(e, appName) {
customLog(`子应用$ {
appName
}加载出错`);
},
},
});
// 展示子应用的页面
<micro-app
name="sub-app-1"
url="http://localhost:3002/"
router-mode="native"
baseroute="/sub-app-1/"
iframe
></micro-app>
子应用配置
// main.js
const app = createApp(App) app.mount('#app')
// 卸载应用
window.unmount = () = >{
app.unmount()
}
完成以上配置,子应用就可以正常加载了,也可以在此基础上配置 UMD 模式,以在多次渲染时获得更好的性能和内存表现
// main.js
import {
createApp
}
from 'vue'import {
createRouter,
createWebHistory
}
from 'vue-router'import routes from './router'import App from './App.vue'
let app = null let router = null let history = null
// 👇 将渲染操作放入 mount 函数,子应用初始化时会自动执行
window.mount = () = >{
history = createWebHistory() router = createRouter({
history,
routes,
})
app = createApp(App) app.use(router) app.mount('#app')
}
// 👇 将卸载操作放入 unmount 函数,就是上面步骤2中的卸载函数
window.unmount = () = >{
app.unmount() history.destroy() app = null router = null history = null
}
// 如果不在微前端环境,则直接执行mount渲染
if (!window.__MICRO_APP_ENVIRONMENT__) {
window.mount()
}
访问子应用静态资源
可以直接通过子应用地址访问,micro-app 默认会对子应用相对地址的资源路径进行补全,以确保所有资源正常加载。
数据通信
- 子应用可以直接获取主应用初始下发的数据,可以用来初始化数据
<!-- 主应用 -->
<html>
<head></head>
<body>
<micro-app ="" name="sub-app-1" url="http://localhost:3002/" router-mode="native" baseroute="/sub-app-1/" iframe="" :data="data"></micro-app>
</body>
</html>
// 子应用 main.js
const data = window.microApp.getData();
- 通过 setData、dispatch Api 和 监听来实现数据
主应用发送数据:
// 返回值会放入数组中传递给setData的回调函数
microApp.setData('sub-app-name', {
city: 'HK'
},
(res: any[]) = >{
console.log(res) // ['返回值1', '返回值2']
})
主应用接收数据:
监听自定义事件:
// 在事件对象的detail.字段中,子应用每次发送都会触发change
<template>
<micro-app name='my-app' url='xx' @datachange='handleDataChange' / >
</template>
<script>
export default {
methods: {
handleDataChange (e) {
console.log('来自子应用的数据:', e.detail.data)
}
}
}
</script >
绑定监听函数
/**
* 绑定监听函数
* appName: 应用名称
* dataListener: 绑定函数
*/
microApp.addDataListener(appName: string, dataListener: (data: Object) = >any)
子应用发送数据
// 返回值会放入数组中传递给dispatch的回调函数
window.microApp.dispatch({
city: 'HK'
},
(res: any[]) = >{
console.log(res) // ['返回值1', '返回值2']
})
子应用接收数据
/**
* 绑定监听函数
* appName: 应用名称
* dataListener: 绑定函数
*/
window.microApp.addDataListener(dataListener: (data: Object) = >any)
路由跳转
主应用跳转子应用指定页面
/**
* @param {string} name 必填,子应用的name
* @param {string} path 必填,子应用除域名外的全量地址(也可以带上域名)
* @param {boolean} replace 可选,是否使用replace模式,不新增堆栈记录,默认为false
*/
router.push({
name: '子应用名称',
path: '页面地址',
replace: 是否使用replace模式
})
子应用跳转主应用指定页面
- 首先要在主应用执行
import microApp from '@micro-zoe/micro-app'
// 注册主应用路由
microApp.router.setBaseAppRouter(主应用的路由对象)
- 子应用中执行:
// 获取主应用路由
const baseRouter = window.microApp.router.getBaseAppRouter()
// 控制主应用跳转
baseRouter.主应用路由的方法 (...)
样式隔离
MicroApp的样式隔离是默认开启的,开启后会以标签作为样式作用域,利用标签的name属性为每个样式添加前缀,将子应用的样式影响禁锢在当前标签区域。

腾讯开源的微前端框架,采用 iframe 实现 js 沙箱,webcomponent 实现页面样式隔离
主应用中注册子应用
子应用配置
无界有三种运行模式:单例模式、保活模式、重建模式
其中保活模式、重建模式子应用无需做任何改造工作,单例模式需要做生命周期改造:
// main.js
const router = createRouter({
history: createWebHistory(),
routes,
});
if (window.__POWERED_BY_WUJIE__) {
let instance;
window.__WUJIE_MOUNT = () = >{
instance = createApp(App);
instance.use(router);
instance.mount("#app");
};
window.__WUJIE_UNMOUNT = () = >{
instance.unmount();
};
} else {
createApp(App).use(router).mount("#app");
}
访问子应用静态资源

通信
- 主应用可以通过props注入数据和方法,子应用可以通过$wujie来获取:
// 主应用
setupApp({
name: "sub-app-1",
url: "http://localhost:3004",
el: document.getElementById("sub-app-1"),
props: {
name: "job",
age: 20,
},
});
// 子应用
const props = window.$wujie ? .props;
- eventBus 通信
// 主应用
import {
bus
}
from "wujie";
// 主应用监听事件
bus.$on("事件名字",
function(arg1, arg2, ...) {});
// 主应用发送事件
bus.$emit("事件名字", arg1, arg2, ...);
// 主应用取消事件监听
bus.$off("事件名字",
function(arg1, arg2, ...) {});
// 子应用
// 子应用监听事件
window.$wujie ? .bus.$on("事件名字",
function(arg1, arg2, ...) {});
// 子应用发送事件
window.$wujie ? .bus.$emit("事件名字", arg1, arg2, ...);
// 子应用取消事件监听
window.$wujie ? .bus.$off("事件名字",
function(arg1, arg2, ...) {});
路由跳转
以 history 模式主应用路由为例:
在主应用中通过 props 向子应用传递路由跳转方法,子应用中可以读取调用:
// 主应用
const router = userRouter();
setupApp({
name: "sub-app-1",
url: "http://localhost:3004",
exec: true,
alive: false,
el: document.getElementById("sub-app-1"),
sync: true,
props: {
jump: (path) = >router.push(path),
},
});
// 子应用
// 跳转到子应用 B
function handleJump() {
window.$wujie ? .props.jump({
path: "/pathB"
});
}
// 跳转到子应用 B 的指定页面
function handleJump() {
window.$wujie ? .props.jump({
path: "/pathB",
query: {
B: "/test"
}
});
}
样式隔离
采用 webcomponent 来实现页面的样式隔离,无界会创建一个wujie自定义元素,然后将子应用的完整结构渲染在内部
