引言

微前端是微服务概念在前端的应用。在微前端架构下,一个大型单一的前端应用被拆分成多个小型、独立的子应用,这些子应用可以独立开发、独立部署、独立运行,从而带来更加灵活高效的项目开发和管理。

作为现代前端开发的趋势,许多企业的技术栈融入了微前端,有些是选择成熟的框架如 Qiankun、Micro-App 或 Single-SPA,有些是自研解决方案。

微前端最简单的实现形式是基于Iframe。本文主要从一个简单的微前端案例入手,通过这个简单的Demo,让我们对微前端框架及其设计理念有个初步的理解。

Demo源代码讲解

目录结构

1
2
3
4
5
6
7
8
9
10
11
12
13
iframe-demo
├─ EventBus
│ └─ EventBus.js
├─ cart
│ ├─ index.html
│ ├─ script.js
│ └─ style.css
├─ catalog
│ ├─ index.html
│ └─ script.js
├─ composition.html
├─ script.js
└─ style.css

这个Demo的目录结构非常简单,主要分为三部分:

  • 两个子应用cart、catalog
  • 全局事件总线
  • 主应用

可以看到这个简单的项目已经体现了微前端的核心设计:应用管理、隔离和共享。

应用管理

composition.html文件我们可以看出这个项目的应用管理非常简单,两个子应用通过iframe标签加载,共同存在于同一个页面。

1
2
3
4
5
<link rel="stylesheet" href="style.css" />
<iframe src="./catalog/index.html" id="catalog"></iframe>
<iframe src="./cart/index.html" id="cart"></iframe>
<script src="script.js"></script>

隔离

由于使用的是iframe标签,两个子应用天然地存在CSS和JS隔离,不会有样式和运行时的冲突。

微前端 why iframe not div?

为什么微前端使用iframe做隔离,而不是普通div

主要是因为iframe在浏览器中具有天然的隔离环境,主要表现在:

  1. JavaScript 隔离iframe 内的 JavaScript 运行在独立的全局上下文中。这意味着,iframe 内的 JavaScript 变量和函数不会影响到主页面,反之亦然。这种特性对于确保应用间不会相互干扰是非常重要的。
  2. 样式隔离iframe 内的样式不会影响到外部页面。每个 iframe 有自己的文档流,所以里面的 CSS 只会作用于 iframe内部,不会泄露到外部,这保证了样式的独立性和一致性。
  3. DOM 隔离:每个 iframe 都有自己的 DOM 树,与主页面的 DOM 树完全隔离。这意味着,iframe 内的 DOM 操作不会影响到外部页面,减少了应用间的直接 DOM 冲突。

这些隔离特性对于普通的div标签是没有的。

共享(通信)

虽然iframe能实现简单的微前端架构,但是通过代码中设计的全局事件总线(EventBus.js文件),我们可以看出基于iframe的微前端需要单独设计跨域通信方式。

本案例主要是通过PostMessage方法实现的。PostMessage 是一种安全地实现不同浏览器窗口(包括弹出窗口和iframe)间通信的方式。允许不同源(origin)的窗口进行数据交换,从而克服了同源策略的限制。适用于多种场景,如页面与弹出窗口、页面与嵌入的iframe、甚至是不同的web workers之间的通信。

基本用法
包括两部分:发送消息和接收消息。

发送消息: window.postMessage
这个方法接受两个参数:要发送的消息和消息接收方的源(origin)。但出于安全考虑,这通常不推荐。

1
otherWindow.postMessage(message, targetOrigin); // otherWindow 是另一个窗口的引用,例如一个 iframe 或者通过 window.open 打开的窗口
  • message:你想要发送的数据。
  • targetOrigin:指定目标窗口的源,例如 “https://example.com“。这是一种安全机制,用来确保消息只被预期的接收者接收。如果你不关心目标窗口的源,也可以使用 “*” 作为通配符。

接收消息:在目标窗口添加一个事件监听器来监听 message 事件

1
2
3
4
5
6
7
8
9
window.addEventListener("message", (event) => {
// 可以通过 event.origin 检查消息来源的源
if (event.origin !== "<http://expected-origin.com>") {
return; // 忽略来自不期望源的消息
}

// 处理接收到的数据
console.log(event.data);
});

在本项目中,composition主应用的JS脚本中注册了message事件监听,将基座应用接收到的消息转发给所有子应用。

1
2
3
4
5
6
7
const sendReceivedMessageToAllIframes = (event) => {
document
.querySelectorAll('iframe')
.forEach((iframe) => iframe.contentWindow.postMessage(event.data, '*'));
};
window.addEventListener('message', sendReceivedMessageToAllIframes);

catalog子应用中,注册了一个全局事件总线,并且给按钮注册了点击事件,当按钮点击后,会触发productToCart事件,在该事件中,会向主应用发送消息。

最终该消息会经过主应用,接着由cart子应用中的全局事件总线接收,执行add方法。

大致的交互示意图如下所示:

Untitled

总结

一个 iframe 具备四层能力:文档的加载能力、HTML 的渲染能力、独立执行 JavaScript 的能力、隔离样式的能力。这些能力,其实也是微前端项目设计的核心要求。通过学习本项目,我们可以很好地初步学习微前端,为后来深入学习Single-SPA、qiankun等框架提供理论基础。