文档版本: v1.0 | 更新日期: 2026-10-26
> 本文档覆盖 6 种主流微前端方案,每套方案均包含可运行案例代码。
| 方案 | 出品方 | 核心技术 | Star⭐ | 最新版本 | 适用场景 |
|---|---|---|---|---|---|
| qiankun | 阿里 | single-spa + Proxy沙箱 | 15k+ | 2.10+ | 中大型项目,多框架 |
| Micro-App | 京东 | Web Component | 5k+ | 1.0+ | 轻量级,Vue友好 |
| Wujie(无界) | 腾讯 | iframe + Web Component | 3k+ | 1.0+ | 高性能要求 |
| Module Federation | Webpack官方 | Webpack5 MF | - | 5.x | 同技术栈 |
| single-spa | 社区 | 路由分发 | 13k+ | 6.x | 多框架集成 |
| iframe + postMessage | 原生 | iframe | - | - | 完全隔离 |
┌─────────────────────────────────────────────────────────────────────────────────┐
│ 微前端架构原理全景图 │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ qiankun │ │ Micro-App │ │ Wujie │ │ MF │ │
│ ├─────────────┤ ├─────────────┤ ├─────────────┤ ├─────────────┤ │
│ │ single-spa │ │ Web Compon. │ │iframe+WC │ │ Webpack5 │ │
│ │ + Proxy沙箱 │ │ + 沙箱隔离 │ │ + 预加载 │ │ Runtime共享 │ │
│ │ + 样式隔离 │ │ + CSS隔离 │ │ + 最佳性能 │ │ + 类型安全 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │ │ │ │ │
│ ▼ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 基座应用 (Main App) │ │
│ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │
│ │ │ 注册子应用 │ │ 路由分发 │ │ 沙箱隔离 │ │ 通信管理 │ │ │
│ │ └────────────┘ └────────────┘ └────────────┘ └────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │ │ │ │ │
│ ▼ ▼ ▼ ▼ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 子应用 A │ │ 子应用 B │ │ 子应用 C │ │ 子应用 D │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────────────────────────────┘
// qiankun-demo/main-app/src/main.ts
import { registerMicroApps, start, initGlobalState } from 'qiankun';
import type { MicroAppStateActions } from 'qiankun';
// ==================== 全局状态管理 ====================
const state = {
user: { id: 'u_001', name: '张三', roles: ['admin'], permissions: ['read', 'write'] },
theme: 'light',
locale: 'zh-CN',
collapsed: false,
notifications: 3,
};
// 初始化全局状态
const actions: MicroAppStateActions = initGlobalState(state);
// 监听全局状态变化
actions.onGlobalStateChange((state, prev) => {
console.log('[基座] 状态变化:', prev, '->', state);
});
// ==================== 注册子应用 ====================
registerMicroApps(
[
{
name: 'sub-vue',
entry: '//localhost:9001',
container: '#subapp-container',
activeRule: '/sub-vue',
props: {
// 方式一:Props 直接注入
userInfo: state.user,
utils: {
formatDate: (date: Date) => date.toLocaleString('zh-CN'),
request: (url: string, options: any) => fetch(url, options).then(r => r.json()),
},
callbacks: {
onLogin: () => console.log('[基座] 子应用触发登录'),
onNavigate: (path: string) => router.push(path),
},
// 方式二:提供全局状态操作对象
actions,
},
},
{
name: 'sub-react',
entry: '//localhost:9002',
container: '#subapp-container',
activeRule: '/sub-react',
props: { userInfo: state.user, actions },
},
],
// 生命周期钩子
{
beforeLoad: [(app) => {
document.getElementById('loading')!.style.display = 'flex';
return Promise.resolve();
}],
afterMount: [(app) => {
document.getElementById('loading')!.style.display = 'none';
return Promise.resolve();
}],
afterUnmount: [(app) => {
console.log('[qiankun] afterUnmount', app.name);
return Promise.resolve();
}],
}
);
// 启动(关键配置)
start({
prefetch: 'all', // 预加载策略
sandbox: {
experimentalStyleIsolation: true, // 实验性样式隔离(推荐)
// strictStyleIsolation: true, // 严格样式隔离(Shadow DOM,兼容性要求高)
},
});
子应用配置(Vue):
// qiankun-demo/sub-vue/src/main.ts
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
import type { App as VueApp } from 'vue';
let app: VueApp | null = null;
// ==================== 渲染函数 ====================
function render(props: any = {}) {
const { container } = props;
app = createApp(App);
app.use(router);
// 挂载到 qiankun 容器或独立挂载
const mountEl = container
? container.querySelector('#subvue-root')
: '#subvue-root';
app.mount(mountEl);
}
// ==================== 独立运行 ====================
if (!(window as any).__POWERED_BY_QIANKUN__) {
render({});
}
// ==================== qiankun 生命周期 ====================
export async function bootstrap() {
console.log('[Vue 子应用] bootstrap');
}
export async function mount(props: any) {
// 1. 接收基座 props
const { userInfo, actions } = props;
// 2. 监听全局状态变化
actions.onGlobalStateChange((state: any) => {
if (state.user) store.setUser(state.user);
if (state.theme) document.documentElement.setAttribute('data-theme', state.theme);
}, true);
// 3. 渲染
render(props);
}
export async function unmount() {
app?.unmount();
app = null;
}
子应用 vite.config.ts(关键配置):
// qiankun-demo/sub-vue/vite.config.ts
export default defineConfig({
base: '/sub-vue', // 必须与 activeRule 匹配
server: {
port: 9001,
cors: true,
headers: {
'Access-Control-Allow-Origin': '*', // 重要:允许跨域
},
},
configureWebpack: {
output: {
library: `qiankun-sub-vue-[name]`,
libraryTarget: 'umd', // qiankun 需要 UMD 格式
chunkLoadingGlobal: `webpackJsonp_qiankun_sub_vue`,
},
},
});
子应用使用基座数据:
<!-- 子应用 Vue 组件中 -->
<template>
<div>
<h2>用户信息(来自基座)</h2>
<p>名称:{{ userInfo.name }}</p>
<p>角色:{{ userInfo.roles.join(', ') }}</p>
<button @click="updateMainState">修改基座状态</button>
<button @click="toggleTheme">切换主题</button>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
// 从全局获取 props
const props = (window as any).__QIANKUN_PROPS__;
const userInfo = ref(props.userInfo);
// 修改基座状态
const updateMainState = () => {
props.actions.setGlobalState({
user: { ...userInfo.value, name: '新名称' }
});
};
const toggleTheme = () => {
const newTheme = props.actions.getGlobalState().theme === 'light' ? 'dark' : 'light';
props.actions.setGlobalState({ theme: newTheme });
};
</script>
┌─────────────────────────────────────────────────────────────────┐
│ qiankun 通信机制 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ 基座 │ │ 子应用 │ │
│ │ │ │ │ │
│ │ initGlobalState │◄────────►│ props.actions │ │
│ │ (state, actions)│ props │ .onGlobalState │ │
│ │ │ 传递 │ Change() │ │
│ │ actions.set │─────────►│ │ │
│ │ GlobalState() │ 状态变更 │ actions.get │ │
│ │ │ 通知 │ GlobalState() │ │
│ └──────────────────┘ └──────────────────┘ │
│ │
│ 通信方式: │
│ ├── 1. props 注入(初始化传参) │
│ ├── 2. initGlobalState(全局状态管理) │
│ ├── 3. 通过 props 传递回调函数 │
│ └── 4. 自定义 EventBus(额外方案) │
└─────────────────────────────────────────────────────────────────┘
<micro-app>)// micro-app-demo/main-app/src/main.ts
import microApp from '@micro-zoe/micro-app';
microApp.start({
// 全局配置
'router-mode': 'state',
'disable-sandbox': false, // JS 沙箱
'disable-scopecss': false, // CSS 隔离
'keep-alive': true, // 缓存
// 全局生命周期
lifeCycles: {
created() { console.log('[micro-app] created'); },
beforemount() {
document.getElementById('loading')!.style.display = 'flex';
},
mounted() {
document.getElementById('loading')!.style.display = 'none';
},
unmount() { console.log('[micro-app] unmount'); },
error() {
document.getElementById('loading')!.style.display = 'none';
},
},
// 自定义 fetch
fetch: (url: string, options: any) => {
return window.fetch(url, options).then(res => res.text());
},
});
基座模板中使用:
<!-- 使用 micro-app 标签 -->
<micro-app
name="sub-vue"
url="http://localhost:9101/"
:data="microData"
base-route="/sub-vue"
keep-alive
@created="handleCreated"
@mounted="handleMounted"
@unmount="handleUnmount"
@datachange="handleDataChange"
></micro-app>
// 基座 Vue 组件
import { ref } from 'vue';
import microApp from '@micro-zoe/micro-app';
const microData = ref({
user: { id: 'u_001', name: '张三' },
theme: 'light',
router: null, // 可以传递路由对象
});
// 向子应用发送数据
const sendData = () => {
microData.value = { ...microData.value, message: '来自基座的数据' };
};
// 或者使用 micro-app 的 API 发送
const sendDataByAPI = () => {
microApp.setData('sub-vue', { type: 'update', data: { count: 1 } });
};
// 接收子应用数据
const handleDataChange = (e: CustomEvent) => {
console.log('子应用发送的数据:', e.detail.data);
};
// 获取子应用数据(异步)
const getData = async () => {
const data = await microApp.getData('sub-vue');
console.log('子应用当前数据:', data);
};
子应用接收和发送数据:
// micro-app-demo/sub-vue/src/main.ts
import microApp from '@micro-zoe/micro-app';
// 获取基座数据
const baseData = microApp.getData();
console.log('基座数据:', baseData);
// 监听基座数据变化
microApp.addDataListener((data: any) => {
console.log('基座数据变化:', data);
// 响应数据变化
});
// 向基座发送数据
microApp.dispatch({ type: 'sub-ready', data: { appName: 'sub-vue' } });
// 解除监听
// microApp.removeDataListener(listener);
┌─────────────────────────────────────────────────────────────────┐
│ Micro-App 通信机制 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 方式一:data 属性绑定(响应式) │
│ ┌────────────┐ :data="microData" ┌────────────┐ │
│ │ 基座 │ ──────────────────► │ 子应用 │ │
│ │ │ addDataListener() │ │ │
│ └────────────┘ ◄────────────────── └────────────┘ │
│ │
│ 方式二:API 主动通信 │
│ ┌────────────┐ microApp.setData() ┌────────────┐ │
│ │ 基座 │ ──────────────────► │ 子应用 │ │
│ │ │ dispatch() │ │ │
│ └────────────┘ ◄────────────────── └────────────┘ │
│ │
│ 方式三:URL 携带参数 │
│ ┌────────────┐ url="**" ┌────────────┐ │
│ │ 基座 │ ──────────────────► │ 子应用 │ │
│ └────────────┘ └────────────┘ │
└─────────────────────────────────────────────────────────────────┘
// wujie-demo/main-app/src/main.ts
import WujieVue from 'wujie-vue3';
const { bus, setupApp, preloadApp, destroyApp } = WujieVue;
// 注册插件
app.use(WujieVue);
// 设置全局事件总线
bus.$on('event-from-sub', (data: any) => {
console.log('子应用事件:', data);
});
// 预加载子应用(启动时执行)
setupApp({
name: 'sub-vue',
url: 'http://localhost:9201/',
exec: true,
// 通信数据
props: {
userInfo: { id: 'u_001', name: '张三' },
theme: 'light',
},
});
// 预加载(空闲时)
preloadApp({ name: 'sub-vue' });
基座组件中使用:
<template>
<div class="wujie-container">
<!-- WujieVue 组件 -->
<WujieVue
name="sub-vue"
:url="subUrl"
:props="wujieProps"
:sync="true"
:before-load="beforeLoad"
:before-mount="beforeMount"
:after-mount="afterMount"
:before-unmount="beforeUnmount"
:after-unmount="afterUnmount"
:load-error="loadError"
/>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { bus } from 'wujie-vue3';
const subUrl = ref('http://localhost:9201/');
const wujieProps = ref({
userInfo: { id: 'u_001', name: '张三', roles: ['admin'] },
theme: 'light',
utils: {
formatDate: (date: Date) => date.toLocaleString(),
},
});
// 向子应用发送事件
const emitToSub = () => {
bus.$emit('event-from-main', { type: 'theme-change', value: 'dark' });
};
// 生命周期
const beforeLoad = () => console.log('beforeLoad');
const beforeMount = (appWindow: Window) => console.log('beforeMount');
const afterMount = (appWindow: Window) => console.log('afterMount');
const beforeUnmount = (appWindow: Window) => console.log('beforeUnmount');
const afterUnmount = (appWindow: Window) => console.log('afterUnmount');
const loadError = (url: string, error: Error) => console.log('loadError', url, error);
</script>
子应用中使用:
// wujie-demo/sub-vue/src/main.ts
import { bus } from 'wujie-vue3';
// 获取基座传递的 props
import { getCurrentInstance } from 'vue';
const props = getCurrentInstance()?.appContext.config.globalProperties.$wujie?.props;
console.log('基座传递的 props:', props);
// 监听基座事件
bus.$on('event-from-main', (data: any) => {
console.log('基座事件:', data);
if (data.type === 'theme-change') {
document.documentElement.setAttribute('data-theme', data.value);
}
});
// 向基座发送事件
bus.$emit('event-from-sub', { type: 'sub-ready' });
┌─────────────────────────────────────────────────────────────────┐
│ Wujie 通信机制 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ 基座 (Host) │ │ 子应用 │ │
│ │ │ iframe 沙箱 │ │ │
│ │ bus.$emit() │ ─────────────────► │ bus.$on() │ │
│ │ │ props 注入 │ │ │
│ │ bus.$on() │ ◄──────────────── │ bus.$emit() │ │
│ └──────────────┘ └──────────────┘ │
│ │
│ 通信方式: │
│ ├── 1. props 注入(初始化) │
│ ├── 2. bus 事件总线(双向通信) │
│ ├── 3. window.$wujie.props(子应用获取) │
│ └── 4. iframe 共享存储(localStorage/cookie) │
│ │
│ 核心优势: │
│ ├── iframe 天然 JS 隔离 │
│ ├── 预加载实现无感切换 │
│ └── 完备的异常边界处理 │
└─────────────────────────────────────────────────────────────────┘
// module-federation-demo/remote/webpack.config.js
const { ModuleFederationPlugin } = require('webpack').container;
module.exports = {
mode: 'development',
devServer: {
port: 9300,
cors: true,
},
output: {
publicPath: 'http://localhost:9300/',
},
plugins: [
new ModuleFederationPlugin({
// 远程应用名称
name: 'remote_app',
// 导出的入口文件名
filename: 'remoteEntry.js',
// 暴露的模块
exposes: {
// 暴露路由配置
'./routes': './src/routes.ts',
// 暴露组件
'./UserList': './src/components/UserList.tsx',
// 暴露页面
'./Dashboard': './src/pages/Dashboard.tsx',
// 暴露工具函数
'./utils': './src/utils/index.ts',
// 暴露 store
'./store': './src/store/index.ts',
},
// 共享依赖
shared: {
react: {
singleton: true, // 单例模式
requiredVersion: '^18.2.0',
eager: true, // 立即加载
},
'react-dom': {
singleton: true,
requiredVersion: '^18.2.0',
},
// Vue 项目
vue: {
singleton: true,
requiredVersion: '^3.4.0',
},
// 通用库
axios: { singleton: true },
dayjs: { singleton: true },
},
}),
],
};
远程应用入口:
// module-federation-demo/remote/src/index.ts
// 暴露异步边界,让远程模块能正常工作
import('./bootstrap');
// module-federation-demo/remote/src/bootstrap.ts
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root')!);
root.render(React.createElement(App));
主应用配置(host):
// module-federation-demo/host/webpack.config.js
const { ModuleFederationPlugin } = require('webpack').container;
module.exports = {
mode: 'development',
devServer: {
port: 9301,
cors: true,
},
plugins: [
new ModuleFederationPlugin({
name: 'host_app',
// 引用远程应用
remotes: {
// 远程应用别名: '远程应用名称@远程入口URL'
remote_app: 'remote_app@http://localhost:9300/remoteEntry.js',
},
// 共享依赖(必须与 remote 配置一致)
shared: {
react: { singleton: true, requiredVersion: '^18.2.0' },
'react-dom': { singleton: true, requiredVersion: '^18.2.0' },
vue: { singleton: true, requiredVersion: '^3.4.0' },
axios: { singleton: true },
dayjs: { singleton: true },
},
}),
],
};
主应用中使用远程模块:
// module-federation-demo/host/src/App.tsx
import React, { Suspense, lazy } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
// ==================== 方式一:同步导入 ====================
import { UserList, utils } from 'remote_app/UserList';
// ==================== 方式二:异步导入(推荐) ====================
const Dashboard = lazy(() => import('remote_app/Dashboard'));
const RemoteRoutes = lazy(() => import('remote_app/routes'));
const RemoteStore = lazy(() => import('remote_app/store'));
function App() {
return (
<BrowserRouter>
<Suspense fallback={<div>Loading remote module...</div>}>
<Routes>
{/* 主应用路由 */}
<Route path="/" element={<Home />} />
{/* 远程应用路由(直接渲染远程组件) */}
<Route path="/users" element={<UserList />} />
<Route path="/dashboard" element={<Dashboard />} />
{/* 远程应用路由(使用远程路由配置) */}
<Route path="/remote/*" element={
<Suspense fallback={<div>Loading...</div>}>
<RemoteRoutes />
</Suspense>
} />
</Routes>
</Suspense>
</BrowserRouter>
);
}
// ==================== 使用远程工具函数 ====================
const { formatDate, request } = utils;
const today = formatDate(new Date());
通信方式:
// ==================== 方式一:共享 Store ====================
// 远程应用暴露 store
// remote/src/store/index.ts
import { createSlice } from '@reduxjs/toolkit';
export const userSlice = createSlice({
name: 'user',
initialState: { user: null, theme: 'light' },
reducers: {
setUser: (state, action) => { state.user = action.payload; },
setTheme: (state, action) => { state.theme = action.payload; },
},
});
// host 中直接使用
import { store } from 'remote_app/store';
store.dispatch(setUser({ name: '张三' }));
// ==================== 方式二:共享事件总线 ====================
// shared/event-bus.ts
class EventBus {
private listeners: Map<string, Function[]> = new Map();
on(event: string, callback: Function) {
if (!this.listeners.has(event)) this.listeners.set(event, []);
this.listeners.get(event)!.push(callback);
}
emit(event: string, data?: any) {
this.listeners.get(event)?.forEach(cb => cb(data));
}
}
export const bus = new EventBus();
// host 和 remote 共享同一个 bus 实例
// host
import { bus } from 'shared/event-bus';
bus.emit('user:login', { name: '张三' });
// remote
import { bus } from 'shared/event-bus';
bus.on('user:login', (user) => console.log('用户登录:', user));
┌─────────────────────────────────────────────────────────────────┐
│ Module Federation 通信机制 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ Host (主应用) │ │ Remote (远程) │ │
│ │ │ │ │ │
│ │ import('remote │ ──运行时──► │ exposes: { │ │
│ │ _app/Dashboard')│ 动态加载 │ './Dashboard' │ │
│ │ │ │ } │ │
│ │ shared: { │ │ shared: { │ │
│ │ react: 单例 │ ◄──共享────► │ react: 单例 │ │
│ │ } │ │ } │ │
│ └──────────────────┘ └──────────────────┘ │
│ │
│ 通信方式: │
│ ├── 1. 直接导入远程模块(组件/函数/store) │
│ ├── 2. shared 共享依赖(单例模式) │
│ ├── 3. 共享事件总线 │
│ └── 4. 共享状态管理(Redux/Pinia) │
│ │
│ 核心优势: │
│ ├── 编译时类型安全(TypeScript) │
│ ├── 运行时动态加载 │
│ └── 共享依赖减少重复打包 │
└─────────────────────────────────────────────────────────────────┘
// single-spa 基座
import { registerApplication, start } from 'single-spa';
// ==================== 注册子应用 ====================
registerApplication({
name: 'sub-vue',
// 加载子应用
app: () => System.import('//localhost:9401/sub-vue.js'),
// 激活条件
activeWhen: '/sub-vue',
// 传递给子应用的自定义 props
customProps: {
userInfo: { id: 'u_001', name: '张三' },
domElement: document.getElementById('subapp-container'),
},
});
registerApplication({
name: 'sub-react',
app: () => System.import('//localhost:9402/sub-react.js'),
activeWhen: '/sub-react',
customProps: {
userInfo: { id: 'u_001', name: '张三' },
},
});
// 启动
start({
// 是否允许没有活动应用时 URL 改变
urlRerouteOnly: true,
});
子应用配置:
// single-spa 子应用
import { registerApplication, start } from 'single-spa';
import { render, setPublicPath } from 'systemjs-interop';
// 设置 public path
__webpack_public_path__ = 'http://localhost:9401/';
// 注册
singleSpa.registerApplication('sub-vue',
() => import('./main'),
(location) => location.pathname.startsWith('/sub-vue')
);
// iframe-demo/main-app/src/main.ts
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');
// ==================== iframe 通信管理器 ====================
class IframeManager {
private iframes: Map<string, HTMLIFrameElement> = new Map();
private listeners: Map<string, Function[]> = new Map();
private pendingRequests: Map<string, { resolve: Function; reject: Function }> = new Map();
// 注册 iframe
register(name: string, iframe: HTMLIFrameElement) {
this.iframes.set(name, iframe);
// 监听子应用消息
window.addEventListener('message', (event) => {
// 安全校验
if (!this.isTrustedOrigin(event.origin)) return;
const { type, payload, requestId } = event.data;
// 处理响应
if (requestId && this.pendingRequests.has(requestId)) {
const { resolve } = this.pendingRequests.get(requestId)!;
resolve(payload);
this.pendingRequests.delete(requestId);
return;
}
// 处理事件
this.listeners.get(type)?.forEach(cb => cb(payload));
});
}
// 发送消息到子应用
send(name: string, type: string, payload?: any) {
const iframe = this.iframes.get(name);
if (!iframe?.contentWindow) return;
iframe.contentWindow.postMessage({ type, payload }, '*');
}
// 请求-响应模式
request<T = any>(name: string, type: string, payload?: any, timeout = 5000): Promise<T> {
return new Promise((resolve, reject) => {
const requestId = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
const timer = setTimeout(() => {
this.pendingRequests.delete(requestId);
reject(new Error(`请求超时: ${type}`));
}, timeout);
this.pendingRequests.set(requestId, {
resolve: (data: T) => { clearTimeout(timer); resolve(data); },
reject: (err: Error) => { clearTimeout(timer); reject(err); },
});
const iframe = this.iframes.get(name);
iframe?.contentWindow?.postMessage({ type, payload, requestId }, '*');
});
}
// 监听子应用事件
on(type: string, callback: Function) {
if (!this.listeners.has(type)) this.listeners.set(type, []);
this.listeners.get(type)!.push(callback);
}
private isTrustedOrigin(origin: string): boolean {
const trustedOrigins = ['http://localhost:9501', 'http://localhost:9502'];
return trustedOrigins.includes(origin);
}
}
export const iframeManager = new IframeManager();
基座组件:
<template>
<div class="iframe-container">
<iframe
ref="subIframe"
src="http://localhost:9501/"
@load="handleIframeLoad"
></iframe>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { iframeManager } from './main';
const subIframe = ref<HTMLIFrameElement>();
onMounted(() => {
if (subIframe.value) {
iframeManager.register('sub-app', subIframe.value);
// 监听子应用事件
iframeManager.on('sub-ready', (data) => {
console.log('子应用就绪:', data);
});
iframeManager.on('sub-data', (data) => {
console.log('子应用数据:', data);
});
}
});
const handleIframeLoad = () => {
// 向子应用发送初始数据
iframeManager.send('sub-app', 'init', {
user: { id: 'u_001', name: '张三' },
theme: 'light',
});
};
// 请求子应用数据
const requestSubData = async () => {
const data = await iframeManager.request('sub-app', 'get-data', { type: 'user' });
console.log('子应用返回数据:', data);
};
</script>
子应用端:
// iframe-demo/sub-app/src/main.ts
class SubAppBridge {
private listeners: Map<string, Function[]> = new Map();
private parentOrigin = '*'; // 生产环境应指定具体域名
constructor() {
window.addEventListener('message', this.handleMessage.bind(this));
}
// 处理基座消息
private handleMessage(event: MessageEvent) {
const { type, payload, requestId } = event.data;
switch (type) {
case 'init':
// 初始化数据
console.log('收到基座初始化数据:', payload);
this.handleInit(payload);
break;
case 'get-data':
// 处理数据请求
const response = this.handleGetData(payload);
// 发送响应
window.parent.postMessage({
type: 'response',
payload: response,
requestId,
}, this.parentOrigin);
break;
default:
this.listeners.get(type)?.forEach(cb => cb(payload));
}
}
// 向基座发送消息
send(type: string, payload?: any) {
window.parent.postMessage({ type, payload }, this.parentOrigin);
}
// 监听基座事件
on(type: string, callback: Function) {
if (!this.listeners.has(type)) this.listeners.set(type, []);
this.listeners.get(type)!.push(callback);
}
private handleInit(data: any) {
// 应用初始化数据
document.documentElement.setAttribute('data-theme', data.theme);
this.send('sub-ready', { appName: 'sub-app', timestamp: Date.now() });
}
private handleGetData(request: any) {
// 返回请求的数据
return { users: [], total: 100 };
}
}
export const bridge = new SubAppBridge();
| 方式 | qiankun | Micro-App | Wujie | MF | iframe |
|---|---|---|---|---|---|
| props 注入 | ✅ | ✅ | ✅ | ❌ | ❌ |
| 全局状态 | ✅ initGlobalState | ✅ data | ✅ props | ✅ shared | ❌ |
| URL 参数 | ✅ | ✅ | ✅ | ✅ | ✅ |
| 回调函数 | ✅ | ✅ | ✅ | ✅ | ✅ |
| 事件总线 | ✅ | ✅ | ✅ bus | ✅ | ✅ postMessage |
| 请求-响应 | ✅ | ✅ | ✅ | ✅ | ✅ |
// ==================== 推荐:分层传参策略 ====================
// 第1层:初始化 Props(基座 → 子应用,一次性)
registerMicroApps([{
name: 'sub-app',
props: {
// 基础数据
userInfo: { id: 'u_001', name: '张三' },
config: { theme: 'light', locale: 'zh-CN' },
// 工具方法
utils: { formatDate, request, encrypt },
// 回调函数
onNavigate: (path: string) => router.push(path),
onError: (err: Error) => Sentry.captureException(err),
},
}]);
// 第2层:全局状态(双向同步)
const actions = initGlobalState(state);
actions.onGlobalStateChange((state, prev) => {
// 响应状态变化
});
actions.setGlobalState({ theme: 'dark' });
// 第3层:事件通信(实时交互)
bus.on('user:login', (user) => { /* 处理登录 */ });
bus.emit('app:ready', { appName: 'sub-app' });
// 第4层:请求-响应(数据获取)
const data = await bus.request('fetch:user', { id: 'u_001' });
// ==================== qiankun:全局状态监听 ====================
// 基座
const actions = initGlobalState(state);
actions.onGlobalStateChange((state, prev) => {
console.log('状态变化:', prev, '->', state);
}, true); // true = 立即执行一次
// 子应用
props.actions.onGlobalStateChange((state) => {
if (state.user) store.setUser(state.user);
if (state.theme) applyTheme(state.theme);
}, true);
// ==================== Micro-App:数据监听 ====================
// 基座
microApp.setData('sub-vue', { type: 'update', data: newData });
// 子应用
microApp.addDataListener((data: any) => {
if (data.type === 'update') {
store.update(data.data);
}
});
// ==================== Wujie:事件总线 ====================
// 基座
bus.$emit('theme-change', { theme: 'dark' });
// 子应用
bus.$on('theme-change', (data) => {
document.documentElement.setAttribute('data-theme', data.theme);
});
// ==================== Module Federation:共享 Store ====================
// 共享 Redux store
import { store } from 'remote_app/store';
store.subscribe(() => {
const state = store.getState();
// 响应状态变化
});
// ==================== iframe:postMessage 监听 ====================
// 子应用
window.addEventListener('message', (event) => {
const { type, payload } = event.data;
switch (type) {
case 'theme-change':
applyTheme(payload.theme);
break;
case 'user-update':
store.setUser(payload.user);
break;
}
});
// shared/reactive-sync.ts
import { watch, ref, toRaw } from 'vue';
interface SyncConfig {
path: string; // 同步路径
immediate?: boolean; // 立即同步
deep?: boolean; // 深监听
debounce?: number; // 防抖(ms)
throttle?: number; // 节流(ms)
transform?: (val: any) => any; // 数据转换
validate?: (val: any) => boolean; // 数据校验
}
class ReactiveSync {
private unsubscribes: Map<string, () => void> = new Map();
// 单向同步:源 → 目标
syncFromMain<T>(
source: () => T,
config: SyncConfig,
callback: (val: T) => void
): () => void {
const { immediate = true, deep = false, debounce: debounceMs, throttle: throttleMs, transform, validate } = config;
let timer: ReturnType<typeof setTimeout> | null = null;
let lastTime = 0;
const handler = (newVal: T, oldVal: T) => {
if (validate && !validate(newVal)) return;
const transformed = transform ? transform(newVal) : newVal;
if (debounceMs) {
if (timer) clearTimeout(timer);
timer = setTimeout(() => callback(transformed), debounceMs);
return;
}
if (throttleMs) {
const now = Date.now();
if (now - lastTime < throttleMs) return;
lastTime = now;
}
callback(transformed);
};
const unwatch = watch(source, handler, { immediate, deep });
this.unsubscribes.set(path, unwatch);
return unwatch;
}
// 双向同步
bidirectionalSync<T>(
localRef: Ref<T>,
remoteSource: () => T,
config: SyncConfig,
sendToRemote: (val: T) => void,
receiveFromRemote: (callback: (val: T) => void) => () => void
): () => void {
const cleanupFns: Array<() => void> = [];
// 本地 → 远程
cleanupFns.push(this.syncFromMain(() => localRef.value, config, sendToRemote));
// 远程 → 本地
cleanupFns.push(receiveFromRemote((val) => {
localRef.value = config.transform ? config.transform(val) : val;
}));
return () => cleanupFns.forEach(fn => fn());
}
dispose(): void {
this.unsubscribes.forEach(unsub => unsub());
this.unsubscribes.clear();
}
}
export const reactiveSync = new ReactiveSync();
┌─────────────────────────────────────────────────────────────────┐
│ 事件通信架构 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Event Bus 事件总线 │ │
│ │ │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌────────┐ │ │
│ │ │ 事件注册 │ │ 事件分发 │ │ 请求响应 │ │ 跨域 │ │ │
│ │ │ on/once │ │ emit │ │ request │ │ 通道 │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ └────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 基座 │ │ 子应用 A │ │ 子应用 B │ │
│ │ │ │ │ │ │ │
│ │ emit() │ │ on() │ │ on() │ │
│ │ on() │ │ emit() │ │ emit() │ │
│ └──────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────────┘
// shared/event-bus.ts
type EventHandler = (payload: any) => void;
interface EventBusOptions {
channelName?: string; // BroadcastChannel 名称
crossOrigin?: boolean; // 是否启用跨域
namespace?: string; // 命名空间
debug?: boolean; // 调试模式
}
export class MicroEventBus {
private static instances: Map<string, MicroEventBus> = new Map();
private handlers: Map<string, Set<EventHandler>> = new Map();
private channel: BroadcastChannel | null = null;
private options: EventBusOptions;
private prefix: string;
private constructor(options: EventBusOptions = {}) {
this.options = {
channelName: 'micro-event-bus',
crossOrigin: true,
namespace: 'default',
debug: false,
...options,
};
this.prefix = `[${this.options.namespace}]`;
if (this.options.crossOrigin && typeof BroadcastChannel !== 'undefined') {
this.channel = new BroadcastChannel(this.options.channelName!);
this.channel.onmessage = this.handleBroadcast.bind(this);
}
}
static getInstance(options?: EventBusOptions): MicroEventBus {
const key = options?.namespace || 'default';
if (!this.instances.has(key)) {
this.instances.set(key, new MicroEventBus(options));
}
return this.instances.get(key)!;
}
static destroy(namespace?: string): void {
const key = namespace || 'default';
this.instances.get(key)?.destroy();
this.instances.delete(key);
}
on(event: string, handler: EventHandler): () => void {
if (!this.handlers.has(event)) this.handlers.set(event, new Set());
this.handlers.get(event)!.add(handler);
return () => this.off(event, handler);
}
once(event: string, handler: EventHandler): () => void {
const wrapper = (payload: any) => { this.off(event, wrapper); handler(payload); };
return this.on(event, wrapper);
}
off(event: string, handler?: EventHandler): void {
if (!handler) this.handlers.delete(event);
else this.handlers.get(event)?.delete(handler);
}
emit(event: string, payload?: any): void {
this.handlers.get(event)?.forEach(handler => {
try { handler(payload); }
catch (error) { console.error(`事件处理错误 [${event}]:`, error); }
});
if (this.channel) {
this.channel.postMessage({ event, payload, timestamp: Date.now(), source: this.options.namespace });
}
}
request<T = any>(event: string, payload?: any, timeout = 5000): Promise<T> {
return new Promise((resolve, reject) => {
const requestId = this.generateId();
const responseEvent = `${event}:response:${requestId}`;
const timer = setTimeout(() => { this.off(responseEvent); reject(new Error(`请求超时: ${event}`)); }, timeout);
this.once(responseEvent, (response) => { clearTimeout(timer); resolve(response); });
this.emit(event, { ...payload, __requestId: requestId, __responseEvent: responseEvent });
});
}
handleRequest<T = any>(event: string, handler: (payload: any) => T | Promise<T>): () => void {
return this.on(event, async (data: any) => {
const { __requestId, __responseEvent, ...payload } = data;
try {
const result = await handler(payload);
this.emit(__responseEvent, { success: true, data: result });
} catch (error) {
this.emit(__responseEvent, { success: false, error: String(error) });
}
});
}
destroy(): void {
this.handlers.clear();
this.channel?.close();
this.channel = null;
}
private handleBroadcast(event: MessageEvent): void {
const { event: eventName, payload, source } = event.data;
if (source === this.options.namespace) return;
this.handlers.get(eventName)?.forEach(handler => {
try { handler(payload); }
catch (error) { console.error(`跨域事件处理错误 [${eventName}]:`, error); }
});
}
private generateId(): string {
return `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
}
}
// shared/protocol.ts
export const Protocol = {
auth: {
LOGIN: 'auth:login',
LOGOUT: 'auth:logout',
REFRESH_TOKEN: 'auth:refresh-token',
USER_INFO_CHANGE: 'auth:user-info-change',
},
nav: {
GO_TO: 'nav:go-to',
GO_BACK: 'nav:go-back',
OPEN_TAB: 'nav:open-tab',
CLOSE_TAB: 'nav:close-tab',
},
data: {
FETCH_USER: 'data:fetch-user',
FETCH_CONFIG: 'data:fetch-config',
UPDATE_CONFIG: 'data:update-config',
SYNC_STATE: 'data:sync-state',
},
ui: {
SHOW_TOAST: 'ui:toast-show',
SHOW_MODAL: 'ui:modal-show',
TOGGLE_SIDEBAR: 'ui:sidebar-toggle',
SET_THEME: 'ui:theme-set',
SET_LOCALE: 'ui:locale-set',
SHOW_LOADING: 'ui:loading-show',
HIDE_LOADING: 'ui:loading-hide',
},
lifecycle: {
APP_BOOTSTRAP: 'lifecycle:bootstrap',
APP_MOUNT: 'lifecycle:mount',
APP_UNMOUNT: 'lifecycle:unmount',
APP_READY: 'lifecycle:ready',
APP_ERROR: 'lifecycle:error',
},
} as const;
// 类型安全的消息定义
export interface MessageMap {
'auth:login': { username: string; password: string; remember?: boolean };
'auth:logout': void;
'nav:go-to': { path: string; params?: Record<string, any> };
'data:fetch-user': { userId: string };
'data:sync-state': { path: string; value: any };
'ui:toast-show': { type: 'success' | 'error' | 'warning' | 'info'; message: string; duration?: number };
'ui:theme-set': { theme: 'light' | 'dark' | 'auto' };
'ui:locale-set': { locale: string };
'lifecycle:ready': { appName: string; timestamp: number };
'lifecycle:error': { appName: string; error: string };
}
export interface UserInfo {
id: string;
name: string;
avatar?: string;
roles: string[];
permissions: string[];
department?: string;
}</longcat_think>
---
### 3.4 CSS 隔离(Style Isolation)
#### CSS 隔离方案对比
| 方案 | 隔离方式 | 兼容性 | 性能 | 推荐度 |
|------|---------|--------|------|--------|
| **Shadow DOM** | 浏览器原生 | 高(IE不支持) | ⚡ 高 | ⭐⭐⭐⭐ |
| **CSS Modules** | 编译时转换 | 极高 | ⚡ 高 | ⭐⭐⭐⭐⭐ |
| **CSS Scoped** | 属性选择器 | 极高 | ⚡ 高 | ⭐⭐⭐⭐ |
| **BEM 规范** | 命名约定 | 极高 | ⚡ 高 | ⭐⭐⭐ |
| **Dynamic StyleSheet** | 动态添加/移除 | 高 | ⚡ 高 | ⭐⭐⭐ |
| **experimentalStyleIsolation** | qiankun Proxy | 高 | 🔶 中 | ⭐⭐⭐⭐ |
| **strictStyleIsolation** | Shadow DOM | 中 | 🔶 中 | ⭐⭐⭐ |
#### 各方案 CSS 隔离实现
**1. qiankun - experimentalStyleIsolation(推荐):**
typescript
start({
sandbox: {
experimentalStyleIsolation: true,
// 为子应用样式选择器添加属性前缀
// .el-button {} → [data-qiankun="sub-app"] .el-button {}
},
});
**2. qiankun - strictStyleIsolation(Shadow DOM):**
typescript
start({
sandbox: {
strictStyleIsolation: true,
// 将子应用包裹在 Shadow DOM 中完全隔离
// 注意:第三方组件库弹出层样式可能异常
},
});
**3. Micro-App - 内置 CSS 隔离:**
typescript
microApp.start({
'disable-scopecss': false, // 默认开启
// 'shadowDOM': true, // 可选:Shadow DOM 模式
});
**4. Wujie - 自动 CSS 隔离:**
html
**5. BEM 命名规范(通用方案):**
css
/ 基座 /
.main-app__header { ... }
.main-app__sidebar { ... }
/ Vue 子应用 /
.sub-vue__header { ... }
.sub-vue__sidebar { ... }
/ React 子应用 /
.sub-react__header { ... }
.sub-react__sidebar { ... }
**6. CSS Modules(推荐新项目):**
vue
**7. CSS Scoped(Vue 默认方案):**
vue
---
### 3.5 JS 隔离(JavaScript Sandbox)
#### JS 隔离方案对比
| 方案 | 隔离方式 | 速度 | 完整性 | 兼容性 |
|------|---------|------|--------|--------|
| **Proxy 沙箱** | Proxy 代理 window | 🔶 中 | ⭐⭐⭐ | 高 |
| **快照沙箱** | 记录/恢复 window 属性 | ⚡ 快 | ⭐⭐ | 高 |
| **iframe 沙箱** | 独立浏览上下文 | ⚡ 快 | ⭐⭐⭐⭐⭐ | 极高 |
| **Web Worker** | 独立线程 | ⚡ 快 | ⭐⭐⭐⭐ | 中 |
#### 各方案 JS 隔离实现
**1. qiankun - Proxy 沙箱(默认):**
typescript
start({
sandbox: {
// 创建 fakeWindow(基于 Proxy)
// 子应用所有 window 操作作用于 fakeWindow
// 卸载时丢弃 fakeWindow
},
});
// Proxy 沙箱核心实现(简化版)
class ProxySandbox {
private proxy: Window;
private running = false;
constructor() {
const fakeWindow = Object.create(null);
this.proxy = new Proxy(fakeWindow, {
get(target, prop) {
return prop in target ? target[prop] : (window as any)[prop];
},
set(target, prop, value) {
if (this.running) target[prop as any] = value;
else (window as any)[prop] = value;
return true;
},
});
}
active() { this.running = true; }
inactive() { this.running = false; }
getProxy() { return this.proxy; }
}
**2. qiankun - 快照沙箱(旧版):**
typescript
class SnapshotSandbox {
private windowSnapshot: PropertyDescriptorMap = {};
private modifyPropsMap: PropertyDescriptorMap = {};
active() {
Object.keys(this.modifyPropsMap).forEach(prop => {
Object.defineProperty(window, prop, this.modifyPropsMap[prop]);
});
this.modifyPropsMap = {};
}
inactive() {
Object.keys(window).forEach(prop => {
if (!this.windowSnapshot[prop]) {
this.modifyPropsMap[prop] = Object.getOwnPropertyDescriptor(window, prop);
delete window[prop];
}
});
}
}
**3. Micro-App - JS 沙箱:**
typescript
microApp.start({
'disable-sandbox': false, // 默认开启
// 子应用 JS 在独立的 Function 作用域中执行
// 重写全局对象访问,子应用间完全隔离
});
**4. Wujie - iframe 天然隔离:**
typescript
// 每个子应用在独立 iframe 中运行
// 天然完全隔离,无需 Proxy 代理
// 预加载时提前创建 iframe,切换时无白屏
#### JS 隔离常见问题
typescript
// 问题1:全局变量污染
window.userInfo = { name: '张三' }; // ❌
actions.setGlobalState({ userInfo: { name: '张三' } }); // ✅
// 问题2:事件监听泄漏
window.addEventListener('resize', handler); // ❌
export async function unmount() {
window.removeEventListener('resize', handler); // ✅
}
// 问题3:定时器泄漏
const timer = setInterval(fn, 1000); // ❌
export async function unmount() {
clearInterval(timer); // ✅
}
// 问题4:原型链污染
Array.prototype.myMethod = () => {}; // ❌
function myMethod(arr: any[]) {} // ✅
// 问题5:样式操作污染
document.body.style.background = 'red'; // ❌
container.style.background = 'red'; // ✅
---
### 3.6 三方库版本问题
#### 版本冲突解决方案对比
| 策略 | 描述 | 优点 | 缺点 | 适用场景 |
|------|------|------|------|---------|
| **shared 共享** | 所有应用使用同一版本 | 体积小、一致性 | 升级风险高 | 相同技术栈 |
| **externals 外部化** | 从 CDN 加载公共库 | 缓存复用 | 网络依赖 | 传统项目 |
| **独立打包** | 各应用打包各自版本 | 完全隔离 | 体积大 | 不同技术栈 |
| **singleton 单例** | 运行时强制唯一版本 | 避免重复加载 | 版本需兼容 | Webpack MF |
#### 各方案版本管理实现
**1. Module Federation shared 配置:**
typescript
new ModuleFederationPlugin({
shared: {
react: {
singleton: true,
requiredVersion: '^18.2.0',
eager: true,
},
'react-dom': {
singleton: true,
requiredVersion: '^18.2.0',
},
vue: {
singleton: true,
requiredVersion: '^3.4.0',
},
},
});
**2. externals 外部化:**
typescript
module.exports = {
externals: {
'vue': 'Vue',
'vue-router': 'VueRouter',
'element-plus': 'ElementPlus',
'axios': 'axios',
'dayjs': 'dayjs',
},
};
**3. qiankun - 沙箱隔离版本:**
typescript
// 子应用可以有自己的 Vue 版本,与基座互不影响
registerMicroApps([{
name: 'sub-vue',
props: {
utils: {
formatDate: dayjs,
request: axiosInstance, // 使用基座的 axios 实例
},
},
}]);
---
## 4. 案例项目结构
### 4.1 整体目录结构
micro-frontends/
├── qiankun-demo/ # qiankun 方案案例
│ ├── main-app/ # 基座应用 (Vue3 + Element Plus)
│ │ ├── package.json
│ │ ├── vite.config.ts
│ │ ├── index.html
│ │ └── src/
│ │ ├── main.ts # 注册子应用 + 全局状态
│ │ ├── App.vue # 基座布局
│ │ ├── router/ # 路由配置
│ │ ├── store/ # 基座状态
│ │ └── views/
│ ├── sub-vue/ # Vue 子应用
│ │ ├── package.json
│ │ ├── vite.config.ts
│ │ └── src/
│ │ ├── main.ts # qiankun 生命周期
│ │ ├── App.vue
│ │ ├── router/
│ │ ├── store/
│ │ └── views/
│ └── sub-react/ # React 子应用
│ ├── package.json
│ ├── vite.config.ts
│ └── src/
│ ├── main.tsx
│ └── App.tsx
│
├── micro-app-demo/ # Micro-App 方案案例
│ ├── main-app/ # 基座 (Vue3)
│ └── sub-vue/ # Vue 子应用
│
├── wujie-demo/ # Wujie 方案案例
│ ├── main-app/ # 基座 (Vue3 + wujie-vue3)
│ └── sub-vue/ # Vue 子应用
│
├── module-federation-demo/ # Module Federation 案例
│ ├── host/ # 主应用 (React18)
│ │ ├── webpack.config.js # MF 插件配置
│ │ └── src/
│ └── remote/ # 远程应用
│ ├── webpack.config.js # exposes 配置
│ └── src/
│
├── iframe-demo/ # iframe 方案案例
│ ├── main-app/ # 基座
│ └── sub-app/ # iframe 子应用
│
├── shared/ # 共享模块
│ ├── src/
│ │ ├── event-bus.ts # EventBus 事件总线
│ │ ├── global-state.ts # 全局状态管理
│ │ └── protocol.ts # 通信协议定义
│ └── package.json
│
└── 微前端方案全面对比.md # 本文档
### 4.2 启动方式
bash
cd micro-frontends/qiankun-demo/main-app && npm install && npm run dev # 端口 9000
cd micro-frontends/qiankun-demo/sub-vue && npm install && npm run dev # 端口 9001
cd micro-frontends/qiankun-demo/sub-react && npm install && npm run dev # 端口 9002
cd micro-frontends/micro-app-demo/main-app && npm install && npm run dev # 端口 9100
cd micro-frontends/wujie-demo/main-app && npm install && npm run dev # 端口 9200
cd micro-frontends/module-federation-demo/remote && npm install && npm run dev # 端口 9300
cd micro-frontends/module-federation-demo/host && npm install && npm run dev # 端口 9301
cd micro-frontends/iframe-demo/main-app && npm install && npm run dev # 端口 9400
---
## 5. 选型指南
### 5.1 决策树
┌─────────────────────────────────────────────────────────────────┐
│ 微前端选型决策树 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 开始 │
│ │ │
│ ▼ │
│ 子应用是否使用相同技术栈? │
│ ├── 是 ──► 是否使用 Webpack5? │
│ │ ├── 是 ──► ✅ Module Federation(首选) │
│ │ └── 否 ──► ✅ qiankun / Micro-App │
│ │ │
│ └── 否 ──► 是否需要完全隔离? │
│ ├── 是 ──► ✅ Wujie / iframe │
│ └── 否 ──► 性能要求高吗? │
│ ├── 是 ──► ✅ Micro-App / Wujie │
│ └── 否 ──► ✅ qiankun(生态最丰富) │
│ │
└─────────────────────────────────────────────────────────────────┘
### 5.2 场景推荐矩阵
| 场景 | 首选方案 | 备选方案 | 原因 |
|------|---------|---------|------|
| 中大型企业内部系统 | qiankun | Micro-App | 生态成熟,文档丰富 |
| Vue 技术栈为主 | Micro-App | Wujie | 轻量,Vue 友好 |
| React 技术栈为主 | qiankun | single-spa | React 生态兼容好 |
| 多框架混合 | qiankun | single-spa | 框架无关性 |
| 高性能要求 | Wujie | Micro-App | 预加载+原生隔离 |
| 同技术栈 Webpack5 | Module Federation | - | 原生的,类型安全 |
| 完全隔离(安全要求高)| iframe + postMessage | Wujie | 隔离性最强 |
| 快速上手/小型项目 | Micro-App | qiankun | 配置简单 |
| 移动端 H5 | Micro-App | qiankun | 体积小 |
| 桌面端 (Electron/Tauri) | iframe | Wujie | 隔离性重要 |
### 5.3 综合评分
| 维度 | qiankun | Micro-App | Wujie | MF | single-spa | iframe |
|------|---------|-----------|-------|-----|-----------|--------|
| 学习成本 | 3 | 4 | 4 | 3 | 2 | 5 |
| 文档完善度 | 5 | 4 | 4 | 4 | 3 | 5 |
| 社区活跃度 | 5 | 3 | 3 | 4 | 3 | 5 |
| 性能表现 | 3 | 4 | 5 | 4 | 3 | 2 |
| JS 隔离 | 4 | 4 | 5 | 1 | 3 | 5 |
| CSS 隔离 | 4 | 4 | 5 | 1 | 2 | 5 |
| 通信便捷性 | 5 | 4 | 4 | 5 | 3 | 2 |
| 多框架支持 | 5 | 4 | 4 | 2 | 5 | 5 |
| 版本管理 | 3 | 3 | 3 | 5 | 3 | 5 |
| **总分** | **37** | **34** | **37** | **29** | **27** | **39** |
---
## 6. 常见问题与解决方案
### 6.1 子应用白屏
typescript
// 原因1:entry 地址错误
registerMicroApps([{
name: 'sub-vue',
entry: '//localhost:9001', // ✅ 确保能返回 HTML
}]);
// 原因2:CORS 跨域
server: {
cors: true,
headers: { 'Access-Control-Allow-Origin': '*' },
}
// 原因3:子应用未导出生命周期
export async function bootstrap() { / ... / }
export async function mount(props: any) { / ... / }
export async function unmount() { / ... / }
// 原因4:Vite 项目需要特殊处理
import qiankun from 'vite-plugin-qiankun';
export default defineConfig({
plugins: [vue(), qiankun('sub-vue', { useDevMode: true })],
});
### 6.2 路由冲突
typescript
// 解决1:使用 hash 路由模式
const router = createRouter({
history: createWebHashHistory(),
routes: [],
});
// 解决2:history 路由 + base 前缀
const router = createRouter({
history: createWebHistory('/sub-vue'), // base 与 activeRule 对应
routes: [],
});
// 解决3:基座统一管理路由
activeRule: '/sub-vue', // 基座规则
base: '/sub-vue', // 子应用 base
### 6.3 内存泄漏
typescript
export async function unmount() {
app?.unmount(); // 1. 卸载应用
clearInterval(timer); // 2. 清理定时器
clearTimeout(timeout);
window.removeEventListener('resize', handler); // 3. 移除事件监听
bus.off('event-name', handler);
container?.querySelector('#subvue-root')?.remove(); // 4. 清理 DOM
app = null; // 5. 释放引用
}
### 6.4 样式污染应急方案
typescript
// 1. 为子应用容器添加唯一属性
// 2. 使用属性选择器覆盖
const style = document.createElement('style');
style.textContent =
[data-app="sub-vue"] .el-button { / 覆盖样式 / }
;document.head.appendChild(style);
// 3. 使用 :where() 降低优先级
:where([data-app="sub-vue"]) .button {
/ 优先级低于子应用内部样式 /
}
``
<hr>
<h2>7. 总结</h2>
<h3>核心结论</h3>
<h3>一句话选型</h3>
<hr>
<blockquote>文档结束 | 案例代码路径: d:\project\micro-frontends\`