前言
在前端开发项目中,不可避免的总会和 iframe 进行打交道,我们通常会使用 postMessage 实现消息通讯。
如果存在下面情况:
- iframe父子通讯
- iframe同层级通讯
- iframe嵌套层级通讯
当面对这种复杂的情况的时候,通讯不可避免成为复杂问题。
 
 
快速开始
为了解决这复杂的问题,我开发了 iframe-bridge 来帮助大家优雅的解决这类问题。
npm install bridge-iframe
# pnpm
pnpm install bridge-iframe
# yarn
yarn add bridge-iframe
假设页面层级如下:
- Main- Main/Node1
 
主页面(Main)
<h1>Main</h1>
<iframe src="Node1.html" id="Node1"></iframe>
import { IFrameBridge, IFrameMessage } from 'bridge-iframe';// 创建桥接对象
const bridge = new IFrameBridge;
// 连接直接下属节点 Node1 关联 iframe 窗口
birdge.ifrme('Node1', document.getElementById('Node1'));// 提供给其他 iframe 节点调用的方法(可以定义无数个)
birdge.on('say', async (vo: IFrameMessage) => {vo.getData(); // 获取请求数据vo.getResult(); // 获取响应数据return '来自于 Main';
});// 等待桥接初始化完成
birdge.ready(async () => {console.log('Main 初始化完成!!!');
});// 等待 Node1 节点桥接完成
birdge.ready('Node1', async () => {console.log('Watch Node1 初始化完成!!!');// 请求 Node1 的 say 方法birdge.request({name: 'Node1',method: 'say',}).then((vo: any) => {console.log('在 Main 中请求 Node1.say 方法', vo);}).catch((err: any) => {console.log('出现错误', err);});
});// 窗口销毁时
bridge.destroy();
子页面(Node1)
<h1>Node1</h1>
import { IFrameBridge } from 'bridge-iframe';// 创建桥接对象
const bridge = new IFrameBridge({ name: 'Node1' });// 提供给其他 iframe 节点调用的方法(可以定义无数个)
birdge.on('say', async (vo: IFrameMessage) => {return '来自于 Nodeq';
});// 等待桥接初始化完成
birdge.ready(async () => {console.log('Node1 初始化完成!!!');
});// 等待 Node1 节点桥接完成
birdge.ready('Main', async () => {console.log('Watch Main 初始化完成!!!');// 请求 Main 的 say 方法birdge.request({name: 'Main',method: 'say',}).then((vo: any) => {console.log('在 Node1 中请求 Main.say 方法', vo);}).catch((err: any) => {console.log('出现错误', err);});
});// 窗口销毁时
bridge.destroy();
其中关于请求 name 在这里称呼为 iframe node 的 域名 作为通讯标识。
 关于子节点的名称可以为任意名称,但有两类名称是内置的代表特殊作用不能被使用。
- Main作为- 主节点/主窗口的名称地址
- Parent作为只请求上一级节点的名称标识,不管上层节点名字是什么
假设页面层级如下(更复杂):
-  Main- Main/Node1- Main/Node1/Node1-1
- Main/Node1/Node1-2
 
- Main/Node2- Main/Node2/Node2-1
- Main/Node2/Node2-2
 
 
-  在这里还是一样的,创建主页面桥接对象,并关联子页面 iframe相对的子页面也创建有名称的桥接对象。
-  还是通过注册一些可以被其他节点调用的方法来实现双通讯的。 
实现原理
这里参考了计算机网络的 交换机 的模式来实现跨层级转发。
网络模型
                         /——> (子节点1)
(父节点) <———> (节点) <——————> (子节点2)\——> (子节点n) ...
- 每个 节点都有上级节点x1和下级节点xN的结构。
- 消息通讯的核心本质还是 postMessage来实现。
- 当消息经过 节点的时候,通过message.path判断message是向上window.parent.postMessage()传递还是向下iframe.contentWindow.postMessage()传递。
- 当消息经过 节点的时候,会记录经过的路径为tracks{ 节点名称, 转发方向 }[]以此来实现初始地址分配,以及消息返回路径确认。
系统协议
为了实现跨层级通讯,动态为 节点 分配地址,得实现 节点名称映射地址库 来实现。
- 主窗口/页面提供如下内置方法: - @bridge/online通知主窗口注册地址
- @bridge/domain获取名称映射地址
- @bridge/mapping获取所有映射地址
 
- 所有窗口/页面提供如下内置方法: - @bridge/ready节点准备好了吗?
 
为了方便调用,定义了如下内置地址:
- Main请求主窗口地址
- Parent向上级请求窗口(无论层级高低都向上级请求)
通讯模拟:
页面层级
- Main- Main/Node1- Main/Node1/Node1-1
- Main/Node1/Node1-2
 
- Main/Node2- Main/Node2/Node2-1
- Main/Node2/Node2-2
 
 
向上请求 Main/Node1/Node1-1 到 Main
- <内置协议获取地址>
- Main/Node1/Node1-1请求 ↑↑↑ 到- Main/Node1- tracks[{Node1-1:U}]
 
- tracks[
- Main/Node1转发 ↑↑↑ 到- Main- tracks[{Node1-1:U}, {Node1:U}]
 
- tracks[
- Main处理逻辑
- Main响应 ↓↓↓ 到- Main/Node1- tracks[{Node1-1:U}]
 
- tracks[
- Main/Node1转发 ↓↓↓ 到- Main/Node1/Node1-1- tracks[]
 
- Main/Node1/Node1-1收到响应
向下请求 Main 到 Main/Node1/Node1-1
- <内置协议获取地址>
- Main请求 ↓↓↓ 到- Main/Node1- tracks[{Main:D}]
 
- tracks[
- Main/Node1转发 ↓↓↓ 到- Main/Node1/Node1-1- tracks[{Main:D}, {Node1:D}]
 
- tracks[
- Main/Node1/Node1-1处理逻辑
- Main/Node1/Node1-1响应 ↑↑↑ 到- Main/Node1- tracks[{Main:D}]
 
- tracks[
- Main/Node1转发 ↑↑↑ 到- Main- tracks[]
 
- Main收到响应
同级请求 Main/Node1/Node1-1 到 Main/Node1/Node1-2
- <内置协议获取地址>
- Main/Node1/Node1-1请求 ↑↑↑ 到- Main/Node1- tracks[{Node1-1:U}]
 
- tracks[
- Main/Node1转发 ↓↓↓ 到- Main/Node1/Node1-2- tracks[{Node1-1:U}, {Node1:D}]]
 
- tracks[
- Main/Node1/Node1-2处理逻辑
- Main/Node1/Node1-2响应 ↑↑↑ 到- Main/Node1- tracks[{Node1-1:U}]
 
- tracks[
- Main/Node1转发 ↓↓↓ 到- Main/Node1/Node1-1- tracks[]
 
- Main/Node1/Node1-1收到响应
跨级请求 Main/Node1/Node1-1 到 Main/Node2/Node2-1
- <内置协议获取地址>
- Main/Node1/Node1-1请求 ↑↑↑ 到- Main/Node1- tracks[{Node1-1:U}]
 
- tracks[
- Main/Node1转发 ↑↑↑ 到- Main- tracks[{Node1-1:U}, {Node1:U}]
 
- tracks[
- Main转发 ↓↓↓ 到- Main/Node2- tracks[{Node1-1:U}, {Node1:U}, {Main:D}]
 
- tracks[
- Main/Node2转发 ↓↓↓ 到- Main/Node2/Node2-1- tracks[{Node1-1:U}, {Node1:U}, {Main:D}, {Node2:D}]
 
- tracks[
- Main/Node2/Node2-1处理逻辑
- Main/Node2/Node2-1响应 ↑↑↑ 到- Main/Node2- tracks[{Node1-1:U}, {Node1:U}, {Main:D}]
 
- tracks[
- Main/Node2转发 ↑↑↑ 到- Main- tracks[{Node1-1:U}, {Node1:U}]
 
- tracks[
- Main转发 ↓↓↓ 到- Main/Node1- tracks[{Node1-1:U}]
 
- tracks[
- Main/Node1转发 ↓↓↓ 到- Main/Node1/Node1-1- tracks[]
 
- Main/Node1/Node1-1收到响应