前言
在前端开发项目中,不可避免的总会和 iframe
进行打交道,我们通常会使用 postMessage
实现消息通讯。
如果存在下面情况:
当面对这种复杂的情况的时候,通讯不可避免成为复杂问题。
快速开始
为了解决这复杂的问题,我开发了 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/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
收到响应