Skip to content

微前端 ​

本手册从"为什么需要微前端"出发,逐步深入到技术方案选型、主流框架原理、qiankun 核心实现(JS 沙箱 / CSS 隔离 / 生命周期)以及应用通信与实战落地,帮助你构建完整的微前端知识体系。

导航目录 ​

一、概念篇 ​

二、方案对比篇 ​

三、框架对比篇 ​

四、qiankun 原理篇 ​

五、通信与实战篇 ​


为什么需要微前端? ​

核心概念

微前端(Micro Frontends) 是一种将大型前端应用按业务维度拆分成多个可独立开发、独立部署的子应用,再由一个主应用(基座)动态加载并整合的架构模式。它把后端"微服务"的分而治之思想搬到了前端。

随着业务膨胀,单体前端应用(Monolith)会逐渐暴露出一系列问题:

单体应用痛点具体表现
代码库臃肿几十万行代码堆在一个仓库,编译一次要几分钟,改一行代码全量构建
技术栈锁死项目起步用 Vue2,想升级 Vue3 / React 几乎不可能,只能推倒重来
团队协作冲突多个团队共用一个仓库,代码合并冲突频繁,发布互相阻塞
牵一发动全身任意模块出错可能拖垮整个应用,发布风险高
老系统难维护遗留系统技术过时,但重写成本极高,无法渐进式改造

生活类比

把单体应用想象成一栋一体浇筑的大楼——想改一间房就得动整栋结构。微前端则像乐高积木搭的大楼:每一块(子应用)可以独立设计、独立更换,拼起来仍是一栋完整的楼。

实现思路 ​

将应用按业务域划分为若干子应用,各自独立打包成可被远程加载的模块;主应用(基座)负责统一的路由分发,在路由切换时动态加载 / 卸载对应的子应用,从而在保持技术独立性的同时,对用户呈现为一个完整的产品。

                    ┌─────────────────────────────┐
                    │        主应用 / 基座          │
                    │   (路由分发 · 全局状态 · 布局)  │
                    └──────────────┬──────────────┘
                       ┌───────────┼───────────┐
                  ┌────▼────┐  ┌───▼────┐  ┌───▼─────┐
                  │ 子应用A  │  │ 子应用B │  │ 子应用C  │
                  │ (Vue3)  │  │ (React) │  │ (Angular)│
                  └─────────┘  └────────┘  └─────────┘

微前端的核心价值与适用场景 ​

核心价值 ​

  1. 技术栈无关:主框架不限制接入子应用的技术栈,Vue、React、Angular 甚至 jQuery 老项目都能共存
  2. 独立开发部署:每个子应用独立仓库、独立 CI/CD,发布互不影响,缩短交付周期
  3. 渐进式迁移:老系统可以"包"进微前端框架,一个模块一个模块地重写,降低升级风险
  4. 团队自治:各团队按自己的节奏迭代,边界清晰,减少沟通成本
  5. 增量升级:可以在不影响其它部分的情况下,单独升级某个子应用的框架版本

微前端不是银弹

微前端引入了额外的复杂度(沙箱、通信、样式隔离、部署编排等)。如果你的应用不大、团队只有一两个人、技术栈统一,那么普通的路由拆分 / 组件库 / Monorepo 往往是更划算的选择。不要为了微前端而微前端。

适用场景 ​

适合 ✅不适合 ❌
大型中后台系统(多个业务模块)小型单页应用
多团队协作、技术栈不统一单人 / 小团队、技术栈统一
老系统渐进式改造、遗留系统整合对首屏性能极致敏感的 C 端页面
需要独立发布、灰度不同模块模块间交互极其频繁、耦合极深

微前端要解决的三大核心问题 ​

无论采用哪种技术方案,微前端框架本质上都在解决以下三个问题:

三大核心问题

  1. 应用加载:如何在运行时把子应用的 HTML / JS / CSS 拉取下来并渲染到主应用指定的容器中?
  2. 应用隔离:如何保证多个子应用之间、子应用与主应用之间的 JS 全局变量 与 CSS 样式 互不污染?
  3. 应用通信:拆分后的子应用之间如何安全、高效地共享状态与传递数据?
问题关键技术典型方案
应用加载HTML Entry / JS Entry、动态 script、import-html-entryqiankun 的 HTML Entry
JS 隔离快照沙箱、Proxy 沙箱、iframe 原生沙箱qiankun Proxy 沙箱、无界 iframe
CSS 隔离Shadow DOM、CSS Scoped、动态样式表增删micro-app WC、qiankun 严格样式隔离
应用通信props、CustomEvent、发布订阅(EventBus)、全局状态qiankun GlobalState、无界 EventBus

后续章节将围绕这三大问题,逐一展开各方案与框架的实现细节。


iframe 方案 ​

实现原理

通过 <iframe> 标签加载子应用页面,利用浏览器原生的沙箱机制天然实现 JS 与 CSS 的完全隔离,应用间通过 postMessage API 通信。

html
<iframe src="//sub-app.com/app1" id="sub"></iframe>
<script>
  // 主应用 → 子应用
  const iframe = document.getElementById("sub");
  iframe.contentWindow.postMessage({ type: "login", token: "xxx" }, "*");

  // 子应用监听
  window.addEventListener("message", (e) => {
    if (e.data.type === "login") console.log(e.data.token);
  });
</script>
优势 ✅局限性 ❌
实现极简,技术成熟弹窗 / 遮罩层被限制在 iframe 内,无法覆盖全屏
天然完美的 JS / CSS 隔离URL 不同步,浏览器前进后退状态丢失、刷新回到首页
安全边界清晰每次切换重建 iframe,DOM / JS 上下文全部销毁,白屏明显
无需改造子应用主子应用 DOM 割裂,共享弹窗、loading 困难

iframe 的致命伤

iframe 的隔离性无可挑剔,但用户体验问题(URL 不同步、弹窗受限、加载慢)使其难以承载复杂应用。"无界"框架正是通过 iframe + Web Components 的组合来扬长避短。

Web Components 方案 ​

实现原理

基于浏览器原生的自定义元素(Custom Elements) 将子应用封装为一个 HTML 标签,利用 Shadow DOM 提供样式与 DOM 作用域隔离,通过 CustomEvent 进行通信。

js
// 定义子应用为自定义元素
class MicroApp extends HTMLElement {
  connectedCallback() {
    // 开启 Shadow DOM,样式天然隔离
    const shadow = this.attachShadow({ mode: "open" });
    shadow.innerHTML = `<style>h1{color:red}</style><h1>子应用</h1>`;
  }
}
customElements.define("micro-app", MicroApp);
html
<micro-app url="//localhost:3000"></micro-app>
优势 ✅局限性 ❌
浏览器原生支持,无框架依赖老浏览器兼容性差(IE 不支持)
Shadow DOM 天然样式隔离Shadow DOM 内某些第三方 UI 库(弹窗挂 body)失效
良好的组件封装性学习曲线较陡,调试相对困难

TIP

micro-app 框架就是以 Web Components 为核心,把子应用包装成 <micro-app> 标签,做到"像用组件一样用微前端",接入几乎零改造。

single-spa 方案 ​

实现原理

single-spa 是微前端的"鼻祖"框架。核心是劫持路由:监听 hashchange / popstate,根据当前 URL 匹配 activeRule,决定加载 / 卸载哪个子应用;子应用需暴露 bootstrap、mount、unmount 三个生命周期钩子。

js
import { registerApplication, start } from "single-spa";

registerApplication({
  name: "app1",
  app: () => System.import("app1"), // 用 SystemJS 加载
  activeWhen: (location) => location.pathname.startsWith("/app1"),
  customProps: { token: "xxx" },
});

start();
优势 ✅局限性 ❌
框架无关,技术栈灵活不提供 JS / CSS 沙箱,需自行实现隔离
微前端生态的事实标准子应用需较多改造(导出生命周期、改打包配置)
支持渐进式迁移需依赖 SystemJS,学习成本高

single-spa 的短板

single-spa 只解决了"应用加载与调度",没有解决隔离问题。这正是 qiankun 的价值所在——qiankun = single-spa(调度)+ import-html-entry(HTML Entry 加载)+ Proxy 沙箱(隔离)。

Module Federation 方案 ​

实现原理

模块联邦(Module Federation,MF) 是 Webpack 5 的构建时特性。它允许一个应用(remote)在运行时将自己的模块暴露出去,另一个应用(host)直接远程加载并运行这些模块,还能共享依赖(如只加载一份 React)。

js
// 子应用(remote)webpack.config.js
new ModuleFederationPlugin({
  name: "app1",
  filename: "remoteEntry.js",
  exposes: { "./Button": "./src/Button" }, // 暴露模块
  shared: ["react", "react-dom"], // 共享依赖
});

// 主应用(host)
new ModuleFederationPlugin({
  name: "host",
  remotes: { app1: "app1@//localhost:3001/remoteEntry.js" },
  shared: ["react", "react-dom"],
});
js
// 主应用中像本地模块一样使用
const RemoteButton = React.lazy(() => import("app1/Button"));
优势 ✅局限性 ❌
构建时优化,依赖共享,性能好强依赖 Webpack 5(或 Vite 插件)
模块级复用,不止是应用级无沙箱隔离,全局变量 / 样式仍可能冲突
开发体验友好,像用 npm 包一样版本 / 依赖协商配置较复杂

方案总览

  • iframe:隔离满分,体验不及格
  • Web Components:原生优雅,兼容性受限
  • single-spa:调度标准,隔离缺失
  • Module Federation:共享复用强,隔离缺失

现代企业级框架(qiankun / micro-app / 无界)本质是把上述方案组合 + 补齐短板后的工程化产物。


qiankun / micro-app / 无界 定位 ​

框架出品方底层方案一句话定位
qiankun蚂蚁金服single-spa + import-html-entry + Proxy 沙箱应用级微前端,生态最成熟、落地最广
micro-app京东零售Web Components + 自研沙箱组件式微前端,接入极简、几乎零改造
无界(wujie)腾讯iframe + Web Componentsiframe 级隔离 + WC 级体验,主打稳定安全

qiankun ​

INFO

基于 single-spa 的企业级微前端解决方案,在阿里内部有大规模落地。核心是基于路由的应用加载与切换,配套 HTML Entry、JS 沙箱、样式隔离、预加载、全局状态等完整能力。子应用需导出生命周期钩子并调整打包配置。

micro-app ​

INFO

京东零售开源,把子应用封装成 <micro-app> 自定义元素,追求"像用 iframe 一样简单,像用组件一样自然"。子应用几乎零改造,路由自动同步,样式借助 Web Components 天然隔离。

无界(wujie) ​

INFO

腾讯开源,用 iframe 承载子应用的 JS 运行环境(拿到 iframe 级的完美隔离),用 Web Components 承载子应用的 DOM(解决 iframe 体验差的问题),子应用完全零改造,兼顾隔离与体验。

核心能力横向对比 ​

技术原理与隔离 ​

维度qiankunmicro-app无界
技术原理路由劫持 + HTML EntryWeb Components + 自研沙箱iframe + Web Components
JS 沙箱Proxy 沙箱(多例)基于 Proxy 的沙箱iframe 原生沙箱
CSS 隔离Shadow DOM / 严格样式隔离WC 天然隔离iframe 完全隔离
路由同步手动配置 activeRule自动同步自动同步
子应用改造需导出生命周期 + 改打包几乎零改造完全零改造

接入成本 ​

维度qiankunmicro-app无界
子应用改造需要生命周期改造几乎零改造完全零改造
配置复杂度中等简单简单
学习成本较高较低较低
接入时间1~2 天几小时几小时

隔离能力评分 ​

能力qiankunmicro-app无界
JS 隔离⭐⭐⭐⭐⭐ Proxy 多例⭐⭐⭐⭐ Proxy⭐⭐⭐⭐⭐ iframe 级
CSS 隔离⭐⭐⭐⭐ 可配置⭐⭐⭐⭐⭐ WC 天然⭐⭐⭐⭐⭐ iframe 级
多实例并存支持支持支持(互不影响)

通信机制 ​

框架通信方式示例
qiankunprops + GlobalStateprops.onGlobalStateChange(cb)
micro-appCustomEvent(setData / dispatch)microApp.setData('app1', {...})
无界EventBus(发布订阅)window.$wujie.bus.$emit('event', data)

性能与生态 ​

维度qiankunmicro-app无界
加载性能中等(需框架 + 沙箱初始化)较好(轻量 + WC)优秀(接近原生 iframe)
运行时性能中等(Proxy 有开销)较好优秀(iframe 级隔离)
社区活跃度⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
文档完善度⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
企业案例⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐

如何选型? ​

选型结论

  • 求稳定、功能全、生态成熟 → 选 qiankun(大型中后台首选,愿意付出接入成本换完整能力)
  • 求快速、轻量、接入简单 → 选 micro-app(京东系、追求极简接入)
  • 求安全、零改造、隔离彻底 → 选 无界(老系统 / 第三方页面嵌入场景)

选型时的关键决策链:

子应用能否改造?
 ├─ 不能改造(第三方 / 遗留系统)→ 无界 / micro-app
 └─ 能改造
     ├─ 需要成熟生态 + 复杂场景 → qiankun
     └─ 追求极简接入            → micro-app

通用避坑

无论选哪个框架,都要提前规划:子应用静态资源的跨域 CORS、publicPath 动态设置、路由 base 前缀、样式隔离策略。这几点是接入阶段 90% 报错的来源(详见常见问题篇)。


qiankun 完整接入流程 ​

qiankun = single-spa(调度) + import-html-entry(HTML Entry 加载) + Proxy 沙箱(隔离)。下面是一套最小可运行的主 / 子应用接入示例。

主应用(基座) ​

js
// main/src/micro.js
import { registerMicroApps, start } from "qiankun";

registerMicroApps(
  [
    {
      name: "vue-app", // 子应用唯一名称,需与子应用 package.json name 一致
      entry: "//localhost:7100", // 子应用 HTML 入口
      container: "#subapp-container", // 挂载点
      activeRule: "/vue", // 路由匹配规则,命中时加载该子应用
      props: { token: "xxx" }, // 传给子应用的数据
    },
    {
      name: "react-app",
      entry: "//localhost:7200",
      container: "#subapp-container",
      activeRule: "/react",
    },
  ],
  {
    beforeLoad: [(app) => console.log("before load", app.name)],
    afterMount: [(app) => console.log("mounted", app.name)],
  }
);

start({ prefetch: true, sandbox: { strictStyleIsolation: false } });

子应用改造(以 Vue3 为例) ​

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

let app = null;

// 独立运行时直接渲染
function render(props = {}) {
  const { container } = props;
  app = createApp(App);
  app.mount(container ? container.querySelector("#app") : "#app");
}
if (!window.__POWERED_BY_QIANKUN__) render();

// ① 导出生命周期钩子
export async function bootstrap() {}
export async function mount(props) {
  render(props); // props 里含主应用传来的数据与通信方法
}
export async function unmount() {
  app.unmount();
  app = null;
}

三处必改配置

  1. 打包格式:output.library 设为应用名、libraryTarget: 'umd',让 qiankun 能拿到生命周期钩子
  2. publicPath:运行时用 __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__ 动态修正,否则静态资源 404
  3. 跨域:子应用 dev-server 与静态资源需开启 CORS(Access-Control-Allow-Origin),否则 HTML Entry 拉取失败

HTML Entry 加载原理 ​

INFO

qiankun 采用 HTML Entry(相比 single-spa 的 JS Entry):直接请求子应用的 index.html,用 import-html-entry 解析出其中的 JS / CSS,将 HTML 模板注入容器、把 JS 放进沙箱执行。好处是子应用改动小、资源信息完整、便于样式隔离。

子应用生命周期 ​

qiankun 沿用 single-spa 的生命周期模型,子应用必须导出三个(可选四个)钩子:

钩子触发时机典型工作
bootstrap子应用首次加载时(仅一次)初始化全局资源、缓存
mount每次进入子应用路由时创建实例、挂载 DOM、订阅通信
unmount每次离开子应用路由时卸载实例、清理副作用、解绑事件
update(可选)主应用主动更新 props 时响应 props 变化
text
[路由命中 activeRule]
主应用 → qiankun → 子应用:
   1. qiankun 请求子应用 index.html (HTML Entry)
   2. qiankun 调用 bootstrap()      (首次加载,仅一次)
   3. qiankun 调用 mount(props)     (挂载)
   4. 子应用渲染完成 → 通知主应用

[路由离开]
   5. qiankun 调用 unmount()        (卸载清理)

内存泄漏高发区

mount 里注册的定时器、事件监听、全局订阅,必须在 unmount 里逐一清理。否则反复切换子应用会累积泄漏,这是 qiankun 项目最常见的隐患。

JS 沙箱三种实现 ​

沙箱的作用

沙箱用于隔离子应用对 window 的污染。子应用运行期间对全局变量的增删改,都应被限制在自己的作用域内,卸载后能干净还原,多个子应用之间也互不干扰。

qiankun 的沙箱演进经历了三个阶段:快照沙箱 → 单例 Proxy 沙箱 → 多例 Proxy 沙箱。

① 快照沙箱(SnapshotSandbox) ​

适用于不支持 Proxy 的低版本浏览器。原理:激活时给 window 拍快照,失活时对比差异并还原。

js
/**
 * 快照沙箱
 * 缺点:需遍历 window 所有属性,性能差、耗内存;且只支持单实例
 */
class SnapshotSandbox {
  constructor() {
    this.modifyPropsMap = {}; // 记录子应用修改过的变量
    this.windowSnapshot = {}; // window 快照
  }

  active() {
    // 1. 备份当前 window 快照
    this.windowSnapshot = {};
    for (const key in window) {
      this.windowSnapshot[key] = window[key];
    }
    // 2. 恢复上次子应用修改的变量
    for (const key in this.modifyPropsMap) {
      window[key] = this.modifyPropsMap[key];
    }
  }

  inactive() {
    for (const key in window) {
      // 对比快照,找出被修改的属性
      if (window[key] !== this.windowSnapshot[key]) {
        this.modifyPropsMap[key] = window[key]; // 记录,激活时恢复
        window[key] = this.windowSnapshot[key]; // 还原 window
      }
    }
  }
}

const sandbox = new SnapshotSandbox();
sandbox.active();
window.a = 1;
window.b = 2;
console.log(window.a, window.b); // 1 2
sandbox.inactive();
console.log(window.a, window.b); // undefined undefined
sandbox.active();
console.log(window.a, window.b); // 1 2

② 单例 Proxy 沙箱(LegacySandbox) ​

用 Proxy 记录对 window 的新增 / 修改,失活时精准还原,无需遍历整个 window,性能优于快照沙箱。

js
/**
 * 单例代理沙箱
 * 缺点:仍直接操作真实 window,多个子应用同时运行会互相冲突
 */
class ProxySandbox {
  constructor() {
    this.addedPropsMap = new Map(); // 子应用新增的属性
    this.modifyPropsMap = new Map(); // 子应用修改前的原始值
    this.allPropsMap = new Map(); // 所有新增+修改,用于激活时恢复

    const fakeWindow = Object.create(null);
    this.proxy = new Proxy(fakeWindow, {
      get: (target, key) => window[key],
      set: (target, key, value) => {
        if (!window.hasOwnProperty(key)) {
          this.addedPropsMap.set(key, value); // 新增
        } else if (!this.modifyPropsMap.has(key)) {
          this.modifyPropsMap.set(key, window[key]); // 记录原值
        }
        window[key] = value;
        this.allPropsMap.set(key, value);
        return true;
      },
    });
  }

  active() {
    // 恢复子应用之前的新增和修改
    this.allPropsMap.forEach((value, key) => (window[key] = value));
  }
  inactive() {
    this.modifyPropsMap.forEach((value, key) => (window[key] = value)); // 还原修改
    this.addedPropsMap.forEach((_, key) => delete window[key]); // 删除新增
  }
}

③ 多例 Proxy 沙箱(当前主力) ​

为什么需要多例

单例沙箱直接改真实 window,两个子应用同时运行会共用一个 window 而冲突。多例沙箱为每个子应用创建一个独立的 fakeWindow,读取时优先取自己的、找不到再回退到真实 window,写入只落在自己的 fakeWindow 上,从而实现真正的多实例隔离。

js
/**
 * 多例代理沙箱
 * 通过独立 fakeWindow 模拟操作,不污染真实 window
 */
class ProxySandbox {
  constructor() {
    this.running = false; // 沙箱是否激活
    const fakeWindow = Object.create(null);
    this.proxy = new Proxy(fakeWindow, {
      get: (target, key) => {
        // 未激活:全部读真实 window;激活:优先读自己的
        if (!this.running) return window[key];
        return key in target ? target[key] : window[key];
      },
      set: (target, key, value) => {
        if (this.running) target[key] = value; // 只写自己的 fakeWindow
        return true;
      },
    });
  }
  active() {
    this.running = true;
  }
  inactive() {
    this.running = false;
  }
}

const s1 = new ProxySandbox();
const s2 = new ProxySandbox();
s1.active();
s2.active();
s1.proxy.a = 1;
s2.proxy.a = 2;
console.log(s1.proxy.a, s2.proxy.a); // 1 2(互不影响)
console.log(window.a); // undefined(真实 window 未被污染)
沙箱类型隔离方式多实例性能兼容性
快照沙箱快照对比还原❌ 单例差(遍历 window)好(无需 Proxy)
单例 Proxy记录增改,操作真实 window❌ 单例较好需 Proxy
多例 Proxy独立 fakeWindow✅ 多例好需 Proxy

CSS 样式隔离原理 ​

子应用样式污染主应用 / 其它子应用,是微前端第二大隔离难题。qiankun 提供两种方案:

① Shadow DOM 严格隔离(strictStyleIsolation) ​

js
start({ sandbox: { strictStyleIsolation: true } });

原理

把子应用的 DOM 挂载到一个 Shadow DOM 节点内。Shadow DOM 内的样式与外界完全隔离,是最彻底的方案。

副作用

Shadow DOM 会导致挂到 document.body 的元素(如 Ant Design 的 Modal、Message、Tooltip)样式丢失,因为它们逃出了 Shadow 边界。这类第三方 UI 库需额外配置 getPopupContainer 把弹窗挂回容器内。

② 运行时 CSS 作用域(experimentalStyleIsolation) ​

js
start({ sandbox: { experimentalStyleIsolation: true } });

原理

qiankun 会给子应用的每条 CSS 规则动态添加属性选择器前缀,实现"软隔离":

css
/* 原始 */
.title {
  color: red;
}
/* 改写后(div[data-qiankun="react-app"] 作用域) */
div[data-qiankun="react-app"] .title {
  color: red;
}

兼容性好、不影响弹窗,但对 @keyframes、@font-face、内联样式等覆盖不全,属于折中方案。

其它常见样式隔离手段 ​

手段原理特点
Shadow DOM原生影子边界最彻底,但弹窗易失效
CSS 前缀作用域运行时改写选择器兼容好,覆盖不全
CSS Modules编译期生成 hash 类名构建时隔离,需子应用配合
BEM / 命名空间约定人为加前缀零成本,靠自觉,易失效
动态样式表增删mount 加载、unmount 移除 <style>qiankun 默认行为,防止残留

加载 / 卸载时的样式处理

qiankun 在子应用 mount 时插入其 <style>,unmount 时移除,保证离开子应用后样式不残留污染主应用——这是"动态样式表"隔离的基础保障。


应用间通信机制 ​

微前端拆分后,主子、子子之间需要共享登录态、用户信息、主题等。常见通信方式如下:

① props 下发(主 → 子) ​

主应用注册子应用时通过 props 传数据,子应用在 mount 生命周期里接收:

js
// 主应用
registerMicroApps([
  {
    name: "app1",
    entry: "//localhost:7100",
    container: "#c",
    activeRule: "/app1",
    props: { userInfo: { name: "张三" } },
  },
]);

// 子应用
export async function mount(props) {
  console.log(props.userInfo); // { name: "张三" }
}

② qiankun 全局状态 GlobalState(双向) ​

INFO

qiankun 内置 initGlobalState 提供一个响应式的全局状态池,主子应用都能读写并监听变化,是 qiankun 推荐的通信方式。

js
// 主应用:初始化全局状态
import { initGlobalState } from "qiankun";
const actions = initGlobalState({ user: null, theme: "light" });

actions.onGlobalStateChange((state, prev) => console.log(state, prev));
actions.setGlobalState({ user: { name: "张三" } });

// 子应用:mount 时拿到 actions(挂在 props 上)
export async function mount(props) {
  props.onGlobalStateChange((state) => console.log("状态更新", state));
  props.setGlobalState({ theme: "dark" });
}

③ 发布订阅 / EventBus(去中心化) ​

适用于任意框架,自实现一个全局事件总线挂到 window,主子应用共享:

js
class EventBus {
  constructor() {
    this.events = {};
  }
  on(name, fn) {
    (this.events[name] ||= []).push(fn);
  }
  emit(name, data) {
    (this.events[name] || []).forEach((fn) => fn(data));
  }
  off(name, fn) {
    this.events[name] = (this.events[name] || []).filter((f) => f !== fn);
  }
}
window.__BUS__ = new EventBus();

通信方式对比 ​

方式方向耦合度适用场景
props主 → 子低初始化数据下发
GlobalState双向中qiankun 全局共享态(登录/主题)
EventBus任意低跨应用事件、框架无关
URL / 路由参数任意最低简单参数、可分享的状态
localStorage任意低持久化、跨标签页(注意隔离)

通信原则

优先用低耦合的方式(props / URL)。全局状态虽方便,但滥用会让子应用间产生隐式依赖,退化成"分布式单体"。约定好数据契约、避免子应用直接读写彼此内部状态。

资源预加载与性能优化 ​

微前端最大的性能痛点是切换子应用时的白屏。优化手段:

核心优化清单

  1. 预加载(prefetch):start({ prefetch: true }) 在主应用空闲时提前拉取其它子应用资源
  2. 公共依赖共享:主子应用通过 externals / MF shared 复用同一份 React / Vue,避免重复加载
  3. 子应用保活(keep-alive):切换时不销毁而是隐藏,减少重复渲染开销
  4. 按需加载:子应用内部路由级懒加载,减小首屏包体积
  5. CDN + 强缓存:子应用静态资源上 CDN,合理设置缓存策略
  6. 骨架屏 / loading:切换过程展示占位,优化白屏体感
js
// 精细化预加载:仅预取指定子应用
import { prefetchApps } from "qiankun";
prefetchApps([{ name: "app1", entry: "//localhost:7100" }]);

Module Federation 实战 ​

除了 qiankun 这类"应用级"框架,Module Federation 提供了更细粒度的"模块级"共享,越来越多项目用它做微前端。

主应用(host)配置 ​

js
// webpack.config.js
const { ModuleFederationPlugin } = require("webpack").container;

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: "host",
      remotes: {
        // 远程应用别名 → 名称@远程入口地址
        app1: "app1@http://localhost:3001/remoteEntry.js",
      },
      shared: {
        react: { singleton: true, requiredVersion: "^18.0.0" },
        "react-dom": { singleton: true },
      },
    }),
  ],
};

远程应用(remote)配置 ​

js
new ModuleFederationPlugin({
  name: "app1",
  filename: "remoteEntry.js", // 供 host 加载的入口清单
  exposes: {
    "./Button": "./src/Button", // 暴露的模块
    "./App": "./src/App",
  },
  shared: {
    react: { singleton: true },
    "react-dom": { singleton: true },
  },
});

主应用消费远程模块 ​

jsx
import React, { Suspense } from "react";
const RemoteButton = React.lazy(() => import("app1/Button"));

export default function App() {
  return (
    <Suspense fallback={<div>加载中...</div>}>
      <RemoteButton />
    </Suspense>
  );
}

shared 的意义

singleton: true 保证共享库全局只有一个实例——这对 React 尤其重要(多份 React 会导致 Hooks 报错)。MF 会在运行时协商版本,优先复用已加载的兼容版本,避免重复下载。

MF 没有沙箱

Module Federation 不提供 JS / CSS 隔离。它擅长依赖共享与模块复用,但全局变量、样式冲突需自行处理。若既要模块共享又要隔离,可考虑 qiankun + MF 组合,或 Vite 生态的 vite-plugin-federation。

常见问题与踩坑 ​

高频报错速查

以下是接入微前端时最常遇到的问题,按出现频率排序。

问题原因解决
子应用资源 404publicPath 用了相对路径运行时设置 __webpack_public_path__ 为 qiankun 注入的地址
子应用 HTML 拉取失败(CORS)子应用未开启跨域dev-server 与生产资源配置 Access-Control-Allow-Origin
qiankun 找不到生命周期钩子打包格式不对libraryTarget: 'umd' + 设置 library 为应用名
样式互相污染未开启样式隔离开 experimentalStyleIsolation 或用 Shadow DOM
Antd 弹窗样式丢失Shadow DOM 隔离了挂 body 的元素配置 getPopupContainer 挂回容器内
路由冲突 / 404子应用 base 未设置子应用路由 base 设为 activeRule 前缀
切换子应用内存泄漏unmount 未清理副作用定时器 / 监听 / 订阅在 unmount 中逐一清理
多个子应用全局变量冲突用了单例沙箱使用多例 Proxy 沙箱(qiankun 默认)
React 多实例 Hooks 报错加载了多份 ReactMF shared 设 singleton: true

JS 沙箱面试高频总结 ​

一句话回答

  • 快照沙箱:拍照 → 改 → 对比还原,兼容老浏览器但性能差、只支持单例
  • 单例 Proxy 沙箱:Proxy 记录增改并操作真实 window,性能好但多应用冲突
  • 多例 Proxy 沙箱:每个子应用独立 fakeWindow,读回退、写隔离,支持多实例并存(qiankun 现役方案)

CSS 隔离面试高频总结 ​

一句话回答

  • Shadow DOM:原生影子边界最彻底,但挂 body 的弹窗会失效
  • 运行时 CSS 前缀:给选择器加属性作用域,兼容好但覆盖不全
  • 动态样式表:mount 插入 / unmount 移除 <style>,防止样式残留

结语

微前端的本质是用工程复杂度换取组织协作效率与技术演进自由。理解"加载 / 隔离 / 通信"三大核心问题,再看 qiankun、micro-app、无界如何各自取舍,就能在实际项目中做出合理的架构决策。