This package has been deprecated

Author message:

Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.

event-bus-iframe
TypeScript icon, indicating that this package has built-in type declarations

0.0.8 • Public • Published

EventBus

适用于 iframe 的基座与子应用通信工具,支持跨浏览器标签页通信

安装

npm install event-bus-iframe

原理

  • 基座和子应用之间的通信是基于浏览器 postMessage API 的
  • 同源多标签页之间的通信、同源iframe之间的通信是基于 sharedWorker 的
  • registryApi 仅是在基座的 bus 实例上注册一个回调函数,在调用的时候实时获取数据
  • getData 函数返回 promise 类型的数据,原理是模拟了【子应用发送post事件 -> 基座通过 on 监听事件 -> 执行回调函数 -> 基座发送 post 事件 -> 子应用通过 on 监听事件】这么一个过程

使用说明

  1. 在 utils 文件夹下创建 bus.ts 文件用于初始化 EventBus 对象
// 基座中使用
import EventBus, { type BusOption } from 'event-bus-iframe'
const option: BusOption = {
  name: 'app-main', // 当前应用的 name
  children: ['app-sub', 'app-sub2'],  // 可选,仅基座需要配置,子应用列表
  // workerPath: '/worker.js', // 可选,当需要开启跨页签通信时,传入 worker.js 的地址
  // devMode: import.meta.env.DEV, // 可选,开启时,可在控制台输出主子应用之间通信日志,生产环境关闭
}
const bus = new EventBus(option)

export default bus
// iframe 子应用中使用
import EventBus, { type BusOption } from 'event-bus-iframe'
const option: BusOption = {
  name: 'app-sub', // 当前应用的 name
  // workerPath: `/${import.meta.env.BASE_URL}/worker.js`, // 可选,当需要开启多 iframe 通信时,传入 worker.js 的地址。子应用不要使用框架的 workerPath
  // devMode: import.meta.env.DEV, // 可选,开启时,可在控制台输出主子应用之间通信日志,生产环境关闭
}
const bus = new EventBus(option)

export default bus
  1. 使用
import bus, { type TransferData } from '@/utils/bus' // 初始化 EventBus

// 注册监听事件,包括监听来自基座/子应用 post 事件和自身的 emit 事件
bus.on<CustomType>('msg', (res) => {
  console.log('基座收到 msg: ', res)
})

// 向基座/子应用发消息,使用 post
const data: TransferData = {
  type: 'msg',
  data: {
    msg: 'message'
  }
}
// 基座向子应用发送消息,第二个参数为目标子应用名称,如果第二个参数为字符串 global,则向子应用列表中的所有子应用广播消息
// 子应用向基座发送消息,第二个参数为空
bus.post(data, 'app-vite')

// 向自身发消息 —— EventBus 也支持当前应用中的发布订阅模式,类似于 mitt 或 this.$bus
bus.emit('msg', { msg: 'message'})

如果需要使用 script 标签引入的方式来使用,可将 lib 资源下的 main.js 文件上传到自己的静态服务来引入使用,引入方式参考:

<!-- 将 main.js 存放在当前工程公共目录 /event-bus 中 -->
<script src="/event-bus/main.js">

<script>
const bus = new EventBus(option)
</script>

更多配置

  • api 调用 api 的设计借鉴了 ajax 的思想,子应用通过 bus.getData('api-path', params) 向基座发起一个数据请求时,基座会根据 api-path 组织需要的数据并返回,这个过程是基于 Promise 的,所以子应用可以在任何时候,任何位置获取基座可提供的数据。
// 基座 bus.ts
// 基座注册一个 user-info 的 api ,子应用可以通过 bus.getData('user-info') 来随时获取 return 的数据
bus.registryApi('user-info', async () => { 
  return {
    username: 'Job',
    age: 18
  }
})
// 子应用中调用
interface IUserInfo {
  token: string
}
bus.getData<IUserInfo>('user-info').then(res => {
  console.log(res) // res 是符合 ApiData 结构的数据
})
  • 动态更新子应用列表,默认会覆盖初始化的子应用列表,可通过第二个参数 isMerge 控制是否对子应用列表进行合并
const list: string[] = ['app-app1', 'app-app2']
bus.setChildrenApp(list)
bus.setChildrenApp(list, true) // 合并已有的子应用列表

使用注意事项

  • 初始化的时候 name 不能为空,否则会报错:EventBus: The name cannot be empty during initialization
  • 基座向子应用发送消息,需要确保子应用已渲染;
  • 基座中使用时需要特别注意!基座向子应用发送消息基于 iframe 的 name 属性,所以编码的时候,需要确保 bus.post(data, target) 时 target 值与 iframe 的 name 属性一致,注册在 children 列表中的子应用名称与 iframe 的 name 属性一致。

保留字符

  • global : post 的第二个参数如果为 global,则向子应用列表中的所有子应用以及父应用发送消息,所以子应用命名不可为 global ;
  • get-data / set-data : 用于服务基座注册接口,子应用调用接口的通信服务,事件的 type 不要使用这两个保留名称;
  • page-unload : 用于页面刷新或关闭时通知 shared-worker 清除缓存,事件的 type 不要使用该名称;
  • appName : 业务中 appName 被用于识别子应用,子应用路由使用查询参数时应避免使用 appName 参数名;

类型说明

/**
 * 约定数据格式
 * type - 事件类型
 * from - 事件发起方
 * target - 事件目标方
 * timestamp - 事件发送时间戳
 * data - 事件传递的数据
 */
export interface TransferData<T = unknown> extends Record<PropertyKey, unknown> {
    type: string;
    from?: string;
    target?: string;
    timestamp?: number;
    data?: T;
}
export interface ApiData<T = unknown> extends TransferData {
    type: EventBusType.GetData | EventBusType.SetData;
    api?: string;
    promise?: 'resolve' | 'reject';
    params?: object;
    data?: T;
}

/**
 * event-bus 实例化配置
 * name - 唯一标识
 * children - 子应用列表,用于发送 global 事件
 * workerPath - worker 静态文件地址,用于同源多标签页或同源 iframe 通信
 * devMode - 开启时,可以在控制台输出 event-bus 通信的日志
 */
export interface BusOption {
    name: string;
    children?: string[];
    workerPath?: string;
    devMode?: boolean;
}

export declare class EventBus {

    constructor(option: BusOption);
    
    /**
     * 监听事件,同时监听 emit 和 post 的事件
     * @param type 事件类型
     * @param cb 触发时的回调函数,接收符合 TransferData 结构的 res 参数
     */
    on<T>(type: '*', cb: EventCBAll<T>): void;
    on<T>(type: string, cb: EventCB<T>): void;

    /**
     * 发送本地事件
     * @param type 事件类型
     * @param data 传递的数据
     */
    emit<T = unknown>(type: string, data?: T): void;

    /**
     * 向子应用、父应用发送消息
     * @param data 传输的数据
     * @param target 目标 iframe
     * @param byId 是否通过 id 查找 iframe,默认通过 name 查找 iframe
     */
    post<T = unknown>(data: TransferData<T>, target?: 'global' | string | string[], byId?: boolean): void;

    /**
     * 关闭事件监听
     * @param type 事件类型
     * @param cb 关闭监听的回调函数,如果不传 cb ,则关闭该事件类型下的所有函数
     */
    off<T = unknown>(type: string, cb?: EventCB<T>): void;

    /**
     * 清除所有事件监听
     */
    clearAll(): void;

    /**
     * 注册 api,用于 getData / getDataSelf 获取数据
     * @param api api 名称
     * @param cb 子应用调用 api 时的回调函数,cb 返回一个 Promise,resolve 的数据即为 getData 时拿到的数据
     */
    registryApi(api: string, cb: ApiCb): void;

    /**
     * 调用框架提供的 api
     * @param api 框架注册的接口名
     * @param params  需要传递的参数
     * @returns 返回一个 Promise eg. .then(res => res.data)
     */
    getData<T>(api: string, params?: object): Promise<ApiData<T>>;

    /**
     * 浏览器某个标签页或 iframe 调用自己提供的 api
     * @param api 注册的接口名
     * @param params  需要传递的参数
     * @returns 返回一个 Promise eg. .then(res => res.data)
     */
    getDataSelf<T>(api: string, params?: object): Promise<ApiData<T>>;
    
    /**
     * 设置子应用列表
     * @param list 传入的子应用名称列表
     * @param isMerge 是否合并列表,默认替换 event-bus 实例上的 children 列表 (false),为 true 时与 children 列表进行合并。已进行去重处理。
     */
    setChildrenApp(list: string[], isMerge?: boolean): void;
}

Shared Worker

  • sharedworker 是一种特殊的 webworker ,可以由多个同源的页面共享,我们选取 sharedworker 进行跨标签页的通信。
  • 因为 sharedworker 是受同源策略限制的,所以我们约定新打开的页面如果需要跨标签页通信的话,需要保持同源,如果要在新页签打开超链接,可以通过反向代理的方式,在同源代理超链接。
  • 同一个 url 只会创建一个 sharedworker,其他页面再使用相同 url 创建 sharedworker,会复用已创建的 worker。
  • 调试方式: 浏览器输入 chrome://inspect/
  • 目前浏览器支持度不是很高,主流的 web 浏览器基本都支持,移动端浏览器支持欠佳。
  • 在不支持 SharedWorker 的浏览器中,我们通过 onstorage 的方案来进行兼容。
  • 子应用使用 sharedWorker 时,传入独立的的 worker path ,不要和框架共用。

Shared Worker 使用方法

  1. 对于 vue 项目来说,可以在 public 文件夹下创建 worker.js 文件,拷贝 node_modules/@hsmos/event-bus/src/worker.js 中的内容
// node_modules/@hsmos/event-bus/src/worker.js
const portList = []
onconnect = e => {
    const port = e.ports[0]
    portList.push(port)
    port.onmessage = m => {
        const data = m.data
        if (data && data.type) {
            if (data.type === 'page-unload') {
                // 页面刷新或关闭时清除 port 缓存
                const index = portList.findIndex(v => v === port)
                if (index !== -1) portList.splice(index, 1)
            } else {
                // 排除自身 port,向其他 port 发消息
                portList.filter(v => v !== port).forEach(v => {
                    v.postMessage(data)
                })
            }
        }
    }
}
  1. 在初始化 EventBus 的时候,option 中传入相应的 workerPath 。

  2. 使用 bus.emit() / bus.on() 即可在同源页签中发送/监听消息事件。

报错信息说明

  • EventBus: The name cannot be empty during initialization 出现这个报错一般是在初始化 EventBus 的时候没有传入 name,或 name 为空,常用的获取 name 的方式为通过 iframe 的 src 参数动态获取,此时应该检查初始化 EventBus 的地方和 iframe src
  • EventBus: Please check if the iframe[name]: [' + iframeName + '] exists 出现该报错一般是基座给子应用发送消息的时候,根据 post() 第二个参数 target 查找指定的 iframe 没有找到,此时应该检查基座发送事件的参数及 iframe 的 name 属性
  • EventBus: Current tabs/iframes not exist api: 出现该报错一般是在执行 getDataSelf 方法的时候,没有找到注册的 api,此时应该检查 registryApi 是否注册成功

更新日志

  • v0.0.2 - 2023-06-03
    • EventBus 工具发布,支持 iframe 基座与子应用通信,支持跨标签页通信;
  • v0.0.3 - 2023-06-10
    • 支持同源多标签页、同源多 iframe 之间注册接口(registryApi)通信;
  • v0.0.4 - 2023-07-11
    • off 接口第二个参数选传;
  • v0.0.5 - 2023-07-31
    • registryApi / getData 支持 reject ;
    • Post 第二个参数为 global 时,不再向父级发送消息,而且只向存活的子应用发送消息;
    • 不兼容更新,registryApi 回调函数新增入参 from;
    • 解决框架在给子应用发消息时,子应用通过 sharedworker 重复通信的问题;
  • v0.0.6 - 2023-08-03
    • 页面关闭和刷新的时候,清除缓存的 worker 逻辑修正;
  • v0.0.7 - 2023-08-22
    • 解决子应用给框架发消息时,多个浏览器标签页同时接收到的问题;
  • v0.0.8 - 2023-08-23
    • 新增日志配置, options.devMode: true 或 localStorage 中配置 show-event-bus-log;

Readme

Keywords

none

Package Sidebar

Install

npm i event-bus-iframe

Weekly Downloads

1

Version

0.0.8

License

none

Unpacked Size

39 kB

Total Files

10

Last publish

Collaborators

  • wudandong