Skip to content

微前端

一、为什么需要微前端?

微前端是将不同的功能按照不同的维度拆分成多个子应用,通过主应用来加载这些子应用的技术架构。其核心思想在于"拆分"与"整合",通过分而治之的方式解决复杂前端应用的开发和管理问题。

微前端借鉴了这种分层和模块化的思想,将单一的大型前端应用拆分为多个可以独立开发、独立部署的子应用。

微前端解决的核心问题

  1. 技术栈异构:不同团队可以使用不同的技术栈同时开发一个应用
  2. 独立开发部署:每个团队开发的模块都可以独立开发、独立测试、独立部署
  3. 渐进式迁移:支持现有应用的逐步改造和迁移,降低技术升级风险
  4. 团队自治:各团队可以按照自己的节奏进行开发和发布

实现思路

通过将应用划分为若干个子应用,将子应用打包成独立的模块,在路由切换时动态加载不同的子应用。这种方式使得每个子应用都能保持技术独立性,同时又能整合为一个完整的用户体验。

二、微前端技术方案对比

关键技术考量

在选择微前端技术方案时,需要重点考虑三个核心问题:

  1. 应用拆分策略:如何将单体应用合理拆分为微前端架构
  2. 应用通信机制:子应用之间如何高效、可靠地进行数据交换
  3. 应用隔离方案:如何确保子应用之间的运行环境隔离

iframe 方案

实现原理

  • 通过 iframe 标签加载子应用
  • 使用 postMessage API 进行应用间通信
  • 利用浏览器原生的沙箱机制实现环境隔离

优势

  • 实现简单,技术成熟
  • 天然的样式和脚本隔离
  • 完美的沙箱安全机制

局限性

  • 用户体验较差(弹窗受限、路由状态丢失)
  • 浏览器前进后退行为复杂
  • 性能开销相对较大

Web Components 方案

实现原理

  • 基于自定义 HTML 元素构建子应用
  • 通过 CustomEvent 实现组件间通信
  • 利用 Shadow DOM 提供样式和作用域隔离

优势

  • 浏览器原生支持,无框架依赖
  • 良好的组件封装性
  • 天然的样式隔离机制

局限性

  • 浏览器兼容性要求较高
  • 学习曲线相对陡峭
  • 调试和样式定制相对困难

single-spa 方案

实现原理

  • 基于路由劫持实现应用的动态加载
  • 使用 SystemJS 作为模块加载器
  • 通过 props 属性进行应用间通信
  • 需要自行实现 JS 和 CSS 沙箱机制

优势

  • 框架无关,技术栈灵活
  • 社区生态相对成熟
  • 支持渐进式迁移

局限性

  • 需要一定的学习成本
  • 缺乏内置的沙箱机制
  • 需要对现有应用进行改造适配

Module Federation 方案

实现原理

  • 基于 Webpack 5 的模块联邦特性
  • 通过共享模块机制实现通信
  • 模块级别的代码共享和复用

优势

  • 构建时优化,性能较好
  • 模块共享机制完善
  • 开发体验相对友好

局限性

  • 依赖 Webpack 5 构建工具
  • 缺乏完整的沙箱隔离机制
  • 配置相对复杂

三、微前端框架对比

qiankun

定位:基于 single-spa 的企业级微前端架构解决方案
背景:蚂蚁金服开源,在阿里内部有大规模落地实践
核心理念:应用级微前端,基于路由的应用加载和切换

micro-app

定位:基于 Web Components 的轻量级微前端框架
背景:京东零售开源,追求极简接入和原生体验
核心理念:组件式微前端,将子应用作为自定义 HTML 元素

无界

定位:基于 Web Components + iframe 的高性能微前端框架
背景:腾讯开源,专注于解决 iframe 的体验问题
核心理念:iframe 级隔离,Web Components 级体验

核心实现原理对比

框架技术原理沙箱机制样式隔离路由同步
qiankun路由劫持 + JS EntryJS Proxy 沙箱Shadow DOM / CSS Scoped手动配置
micro-appWeb Components + Module Federation基于 Proxy 的沙箱Web Components 天然隔离自动同步
无界iframe + Web Componentsiframe 原生沙箱iframe 完全隔离自动同步

应用加载方式

qiankun - JS Entry 模式

javascript
// 基于路由的应用注册
registerMicroApps([
  {
    name: "react-app",
    entry: "//localhost:7100",
    container: "#subapp-container",
    activeRule: "/react",
  },
]);

micro-app - 组件式加载

html
<!-- 自定义元素方式 -->
<micro-app
  name="app1"
  url="http://localhost:3000"
  baseroute="/app1"
></micro-app>

无界 - iframe 增强

javascript
// 创建无界应用
Wujie.start({
  name: "vue-app",
  url: "http://localhost:8080",
  el: "#container",
  exec: true,
  sync: true,
});

1. 接入成本

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

2. 隔离能力

JS 隔离

  • qiankun:⭐️⭐️⭐️⭐️⭐️

    • 基于 Proxy 的完善沙箱
    • 支持多实例同时运行
    • 完善的错误边界
  • micro-app:⭐️⭐️⭐️⭐️

    • 基于 Proxy 的沙箱
    • 较好的作用域隔离
    • 部分场景可能存在冲突
  • 无界:⭐️⭐️⭐️⭐️⭐️

    • iframe 级完全隔离
    • 无任何 JS 冲突风险
    • 最安全的隔离方案

CSS 隔离

  • qiankun:⭐️⭐️⭐️⭐️

    • 支持 Shadow DOM 隔离
    • 支持 CSS Scoped 方案
    • 需要手动配置
  • micro-app:⭐️⭐️⭐️⭐️⭐️

    • Web Components 天然隔离
    • 自动样式作用域
    • 无需额外配置
  • 无界:⭐️⭐️⭐️⭐️⭐️

    • iframe 完全样式隔离
    • 零样式污染风险
    • 最彻底的隔离方案

3. 通信机制

qiankun - 基于 props

javascript
// 主应用传递数据
registerMicroApps([
  {
    name: "app1",
    // ...其他配置
    props: {
      userInfo: { name: "张三" },
      onGlobalStateChange: () => {},
    },
  },
]);

// 子应用接收
export async function mount(props) {
  props.onGlobalStateChange((state, prev) => {
    // 状态变更处理
  });
}

micro-app - 基于 CustomEvent

javascript
// 主应用发送数据
microApp.setData("app1", { type: "数据更新", data: {} });

// 子应用监听
window.addEventListener("datachange", (e) => {
  const data = e.detail.data;
});

无界 - 基于事件总线

javascript
// 主应用通信
window.$wujie?.bus.$emit("event", data);

// 子应用通信
window.parent?.$wujie?.bus.$on("event", (data) => {
  // 处理数据
});

4. 路由管理

特性qiankunmicro-app无界
路由模式路由劫持自动同步自动同步
路由同步手动配置自动处理自动处理
路由冲突可能发生自动解决自动解决
前进后退完全支持完全支持完全支持

5. 性能表现

加载性能

  • qiankun:中等

    • 需要加载框架代码
    • 沙箱初始化开销
    • 资源预加载支持
  • micro-app:较好

    • 轻量级框架
    • 原生 Web Components
    • 模块联邦优化
  • 无界:优秀

    • 接近原生 iframe 性能
    • 无额外框架开销
    • 完善的预加载机制

运行时性能

  • qiankun:中等

    • 沙箱代理有一定开销
    • 多实例内存占用较高
  • micro-app:较好

    • 接近原生性能
    • 内存占用较低
  • 无界:优秀

    • iframe 级别性能
    • 内存隔离,互不影响

团队技术能力

框架技术要求维护成本扩展性
qiankun较高,需要理解微前端原理中等,有完善的最佳实践优秀,支持复杂场景扩展
micro-app较低,开箱即用较低,配置简单良好,满足大部分场景
无界较低,简单易用较低,稳定可靠良好,专注于嵌入场景

长期维护考虑

  • qiankun:蚂蚁金服持续维护,社区活跃,适合长期项目
  • micro-app:京东零售团队维护,更新频繁,适合追求新技术
  • 无界:腾讯团队维护,稳定性好,适合对稳定性要求高的项目

社区生态

指标qiankunmicro-app无界
GitHub Stars⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️
文档完善度⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️
社区活跃度⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️
企业使用案例⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️

核心结论

  • 求稳定、功能全 → 选择 qiankun
  • 求快速、轻量级 → 选择 micro-app
  • 求安全、零改造 → 选择 无界

四、JS 沙箱实现

沙箱 防止应用加载的时候 对 widow 造成污染

快照沙箱

js
/**
 * 快照沙箱
 * 缺点:性能较差浪费内存,因为需要遍历存储window对象的所有属性
 */
class SnapshotSandbox {
  constructor() {
    // 记录修改的变量
    this.modifyPropsMap = {};
    // window快照
    this.windowSnapshot = {};
  }

  // 激活沙箱
  active() {
    // 激活沙箱备份window快照
    this.windowSnapshot = {};
    for (let key in window) {
      this.windowSnapshot[key] = window[key];
    }

    // 恢复上次修改的变量
    for (let key in this.modifyPropsMap) {
      window[key] = this.modifyPropsMap[key];
    }
  }
  // 失活沙箱

  inactive() {
    this.modifyPropsMap = {};
    for (let key in window) {
      // 由于在激活沙箱时备份了window快照
      // 所以这里只需要**对比快照**和当前window的变量是否相等
      if (window[key] != this.windowSnapshot[key]) {
        // 记录修改的变量,激活的时候恢复挂载
        this.modifyPropsMap[key] = window[key];
        // 恢复window快照
        window[key] = this.windowSnapshot[key];
      }
    }
  }
}

const snapshotSandbox = new SnapshotSandbox();
snapshotSandbox.active(); // 激活沙箱
window.a = 1;
window.b = 2;
console.log(window.a, window.b); // 1 2
snapshotSandbox.inactive(); // 失活沙箱
console.log(window.a, window.b); // undefined undefined
snapshotSandbox.active(); // 激活沙箱
console.log(window.a, window.b); // 1 2

代理沙箱

js
/**
 * 代理沙箱
 * 缺点:如果两个应用一起运行 window就一个,会导致数据冲突
 */
class ProxySandbox {
  constructor() {
    // 存储新增
    this.addedPropsMap = new Map();
    // 存储window修改之前的原始值
    this.modifyPropsMap = new Map();
    // 所有新增和修改的属性
    this.allPropsMap = new Map();

    const fakeWindow = Object.create(null);
    this.proxy = new Proxy(fakeWindow, {
      get: (target, key) => {
        return window[key];
      },
      set: (target, key, value) => {
        if (!window.hasOwnProperty(key)) {
          // 记录新增的值
          this.addedPropsMap.set(key, value);
        } else if (!this.modifyPropsMap.has(key)) {
          // 记录window修改前的值
          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.addedPropsMap.forEach((value, key) => {
      delete window[key];
    });
    // 恢复修改的属性
    this.modifyPropsMap.forEach((value, key) => {
      window[key] = value;
    });
  }
}

let sandbox = new ProxySandbox();

sandbox.proxy.a = 1;
console.log(window.a, sandbox.proxy.a); // 1 1
sandbox.inactive(); // 失活沙箱
console.log(window.a, sandbox.proxy.a); // undefined undefined
sandbox.active(); // 激活沙箱
console.log(window.a, sandbox.proxy.a); // 1 1

代理沙箱-多例模式

js
/**
 * 代理沙箱-多例模式
 * 通过代理对象模拟操作,不操作真实的window
 */
class ProxySandbox {
  constructor() {
    this.running = false; // 沙箱是否激活
    const fakeWindow = Object.create(null);
    this.proxy = new Proxy(fakeWindow, {
      get: (target, key) => {
        if (!this.running) {
          // 沙箱未激活时,不操作proxy上的值,全部从window上获取
          return window[key];
        }
        return key in target ? target[key] : window[key];
      },
      set: (target, key, value) => {
        if (this.running) {
          // 沙箱激活时,修改proxy上的值
          target[key] = value;
        }
        return true;
      },
    });
  }

  // 激活沙箱
  active() {
    this.running = true;
  }
  // 失活沙箱
  inactive() {
    this.running = false;
  }
}

const sandbox1 = new ProxySandbox();
const sandbox2 = new ProxySandbox();
sandbox1.active(); // 激活沙箱
sandbox1.proxy.a = 1;
console.log(sandbox1.proxy.a); // 1
sandbox1.inactive(); // 失活沙箱
console.log(sandbox1.proxy.a); // undefined