POST · 前端 · 文章

微前端学习笔记:qiankun / micro-app / 无界

2026/11/13 · by ZHANG XING YUN · 阅读 1 次 · 6350 字 · 预计阅读 16 分钟

本文覆盖微前端的适用场景、核心概念,以及 qiankun、micro-app、无界(wujie)三个方案的基础使用、渲染机制、沙箱、路由与通信,最后给出横向对比与选型建议。

  • 版本基线:qiankun 2.x、micro-app 1.x、无界 1.x(API 会随版本变化,落地前以官方文档为准)
  • 示例栈:Vue 3 + Vite / React 18 + Vite,主应用为 history 路由
  • 说明:三个方案都在快速演进,本文侧重"原理与用法共性",具体选项以对应版本文档为准

一、微前端是什么

微前端是把一个前端应用拆成多个可以独立开发、独立部署、运行时组合的子应用,再由一个主应用把它们拼在一起的架构方式。

它解决的问题:

问题 微前端的作用
单体应用过大,构建慢、发布互相阻塞 子应用独立构建、独立发布
多团队技术栈不同(Vue 2 / Vue 3 / React) 每个子应用可用自己的框架与版本
老系统无法一次性重写 新功能做成子应用,逐步替换旧模块
公共模块(登录、菜单、权限)重复实现 主应用统一承载,子应用复用

代价(必须提前评估):

  • 复杂度上升:多一套构建、部署、调试、监控流程。
  • 隔离不完美:JS 全局变量、样式、路由都可能互相影响,沙箱只能缓解。
  • 性能开销:多份运行时、重复依赖、跨应用通信成本。
  • 排障变难:问题可能出在主子应用交界处,需要统一的日志与错误上报。

不该用的场景:只有一个团队、一个仓库、发布节奏一致的小中型项目——单体或普通的多包仓库(monorepo)更简单。


二、方案全景

方案 隔离方式 接入成本 特点
iframe 浏览器原生(完全隔离) 低 隔离最彻底,但路由、弹窗、通信、体验都受限
single-spa 无沙箱,需自行处理 中 微前端底座,qiankun 基于它
qiankun JS 沙箱 + 样式隔离 中 生态成熟、社区资料多、约定较多
micro-app CustomElement + iframe 沙箱 低 使用方式接近写标签,支持 Vite
无界(wujie) iframe(JS)+ Shadow DOM(CSS) 低 隔离最彻底,支持保活与路由同步
Module Federation 无(模块共享) 中 构建期共享模块,不是完整微前端方案

Module Federation 解决的是"模块复用与联合构建",运行时的应用编排、路由、沙箱仍需自行处理,通常与上述方案组合使用。


三、通用概念

3.1 主应用与子应用

  • 主应用(基座):负责布局、登录、菜单、权限、路由分发、加载子应用。
  • 子应用:独立的项目与构建产物,对外暴露生命周期或被标签/组件挂载。

3.2 生命周期

几乎所有方案都要求子应用暴露三个生命周期(名称可能不同):

// 子应用入口(qiankun 约定)
export async function bootstrap() {}
export async function mount(props) {}
export async function unmount(props) {}
阶段 时机 典型操作
bootstrap 首次加载,只执行一次 初始化全局配置
mount 每次进入 挂载 DOM、启动路由
unmount 每次离开 卸载 DOM、清理定时器与监听

3.3 沙箱

沙箱解决两类冲突:

类型 冲突表现 隔离手段
JS 沙箱 子应用改 window,影响其它应用 Proxy 代理 window、快照还原、iframe 隔离
样式沙箱 全局 CSS 互相污染 scoped 前缀、Shadow DOM、iframe

3.4 路由

微前端的路由有两种模式:

  • 主应用管理路由(qiankun、micro-app 的 baseroute):主应用根据 URL 决定加载哪个子应用,子应用内部再用自己的 router。
  • 子应用接管路由(无界的 sync):子应用路由变化同步到主应用地址栏,刷新后可还原。

3.5 通信

方式 特点
props 传值 主 → 子,简单直接
全局状态 主 ↔ 子,适合登录态、主题、权限
事件总线 任意方向,松耦合,注意解绑
URL / 路由参数 刷新可恢复,适合页面级参数

四、qiankun

4.1 原理

qiankun 基于 single-spa,核心是 HTML Entry:

主应用调用 registerMicroApps
  → 根据 activeRule 匹配 URL
  → fetch 子应用 HTML(import-html-entry)
  → 解析出 script / style / template
  → 创建沙箱(Proxy window + 样式隔离)
  → 执行 script(拿到 bootstrap/mount/unmount)
  → mount 时把 template 插入容器

关键点:子应用不是 iframe,而是把 HTML 里的脚本和样式"取出来"在当前页面执行,因此需要沙箱来隔离。

4.2 主应用接入

// main.js
import { createApp } from "vue";
import { registerMicroApps, start } from "qiankun";
import App from "./App.vue";
import router from "./router";

createApp(App).use(router).mount("#app");

registerMicroApps([
	{
		name: "vue-app",
		entry: "//localhost:7101",
		container: "#subapp-container",
		activeRule: "/vue",
		props: { token: "xxx" }
	},
	{
		name: "react-app",
		entry: "//localhost:7102",
		container: "#subapp-container",
		activeRule: "/react"
	}
]);

start({
	prefetch: "all", // 预加载:all | true | false
	sandbox: {
		strictStyleIsolation: false, // Shadow DOM 隔离
		experimentalStyleIsolation: true // scoped 前缀隔离
	},
	singular: true // 同一时间只渲染一个子应用
});

registerMicroApps 的第二个参数是全局生命周期:

registerMicroApps(apps, {
	beforeLoad: [app => console.log("before load", app.name)],
	beforeMount: [app => console.log("before mount", app.name)],
	afterUnmount: [app => console.log("after unmount", app.name)]
});

手动加载(适合弹窗、局部嵌入):

import { loadMicroApp } from "qiankun";

const microApp = loadMicroApp({
	name: "vue-app",
	entry: "//localhost:7101",
	container: "#dialog-container",
	props: { mode: "dialog" }
});

// 卸载
await microApp.unmount();

4.3 子应用改造

// main.js
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";

let instance = null;

function render(props = {}) {
	const { container } = props;
	instance = createApp(App);
	instance.use(router);
	instance.mount(container ? container.querySelector("#app") : "#app");
}

// 独立运行时直接渲染
if (!window.__POWERED_BY_QIANKUN__) {
	render();
}

export async function bootstrap() {}

export async function mount(props) {
	render(props);
}

export async function unmount() {
	instance.unmount();
	instance = null;
}

配套修改:

  1. publicPath:资源必须能被主应用按绝对地址加载。
// vite.config.js
export default defineConfig({
	base: "/vue/", // 与 activeRule 对应
	server: { cors: true, headers: { "Access-Control-Allow-Origin": "*" } }
});
  1. 路由 base:子应用路由要带上前缀。
createRouter({
	history: createWebHistory(window.__POWERED_BY_QIANKUN__ ? "/vue" : "/"),
	routes
});
  1. CORS:主应用与子应用不同源时,子应用静态资源必须允许跨域(Access-Control-Allow-Origin)。

  2. Vite 限制:qiankun 依赖 UMD 与 eval 执行脚本,Vite 的 ESM 产物不能直接接入,需要 vite-plugin-qiankun 之类的插件,或改用 webpack 构建。

4.4 渲染

阶段 行为
加载 拉取 HTML,解析出 JS/CSS 与模板
执行 在沙箱中执行 JS,得到生命周期函数
挂载 把模板插入 container,调用 mount
卸载 调用 unmount,清理容器内容与沙箱

注意:qiankun 不会自动卸载子应用的全局副作用(定时器、window 监听、事件总线),必须在 unmount 里手动清理,否则重复进入会重复绑定。

4.5 沙箱

沙箱 说明 使用
proxySandbox 用 Proxy 代理 window 的读写,写操作不落到真实 window 2.x 默认
legacySandbox 记录变更,卸载时还原 兼容旧场景
snapshotSandbox 挂载前快照、卸载后还原 单实例、低版本行为
strictStyleIsolation Shadow DOM 隔离样式 需注意组件库弹窗挂到 body
experimentalStyleIsolation 给子应用样式加前缀 兼容性更好,但 :root、body 选择器仍会穿透

已知限制:

  • document.body 相关的样式(全局字体、body 背景)无法隔离。
  • 子应用往 document.body 追加的弹窗不受 Shadow DOM 约束。
  • 多个子应用同时挂载时,全局样式冲突仍可能发生。

4.6 路由

// 主应用:activeRule 决定何时加载
{ name: "vue-app", activeRule: "/vue" }
  • 子应用内部路由必须以 /vue 为前缀,且 createWebHistory('/vue')。
  • 主应用跳转子应用:router.push('/vue/detail')。
  • 子应用跳主应用:通过 props 传入主应用的路由实例,或在 unmount 后由主应用接管。
  • 刷新问题:子应用直接访问 /vue/detail 时,主应用的静态服务器需要把该路径回退到 index.html(history 模式通用问题)。

4.7 通信

方式一:props

// 主应用
registerMicroApps([{ name: "vue-app", entry, activeRule, props: { token, user } }]);

// 子应用 mount
export async function mount(props) {
	console.log(props.token, props.user);
}

方式二:全局状态 initGlobalState

// 主应用
import { initGlobalState, MicroAppStateActions } from "qiankun";

const actions = initGlobalState({ user: null, theme: "light" });
actions.onGlobalStateChange((state, prev) => {
	console.log("主应用收到变化", state, prev);
});
actions.setGlobalState({ user: { name: "张三" } });
// 子应用 mount 中接收
export async function mount(props) {
	props.onGlobalStateChange((state, prev) => {
		console.log("子应用收到", state);
	}, true); // true 表示立即执行一次
	props.setGlobalState({ theme: "dark" });
}

要点:

  • 全局状态是单向数据流:任何一方 setGlobalState 都会通知所有订阅者。
  • 不要在状态里放不可序列化对象(组件实例、DOM)。
  • 子应用卸载时记得取消监听(qiankun 会尝试清理,但自定义订阅需要自行处理)。

4.8 常见坑

现象 原因 处理
子应用资源 404 publicPath 未设置,仍按相对路径加载 设置 base/publicPath
白屏、控制台 CORS 报错 子应用未允许跨域 静态服务加 Access-Control-Allow-Origin
刷新子路由 404 history 模式下服务器未回退 配置 try_files 或 historyApiFallback
样式串台 样式隔离未开或穿透到 body 开 experimentalStyleIsolation,收敛全局样式
重复挂载/事件重复触发 unmount 未清理定时器与监听 在 unmount 里统一清理
Vite 子应用跑不起来 qiankun 不支持 ESM 产物 用插件或改 webpack
图标字体 404 字体路径相对,未走 publicPath 字体改用绝对地址或打包引入

五、micro-app

5.1 原理

micro-app 基于 CustomElement(Web Components):

主应用写 <micro-app name url baseroute>
  → 浏览器创建自定义元素并触发 connectedCallback
  → micro-app 拉取子应用 HTML/JS/CSS
  → 在 iframe 沙箱中执行 JS(提供独立 window)
  → 把 DOM 渲染到自定义元素内部
  → 路由变化时卸载/挂载

它与 qiankun 最大的差异是:接入方式是标签,主应用不需要为每个子应用写注册配置;沙箱默认用 iframe 提供独立 window,天然隔离更彻底。

5.2 主应用接入

npm i @micro-zoe/micro-app
// main.js
import microApp from "@micro-zoe/micro-app";

microApp.start({
	// 可选:预加载、插件、全局生命周期
	// preFetchApps: [{ name: "vue-app", url: "//localhost:7101/" }]
});
<!-- 页面中直接使用 -->
<micro-app
	name="vue-app"
	url="//localhost:7101/"
	baseroute="/vue"
	:data="dataForChild"
	@datachange="handleDataChange"
></micro-app>

常用属性:

属性 作用
name 子应用唯一标识,通信时使用
url 子应用地址(含协议与端口)
baseroute 子应用路由前缀,配合子应用 router base
data 主应用下发的数据(对象)
disableSandbox 关闭沙箱
shadowDOM 开启 Shadow DOM 样式隔离
clear-data 卸载时清空通信缓存
keep-alive 保活(保留 DOM 与状态)

5.3 子应用改造

micro-app 的要求比 qiankun 少,通常只需要:

  1. 处理路由 base:
const baseRoute = window.__MICRO_APP_BASE_ROUTE__ || "/";

createRouter({
	history: createWebHistory(baseRoute),
	routes
});
  1. 处理 publicPath(Vite):base 设置为子应用部署路径。

  2. 静态资源跨域:与 qiankun 相同。

  3. 生命周期(可选):不需要导出 bootstrap/mount/unmount,micro-app 会自动处理挂载;需要自定义时用 window.microApp 或元素事件。

5.4 渲染

阶段 行为
连接 <micro-app> 进入文档,触发元素生命周期
加载 拉取 HTML、解析资源、创建沙箱
渲染 在自定义元素内部生成 DOM,执行子应用脚本
卸载 移除 DOM,销毁沙箱

micro-app 的 DOM 直接在自定义元素内,没有 iframe 的 DOM 层级问题,弹窗、document.body 追加等行为比纯 iframe 方案自然。

5.5 沙箱

  • 默认:iframe 沙箱 + Proxy window。子应用的 JS 运行在 iframe 提供的独立环境中,通过 Proxy 映射到主应用的 DOM,从而既隔离 JS 又能正常渲染。
  • disableSandbox:关闭后子应用与主应用共享 window,性能略好但隔离消失(此时通信需要 EventCenterForMicroApp)。
  • 样式隔离:默认 scoped(给样式加前缀),可选 shadowDOM(更彻底,但组件库弹窗可能挂到 body 导致样式丢失)。

5.6 路由

<micro-app name="vue-app" url="//localhost:7101/" baseroute="/vue"></micro-app>
  • 主应用路由 /vue/* 时,baseroute 告诉子应用自己的前缀,子应用用 window.__MICRO_APP_BASE_ROUTE__ 初始化 router。
  • 主应用切换路由离开 /vue 时,micro-app 自动卸载子应用。
  • 子应用内部跳转由子应用自己的 router 完成,地址栏仍是主应用地址(history 模式下需要主应用路由支持该前缀)。

5.7 通信

主 → 子

import microApp from "@micro-zoe/micro-app";

// 主动下发(name 与 <micro-app> 的 name 一致)
microApp.setData("vue-app", { user: { name: "张三" } });

// 取子应用数据
const data = microApp.getData("vue-app");

子 → 主

// 子应用
window.microApp.dispatch({ type: "child-ready" });
// 主应用监听
microApp.addDataListener("vue-app", data => {
	console.log("来自子应用", data);
});

子应用接收主应用数据

const data = window.microApp.getData();

function listener(payload) {
	console.log("主应用下发", payload);
}
window.microApp.addDataListener(listener, true); // true:有缓存数据时立即触发

全局数据(跨应用)

microApp.setGlobalData({ theme: "dark" }); // 主应用
window.microApp.getGlobalData(); // 任意应用读取

要点:

  • setData / dispatch 的数据会缓存,卸载后仍保留,必要时用 clear-data 或 clearData 清理。
  • dispatch 只接受对象,且会做浅合并与去重(第一层 key 变化才发送)。
  • 数据是异步合并发送的,连续调用会在下一帧合并。

5.8 常见坑

现象 原因 处理
子应用路由错乱 未使用 baseroute / 未取 __MICRO_APP_BASE_ROUTE__ 统一 base 配置
样式丢失 开了 shadowDOM,组件库弹窗挂到 body 关掉 shadowDOM,或把弹窗容器挂到应用内
子应用 window 上挂载的库找不到 沙箱隔离后 window 不是全局 改用模块导入,或用 disableSandbox
通信数据不更新 只改了嵌套对象内部字段(浅比较) 传新对象,或用 forceSetData
卸载后数据还在 通信数据有缓存 加 clear-data

六、无界(wujie)

6.1 原理

无界用 iframe + Shadow DOM 组合,把"JS 运行环境"和"渲染位置"分开:

主应用渲染 <wujie-vue name url>
  → 创建同域 iframe,加载子应用 HTML 与 JS(独立 window/document/路由)
  → 子应用在 iframe 中执行,操作 document 时被 Proxy 拦截
  → 拦截到的 DOM 操作落到主应用的 Shadow DOM 中(用户看到的是主应用页面的一部分)

因此:

  • JS 隔离由 iframe 天然提供(独立 window、独立全局变量、独立路由)。
  • 样式隔离由 Shadow DOM 提供。
  • 子应用代码几乎不需要改造(不需要导出生命周期,也不需要改 publicPath)。

6.2 主应用接入

npm i wujie-vue3
// main.js
import { createApp } from "vue";
import WujieVue from "wujie-vue3";
import App from "./App.vue";

createApp(App).use(WujieVue).mount("#app");
<template>
	<wujie-vue
		name="vue-app"
		:url="'//localhost:7101/'"
		:alive="true"
		:sync="true"
		:props="{ token }"
		:before-load="onBeforeLoad"
		:after-mount="onAfterMount"
	/>
</template>

常用属性:

属性 作用
name 唯一标识,缓存与通信使用
url 子应用地址
alive 保活:切换走时不销毁,状态保留
sync 子应用路由同步到主应用地址栏
props 传给子应用的数据(window.$wujie.props)
fetch 自定义资源请求(可加 token、改请求头)
plugins 插件(如给子应用注入全局变量)
fiber 开启 fiber 模式(优化渲染性能,注意兼容性)
degrade 降级为纯 iframe 模式

生命周期:

<wujie-vue
	name="vue-app"
	:url="url"
	:before-load="() => console.log('beforeLoad')"
	:before-mount="() => console.log('beforeMount')"
	:after-mount="() => console.log('afterMount')"
	:before-unmount="() => console.log('beforeUnmount')"
	:after-unmount="() => console.log('afterUnmount')"
	:activated="() => console.log('activated')"
	:deactivated="() => console.log('deactivated')"
/>

6.3 渲染

阶段 行为
加载 在 iframe 中加载子应用 HTML,执行脚本
渲染 子应用的 DOM 通过代理渲染到 Shadow DOM
保活 alive 为 true 时,离开不销毁,仅隐藏
卸载 销毁 iframe 与 Shadow DOM

注意:保活(alive)会保留子应用状态,意味着子应用不会重新执行 mounted;需要在 activated 里刷新数据。

6.4 沙箱

隔离 手段 说明
JS iframe 独立 window、全局变量、事件循环
CSS Shadow DOM 样式作用域天然隔离
路由 iframe 内 history + sync 子应用路由独立,可同步到地址栏

优点:隔离最彻底,老项目几乎零改造。 限制:

  • iframe 内的弹窗、document.body、焦点管理需要无界的代理补齐,边界场景仍有差异。
  • iframe 会增加内存占用,多应用常驻时更明显。
  • sync 需要主子应用的路由配合,hash 模式下 query 处理有额外细节。

6.5 路由

sync 为 true 时,子应用路由变化会同步到主应用地址栏:

<wujie-vue name="vue-app" url="//localhost:7101/" :sync="true" />

跳转方式:

  • 主应用跳子应用:主应用 router.push('/vue/detail'),子应用根据 URL 同步。
  • 子应用跳主应用:主应用通过 props 传入 jump 函数,子应用调用。
// 主应用
const jump = location => router.push(location);

// 模板
<wujie-vue name="vue-app" :url="url" :props="{ jump }" />
// 子应用
window.$wujie?.props.jump({ path: "/order" });

已保活的应用再跳指定路由,需要通信配合:

// 子应用 A
window.$wujie?.bus.$emit("routeChange", "/test");

// 子应用 B
window.$wujie?.bus.$on("routeChange", path => router.push(path));

6.6 通信

方式 方向 用法
props 主 → 子 子应用读 window.$wujie.props
bus 任意 window.$wujie.bus.$emit/$on/$off
sync + URL 双向 路由参数同步
window.$wujie 其他能力 子 → 主 如 $wujie.props.jump、$wujie.location
// 主应用下发
<wujie-vue name="vue-app" :url="url" :props="{ user, theme }" />

// 子应用读取
const { user, theme } = window.$wujie.props;

// 事件总线(推荐用于跨应用通知)
window.$wujie.bus.$emit("user-updated", user);
window.$wujie.bus.$on("user-updated", user => {});
window.$wujie.bus.$off("user-updated", handler);

注意:bus 是全局的,多个子应用共享,事件名要加前缀(order:refresh),并在 unmount/deactivated 时解绑。

6.7 常见坑

现象 原因 处理
子应用弹窗位置错乱 弹窗挂到 iframe 的 body 使用无界的弹窗兼容方案,或把弹窗改为应用内渲染
刷新后路由丢失 sync 未开启或路由未还原 开 sync,确认子应用路由模式一致
保活后数据不刷新 alive 保留了旧状态 在 activated 中主动刷新
内存上涨 多个 iframe 常驻 减少 alive 应用数量,或及时销毁
静态资源 404 子应用 publicPath 不对 配置 base/publicPath 为部署绝对路径

七、三者对比

7.1 基础能力

维度 qiankun micro-app 无界(wujie)
底层方案 single-spa + HTML Entry CustomElement + iframe 沙箱 iframe + Shadow DOM
主应用接入 注册配置(registerMicroApps) 写标签(<micro-app>) 写组件(<wujie-vue>)
子应用改造量 较大(生命周期、publicPath、router base) 较小(router base、publicPath) 几乎为零
JS 隔离 Proxy 沙箱(默认) iframe + Proxy iframe(原生隔离)
样式隔离 scoped / Shadow DOM(可选) scoped / Shadow DOM(可选) Shadow DOM(默认)
路由接管 主应用主导(activeRule) 主应用主导(baseroute) 子应用主导(sync)
保活 需自行实现(loadMicroApp + 缓存 DOM) keep-alive alive
Vite 支持 需插件 原生支持 原生支持
预加载 内置 prefetch preFetchApps 需自行实现

7.2 通信能力

能力 qiankun micro-app 无界
主 → 子 props / setGlobalState data 属性 / setData props
子 → 主 setGlobalState dispatch / datachange bus / props 回调
跨应用广播 需自行封装 setGlobalData bus(全局)
数据持久化 无(状态在内存) 有缓存(需 clear-data 清理) 随 alive 保留
使用复杂度 中 低 低

7.3 优势与不足

qiankun

  • 优势:生态最成熟、文档与案例多;loadMicroApp 支持手动挂载;全局状态 API 清晰;社区插件丰富。
  • 不足:子应用改造量最大(生命周期、publicPath、路由 base、跨域都要处理);Vite 不原生支持;沙箱是"尽力隔离",全局样式与 body 级副作用仍会穿透;保活需要自己实现。

micro-app

  • 优势:接入方式最像写标签,改造成本低;默认 iframe 沙箱,隔离较强;原生支持 Vite;通信 API 完整(含全局数据与缓存机制);支持 Shadow DOM。
  • 不足:通信数据有缓存语义,需要理解合并与去重规则;shadowDOM 与组件库弹窗存在兼容问题;生态与社区资料少于 qiankun。

无界(wujie)

  • 优势:iframe + Shadow DOM,隔离最彻底、老项目几乎零改造;alive 保活与 sync 路由同步开箱即用;支持 Vite;插件机制可注入依赖。
  • 不足:iframe 带来内存与边界问题(弹窗、焦点、同域要求);多应用常驻时资源占用偏高;路由同步在 hash 模式下有额外处理;社区规模小于 qiankun。

7.4 场景对照

场景 推荐
新项目、团队已熟悉 qiankun、子应用可改造 qiankun
老项目(jQuery/Vue2)改造预算有限,要求隔离 无界
子应用多、技术栈杂、想少改代码,需要 Vite micro-app
只需要共享模块、不要求独立部署 Module Federation
简单嵌入第三方页面、无通信需求 iframe

实际落地常见组合:

  • qiankun + webpack:最稳的组合,资料最多。
  • micro-app / 无界 + Vite:新项目友好,接入快。
  • 主应用统一登录 + 权限,子应用只关心业务:三个方案都支持,关键是把鉴权放在主应用并通过 props/状态下发。

八、选型建议

决策顺序建议:

  1. 先问是否真的需要微前端:一个团队、发布节奏一致时,monorepo 或单体更划算。
  2. 看子应用能否改造:不能改(老系统、第三方)→ 无界;能改但不想大改 → micro-app;能接受改造 → qiankun。
  3. 看构建工具:Vite 为主 → micro-app / 无界;webpack 为主 → qiankun 也可。
  4. 看隔离要求:要求强隔离(多套组件库、样式冲突严重)→ 无界、micro-app(iframe 沙箱)。
  5. 看团队维护能力:需要长期自研插件、深度定制 → qiankun 社区资源多;希望少维护 → micro-app/无界开箱即用。

九、共性工程问题

9.1 公共依赖

多个子应用重复打包 Vue/React 会增大体积。做法:

  • 主应用通过 externals 或 import maps 提供公共库,子应用不打包。
  • 或接受重复(隔离优先),用 HTTP 缓存抵消。

注意:共享同一份运行时(同一 Vue 实例)会带来状态与版本耦合,隔离优先时不要强行共享。

9.2 部署

项 建议
域名 主子应用同域(/vue、/react)可减少 CORS 与 cookie 问题
静态资源 子应用使用绝对地址(base/publicPath),不要依赖相对路径
缓存 子应用入口 HTML 用 no-cache,带 hash 的资源长缓存
灰度 子应用按版本部署,主应用按用户/环境决定加载哪个地址

9.3 版本与发布

  • 主应用记录子应用版本(如 manifest.json),避免"主应用改了但子应用没同步"。
  • 子应用接口变更要向后兼容,或主应用按版本下发配置。
  • 发布顺序:先发子应用(向后兼容),再发主应用。

9.4 监控与降级

  • 统一错误上报:主子应用使用同一套 SDK,带上 appName 标记。
  • 子应用加载失败要有兜底 UI 与重试,不要让整个页面白屏。
  • 关键子应用准备降级页(静态提示 + 跳转)。

9.5 样式冲突排查步骤

  1. 确认隔离方式(scoped / Shadow DOM / 无)。
  2. 用 DevTools 看冲突元素的计算样式来源。
  3. 检查是否有全局选择器(*、body、:root、组件库 reset)。
  4. 临时开启强隔离验证;确认后再决定是收敛全局样式还是换隔离方案。

十、迁移路径

从单体到微前端,推荐渐进式:

  1. 拆公共层:登录、权限、布局、菜单先抽到主应用。
  2. 选一个低风险模块做第一个子应用(如报表、帮助中心)。
  3. 打通链路:路由、通信、部署、监控。
  4. 逐步迁移:每次迁一个模块,旧模块保留在主应用,直到清空。
  5. 收尾:统一构建规范、依赖版本、错误上报。

从 iframe 迁到框架方案:

  • 先保留 iframe 作为降级路径,新模块用框架接入。
  • 逐步替换 iframe 中的老页面,注意 cookie、跨域、弹窗兼容。

十一、FAQ

Q1:微前端能解决样式冲突吗?

只能缓解。scoped 与 Shadow DOM 都能隔离大部分样式,但 body、:root、全局 reset、挂到 body 的弹窗仍会穿透。彻底隔离要用 iframe(无界、micro-app 的默认沙箱)。

Q2:qiankun 为什么要求子应用改 publicPath?

因为子应用的资源是从主应用页面加载的,相对路径会以主应用地址为基准,必须改成绝对路径或加前缀。

Q3:Vite 项目能接 qiankun 吗?

不原生支持(qiankun 依赖 UMD 与 eval 执行),需要 vite-plugin-qiankun 等社区方案,或用 webpack 构建子应用。

Q4:micro-app 和 qiankun 最大的区别?

接入方式与沙箱:micro-app 用标签 + iframe 沙箱,改造成本低;qiankun 用注册配置 + Proxy 沙箱,生态更成熟。

Q5:无界的 iframe 会不会很慢?

首次加载有 iframe 初始化成本,但可以预加载、保活;多应用常驻时内存占用明显,需要控制 alive 数量。

Q6:子应用之间能直接通信吗?

qiankun 需借助主应用的全局状态;micro-app 用 setGlobalData;无界用全局 bus。都建议通过主应用中转,避免子应用互相耦合。

Q7:刷新页面后子应用路由丢失怎么办?

qiankun 靠主应用路由 + 服务器回退;micro-app 靠 baseroute + __MICRO_APP_BASE_ROUTE__;无界开 sync 同步路由到地址栏。

Q8:需要保活吗?

看场景:频繁切换的列表页建议保活;表单页保活后要处理"数据残留"(在 activated 里重置)。保活会占内存,不宜过多。

Q9:子应用能独立运行吗?

应该可以。三个方案都要求子应用"单独启动也能跑"(qiankun 用 __POWERED_BY_QIANKUN__ 判断),否则调试成本很高。

Q10:微前端和 iframe 的本质区别?

iframe 是完全独立的文档,隔离彻底但通信与体验受限;微前端方案让子应用与主应用共享同一个文档(或部分 DOM),体验更自然,但需要沙箱来弥补隔离。

← 返回帖子 更多前端内容 →