← 返回首页← 专题总览

微前端方案全面对比

| 方案 | 出品方 | 核心技术 | Star⭐ | 最新版本 | 适用场景 |
前端架构师通关手册 · 专题 24
文档版本: v1.0 | 更新日期: 2026-10-26
> 本文档覆盖 6 种主流微前端方案,每套方案均包含可运行案例代码。

目录

  1. [
方案概览](#1-方案概览)
  1. [
方案详细对比](#2-方案详细对比)
  1. [
核心问题深度分析](#3-核心问题深度分析)
  1. [
案例项目结构](#4-案例项目结构)
  1. [
选型指南](#5-选型指南)
  1. [
常见问题与解决方案](#6-常见问题与解决方案)

1. 方案概览

1.1 六大主流方案

方案出品方核心技术Star⭐最新版本适用场景
qiankun阿里single-spa + Proxy沙箱15k+2.10+中大型项目,多框架
Micro-App京东Web Component5k+1.0+轻量级,Vue友好
Wujie(无界)腾讯iframe + Web Component3k+1.0+高性能要求
Module FederationWebpack官方Webpack5 MF-5.x同技术栈
single-spa社区路由分发13k+6.x多框架集成
iframe + postMessage原生iframe--完全隔离

1.2 架构原理图

┌─────────────────────────────────────────────────────────────────────────────────┐
│                           微前端架构原理全景图                                     │
├─────────────────────────────────────────────────────────────────────────────────┤
│                                                                                 │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐            │
│  │   qiankun   │  │ Micro-App   │  │   Wujie     │  │     MF      │            │
│  ├─────────────┤  ├─────────────┤  ├─────────────┤  ├─────────────┤            │
│  │ single-spa  │  │ Web Compon. │  │iframe+WC    │  │ Webpack5    │            │
│  │ + Proxy沙箱 │  │ + 沙箱隔离  │  │ + 预加载    │  │ Runtime共享 │            │
│  │ + 样式隔离  │  │ + CSS隔离   │  │ + 最佳性能  │  │ + 类型安全  │            │
│  └─────────────┘  └─────────────┘  └─────────────┘  └─────────────┘            │
│         │               │               │               │                      │
│         ▼               ▼               ▼               ▼                      │
│  ┌─────────────────────────────────────────────────────────────────────┐       │
│  │                        基座应用 (Main App)                            │       │
│  │  ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐       │       │
│  │  │ 注册子应用  │ │ 路由分发   │ │ 沙箱隔离   │ │ 通信管理   │       │       │
│  │  └────────────┘ └────────────┘ └────────────┘ └────────────┘       │       │
│  └─────────────────────────────────────────────────────────────────────┘       │
│         │               │               │               │                      │
│         ▼               ▼               ▼               ▼                      │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐                      │
│  │ 子应用 A │  │ 子应用 B │  │ 子应用 C │  │ 子应用 D │                      │
│  └──────────┘  └──────────┘  └──────────┘  └──────────┘                      │
└─────────────────────────────────────────────────────────────────────────────────┘

2. 方案详细对比

2.1 qiankun (阿里)

核心原理

完整案例

基座注册子应用:
// 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 通信机制详解

┌─────────────────────────────────────────────────────────────────┐
│                    qiankun 通信机制                               │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  ┌──────────────────┐          ┌──────────────────┐            │
│  │     基座         │          │     子应用        │            │
│  │                  │          │                  │            │
│  │  initGlobalState │◄────────►│  props.actions   │            │
│  │  (state, actions)│  props   │  .onGlobalState  │            │
│  │                  │ 传递     │  Change()        │            │
│  │  actions.set     │─────────►│                  │            │
│  │  GlobalState()   │  状态变更 │  actions.get     │            │
│  │                  │  通知    │  GlobalState()   │            │
│  └──────────────────┘          └──────────────────┘            │
│                                                                 │
│  通信方式:                                                      │
│  ├── 1. props 注入(初始化传参)                                  │
│  ├── 2. initGlobalState(全局状态管理)                           │
│  ├── 3. 通过 props 传递回调函数                                   │
│  └── 4. 自定义 EventBus(额外方案)                               │
└─────────────────────────────────────────────────────────────────┘

2.2 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 通信机制

┌─────────────────────────────────────────────────────────────────┐
│                   Micro-App 通信机制                              │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  方式一:data 属性绑定(响应式)                                   │
│  ┌────────────┐  :data="microData"  ┌────────────┐              │
│  │    基座    │ ──────────────────► │  子应用     │              │
│  │            │  addDataListener()  │            │              │
│  └────────────┘ ◄────────────────── └────────────┘              │
│                                                                 │
│  方式二:API 主动通信                                             │
│  ┌────────────┐  microApp.setData() ┌────────────┐              │
│  │    基座    │ ──────────────────► │  子应用     │              │
│  │            │ dispatch()         │            │              │
│  └────────────┘ ◄────────────────── └────────────┘              │
│                                                                 │
│  方式三:URL 携带参数                                             │
│  ┌────────────┐   url="**"         ┌────────────┐              │
│  │    基座    │ ──────────────────► │  子应用     │              │
│  └────────────┘                    └────────────┘              │
└─────────────────────────────────────────────────────────────────┘

2.3 Wujie / 无界 (腾讯)

核心原理

完整案例

基座使用:
// 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 通信机制

┌─────────────────────────────────────────────────────────────────┐
│                    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 隔离                                         │
│  ├── 预加载实现无感切换                                          │
│  └── 完备的异常边界处理                                          │
└─────────────────────────────────────────────────────────────────┘

2.4 Module Federation (Webpack 5)

核心原理

完整案例

远程应用配置(remote):
// 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 通信机制

┌─────────────────────────────────────────────────────────────────┐
│                Module Federation 通信机制                         │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  ┌──────────────────┐              ┌──────────────────┐        │
│  │   Host (主应用)   │              │  Remote (远程)    │        │
│  │                  │              │                  │        │
│  │  import('remote  │ ──运行时──►  │  exposes: {      │        │
│  │  _app/Dashboard')│   动态加载   │    './Dashboard' │        │
│  │                  │              │  }               │        │
│  │  shared: {       │              │  shared: {       │        │
│  │    react: 单例   │ ◄──共享────► │    react: 单例   │        │
│  │  }               │              │  }               │        │
│  └──────────────────┘              └──────────────────┘        │
│                                                                 │
│  通信方式:                                                      │
│  ├── 1. 直接导入远程模块(组件/函数/store)                       │
│  ├── 2. shared 共享依赖(单例模式)                               │
│  ├── 3. 共享事件总线                                             │
│  └── 4. 共享状态管理(Redux/Pinia)                              │
│                                                                 │
│  核心优势:                                                      │
│  ├── 编译时类型安全(TypeScript)                                │
│  ├── 运行时动态加载                                              │
│  └── 共享依赖减少重复打包                                        │
└─────────────────────────────────────────────────────────────────┘

2.5 single-spa

核心原理

完整案例

基座配置:
// 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')
);

2.6 iframe + postMessage

核心原理

完整案例

基座端:
// 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();

3. 核心问题深度分析

3.1 传参(Parameter Passing)

传参方式对比

方式qiankunMicro-AppWujieMFiframe
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' });

3.2 数据监听(Data Observation)

数据监听方案对比

// ==================== 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();

3.3 事件响应(Event Response)

事件通信架构

┌─────────────────────────────────────────────────────────────────┐
│                     事件通信架构                                  │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │                   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

qiankun 案例

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

micro-app 案例

cd micro-frontends/micro-app-demo/main-app && npm install && npm run dev # 端口 9100

wujie 案例

cd micro-frontends/wujie-demo/main-app && npm install && npm run dev # 端口 9200

module federation 案例

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

iframe 案例

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>

  1. <
strong>传参:推荐「分层策略」—— Props 注入(初始化)+ 全局状态(双向同步)+ 事件总线(实时交互)+ 请求-响应(数据获取)
  1. <
strong>数据监听:根据场景选择防抖/节流/立即同步,使用 ReactiveSync 统一封装,避免多次重复监听
  1. <
strong>事件响应:使用类型安全的通信协议(Protocol),EventBus 支持同域+跨域双通道
  1. <
strong>三方库版本:优先使用 singleton 共享,其次 externals 外部化,最后才是独立打包
  1. <
strong>CSS 隔离:新项目用 CSS Modules / Scoped,老项目用 experimentalStyleIsolation,严格隔离用 Shadow DOM
  1. <
strong>JS 隔离:qiankun Proxy 沙箱适合大多数场景,Wujie iframe 沙箱适合强隔离需求

<h3>一句话选型</h3>

  • 🏢 企业项目:qiankun(生态最丰富)
  • 🚀 性能优先:Wujie(预加载+iframe隔离)
  • ⚡ 轻量快速:Micro-App(Web Component方案)
  • 🔧 同技术栈:Module Federation(Webpack5原生)
  • 🔒 完全隔离:iframe + postMessage(原生方案)

<hr>

<blockquote>文档结束 | 案例代码路径: d:\project\micro-frontends\`