首页首页
前端
非前端
辅助
Github
前端
非前端
辅助
Github
  • 前端基础

    • HTML基础
    • CSS基础
    • JS基础
    • ES6基础
    • HTTP基础
    • 前端缓存
    • 页面性能
    • 数据结构基础
    • 我的文章
  • 前端进阶

    • Vue2基础
    • Vue2进阶
    • Vue3基础
    • Vue3进阶
    • React基础
    • React进阶
    • React18新特性
    • Vue和React对比
    • RN基础
    • RN环境搭建和打包发布
    • 打包工具
    • TS基础
    • Nuxt基础
    • 小程序基础
    • 微前端基础
    • uni-app基础
    • 业务相关
    • 低代码相关
  • 前端代码练习

    • CSS代码练习
    • JS代码练习
    • 算法代码练习
  • 前端代码技巧

    • 工具库
    • 工具函数
    • CSS动画库
    • CSS代码技巧
    • JS代码技巧
    • 项目技巧

1.前言

微前端架构是为了在解决单体应用在一个相对长的时间跨度下,由于参与的人员、团队的增多、变迁,从一个普通应用演变成一个巨石应用(Frontend Monolith)后,随之而来的应用不可维护的问题。这类问题在企业级 Web 应用中尤其常见。

微前端框架内的各个应用都支持独立开发部署、不限技术框架、支持独立运行、应用状态隔离但也可共享等特征。

从框架的应用隔离实现方案、实战、优缺点三个方面探一探各个框架

2.iframe

在没有各大微前端解决方案之前,iframe是解决这类问题的不二之选,因为iframe提供了浏览器原生的硬隔离方案,不论是样式隔离、js 隔离这类问题统统都能被完美解决。

但他的最大问题也在于他的隔离性无法被突破,导致应用间上下文无法被共享,随之带来的开发体验、产品体验的问题:

  • 1、url 不同步,浏览器刷新 iframe url 状态丢失、后退前进按钮无法使用。
  • 2、UI 不同步,DOM 结构不共享,弹窗只能在iframe内部展示,无法覆盖全局
  • 3、全局上下文完全隔离,内存变量不共享,iframe 内外系统的通信、数据同步等需求,主应用的 cookie
  • 4、慢, 每次子应用进入都是一次浏览器上下文重建、资源重新加载的过程。

3.iframe与父网页通信

同源 iframe 可以无限制通信,不同源 iframe 只能通过 postMessage 进行有限通信,且受同源策略严格限制。
在微前端、第三方小部件集成、支付网关等场景中,postMessage是跨域通信的标准解决方案

通信方式同源支持跨域支持特点
contentWindow✅❌直接访问
contentDocument✅❌直接访问
postMessage✅✅安全,异步
MessageChannel✅✅双向通道
BroadcastChannel✅✅广播通信
localStorage✅❌存储共享

一. 同源 iframe 通信(完全访问)

  • 父页面 → 子 iframe
<!-- 父页面 parent.html -->
<iframe id="myIframe" src="child.html"></iframe>

<script>
// 等待 iframe 加载完成
document.getElementById('myIframe').onload = function() {
  const iframeWindow = this.contentWindow;  // 获取 iframe 的 window
  const iframeDocument = this.contentDocument;  // 获取 iframe 的 document
  
  // 1. 直接调用 iframe 中的函数
  iframeWindow.childFunction('来自父页面的消息');
  // 2. 直接修改 iframe 的 DOM
  iframeDocument.getElementById('child-element').style.color = 'red';
  // 3. 直接访问 iframe 的变量
  console.log('子页面变量:', iframeWindow.childVariable);
  // 4. 通过事件传递
  iframeWindow.dispatchEvent(new CustomEvent('parent-message', {
    detail: { message: 'Hello from parent' }
  }));
};
</script>
  • 子 iframe → 父页面
<!-- 子页面 child.html -->
<script>
// 1. 直接访问父页面
window.parent.parentFunction('来自子页面的消息');
// 2. 访问顶级窗口
window.top.topLevelFunction();
// 3. 修改父页面 DOM
window.parent.document.getElementById('parent-element').innerText = '已修改';
// 4. 通过事件传递
window.parent.dispatchEvent(new CustomEvent('child-message', {
  detail: { message: 'Hello from child' }
}));
// 5. 通过 opener(如果是从父页面打开的)
if (window.opener) {
  window.opener.receiveFromChild('通过opener通信');
}
</script>

二. 跨域 iframe 通信(受限制)

  • 使用 postMessage(最安全)
<!-- 父页面 parent.html -->
<iframe id="crossOriginIframe" src="https://other-domain.com/child.html"></iframe>

<script>
const iframe = document.getElementById('crossOriginIframe');
// 1. 父页面发送消息到子 iframe
iframe.onload = function() {
  // 发送消息到 iframe
  iframe.contentWindow.postMessage(
    {
      type: 'greeting',
      data: 'Hello from parent',
      timestamp: Date.now()
    },
    'https://other-domain.com'  // 目标源,可以是 '*' 但建议指定
  );
};
// 2. 父页面接收来自子 iframe 的消息
window.addEventListener('message', function(event) {
  // 安全检查:验证消息来源
  if (event.origin !== 'https://other-domain.com') {
    return;  // 拒绝处理来自非预期源的消息
  }
  console.log('收到消息:', event.data);
  console.log('来源:', event.origin);
  console.log('来源窗口:', event.source);
  if (event.data.type === 'response') {
    console.log('处理响应:', event.data.message);
  }
}, false);
</script>
<!-- 子页面 child.html (在 other-domain.com) -->
<script>
// 1. 子 iframe 接收来自父页面的消息
window.addEventListener('message', function(event) {
  // 重要:验证消息来源
  if (event.origin !== 'https://parent-domain.com') {
    return;  // 拒绝非信任源的消息
  }
  console.log('子页面收到:', event.data);
  // 2. 子 iframe 发送响应回父页面
  event.source.postMessage(
    {
      type: 'response',
      message: 'Hello from child',
      originalMessage: event.data
    },
    event.origin  // 发送回消息来源
  );
}, false);

// 3. 主动发送消息到父页面
function sendToParent() {
  window.parent.postMessage(
    {
      type: 'notification',
      message: '主动发送的消息'
    },
    'https://parent-domain.com'  // 目标源
  );
}
</script>

三. 高级通信模式

  • 使用 MessageChannel(双向通道)
// 父页面
const iframe = document.getElementById('myIframe');
const channel = new MessageChannel();

// 监听端口1的消息
channel.port1.onmessage = function(event) {
  console.log('父页面收到:', event.data);
  // 回复消息
  if (event.data.type === 'request') {
    channel.port1.postMessage({
      type: 'response',
      data: 'Here is your data'
    });
  }
};
// 将端口2传递给 iframe
iframe.onload = function() {
  iframe.contentWindow.postMessage(
    { type: 'INIT_PORT', port: channel.port2 },
    '*',
    [channel.port2]  // 转移端口所有权
  );
};
// 子 iframe
let messagePort;
window.addEventListener('message', function(event) {
  if (event.data.type === 'INIT_PORT') {
    messagePort = event.ports[0];
    messagePort.onmessage = function(e) {
      console.log('子页面收到:', e.data);
    };
    // 开始通信
    messagePort.postMessage({
      type: 'request',
      data: '我需要一些数据'
    });
  }
});
  • 使用 BroadcastChannel(广播通信)
// 所有页面(父页面、子iframe、其他标签页)
const channel = new BroadcastChannel('app-channel');
// 发送消息
channel.postMessage({
  type: 'user-update',
  data: { userId: 123, name: '张三' }
});
// 接收消息
channel.onmessage = function(event) {
  console.log('收到广播:', event.data);
  if (event.data.type === 'user-update') {
    updateUI(event.data.data);
  }
};

4.single-spa

Single-spa 实现了一套生命周期,开发者需要在相应的时机自己去加载对应的子应用。 它做的事情就是注册子应用、监听 URL 变化,然后加载对应的子应用js,执行对应子应用的生命周期流程。

优点:

  • 敏捷性 - 独立开发、独立部署,微应用仓库独立,前后端可独立开发,部署完成后主框架自动完成同步更新;
  • 技术栈无关,主框架不限制接入应用的技术栈,微应用具备完全自主权;
  • 增量升级,在面对各种复杂场景时,我们通常很难对一个已经存在的系统做全量的技术栈升级或重构,而微前端是一种非常好的实施渐进式重构的手段和策略.

缺点:

  • 需要自己去加载子应用
  • 不支持 Javascript 沙箱隔离,需要自己去使用single-spa-leaked-globals之类的库去隔离
  • 不支持css隔离,需要自己使用single-spa-css库或者postcss等去解决样式冲突问题
  • 无法预加载

5.qiankun

阿里的qiankun 是一个基于 single-spa 的微前端实现库,孵化自蚂蚁金融,帮助大家能更简单、无痛的构建一个生产可用微前端架构系统。

实现方案

  • single-spa是基于js-entry方案,而qiankun 是基于html-entry 及沙箱设计,使得微应用的接入 像使用 iframe 一样简单。
  • 主应用监听路由,加载对应子应用的html,挂载到主应用的元素内,然后解析子应用的html,从中分析出css、js再去沙盒化后加载执行,最终将子应用的内容渲染出来。
  • qiankun实现样式隔离有两种模式可供开发者选择:
  • strictStyleIsolation: 这种模式下 qiankun 会为每个微应用的容器包裹上一个 shadow dom 节点,从而确保微应用的样式不会对全局造成影响。
  • experimentalStyleIsolation: 当 experimentalStyleIsolation 被设置为 true 时,qiankun 会改写子应用所添加的样式,会为所有样式规则增加一个特殊的选择器规则,来限定其影响范围
  • qiankun实现js隔离,采用了两种沙箱,分别为基于Proxy实现的沙箱和快照沙箱,当浏览器不支持Proxy会降级为快照沙箱

优点

  • html entry的接入方式,不需要自己写load方法,而是直接写子应用的访问链接就可以。
  • 提供js沙箱
  • 提供样式隔离,两种方式可选
  • 资源预加载,在浏览器空闲时间预加载未打开的微应用资源,加速微应用打开速度。
  • 社区活跃
  • umi 插件,提供了 @umijs/plugin-qiankun 供 umi 应用一键切换成微前端架构系统 除了最后一点拓展以外,微前端想要达到的效果都已经达到。
  • 应用间通信简单,全局注入
  • 路由保持,浏览器刷新、前进、后退,都可以作用到子应用

缺点

  • 改造成本较大,从 webpack、代码、路由等等都要做一系列的适配
  • 对 eval 的争议,eval函数的安全和性能是有一些争议的:MDN的eval介绍;
  • 无法同时激活多个子应用,也不支持子应用保活
  • 无法支持 vite 等 ESM 脚本运行

参考

最后更新: 2025/12/28 22:42
Prev
小程序基础
Next
uni-app基础