前言
在前端开发项目中,不可避免的总会和 iframe
进行打交道,我们通常会使用 postMessage
实现消息通讯。
如果存在下面情况:
iframe
父子通讯iframe
同层级通讯iframe
嵌套层级通讯
当面对这种复杂的情况的时候,通讯不可避免成为复杂问题。
快速开始
为了解决这复杂的问题,我开发了 iframe-bridge 来帮助大家优雅的解决这类问题。
bash
npm install bridge-iframe
# pnpm
pnpm install bridge-iframe
# yarn
yarn add bridge-iframe
假设页面层级如下:
Main
Main/Node1
主页面(Main)
html
<h1>Main</h1>
<iframe src="Node1.html" id="Node1"></iframe>
javascript
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)
html
<h1>Node1</h1>
javascript
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
相对的子页面也创建有名称的桥接对象。 -
还是通过注册一些可以被其他节点调用的方法来实现双通讯的。
实现原理
这里参考了计算机网络的 交换机
的模式来实现跨层级转发。
网络模型
bash
/------> (子节点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
收到响应