webpage-channel 让不同页面通信像组件通信一样简便

一个轻量级、类型友好的浏览器端消息通信库。

简介

提供统一的事件 API(ononceemitoff等),用于在不同网页上下文之间通信,例如:多标签页、iframe 与 worker 场景。也可以用于跨组件通信。

默认基于 BroadcastChannel,并支持通过适配器扩展到 postMessage 等通信方式。

代码实现不到 200 行,单元测试覆盖率 100%

特性

  • 轻量易用:ononceemitoff 即可完成事件收发。
  • TypeScript 友好:通过 泛型 约束事件名和事件数据类型。
  • 可扩展适配器:默认 BroadcastChannel,可自定义适配器。
  • 可自定义序列化:支持替换 JSON.stringify/parse
  • 错误可观察:提供消息编解码错误与底层消息错误回调。
  • 独立事件系统:抽离了 Event bus 实现,可以只引入该模块,用于组件通信等。

安装

bash 复制代码
pnpm add webpage-channel
# 或
npm i webpage-channel
# 或
yarn add webpage-channel

同域通信

默认基于 BroadcastChannel,同域页面通信时,可以直接使用默认配置。跨域通信请参考跨域通信

1. 定义事件类型

在同域的两个不同页面,都添加以下代码:

ts 复制代码
import { WebpageChannel } from 'webpage-channel';

type Events = {
	'user:update': (payload: { id: string; name: string }) => void;
	'toast:show': (payload: { message: string; type: 'success' | 'error' }) => void;
};

const channel = new WebpageChannel<Events>('app-channel');

2. 监听消息

在接收消息方,添加以下代码:

ts 复制代码
channel.on('user:update', (payload) => {
	console.log('收到用户更新', payload.id, payload.name);
});

3. 发送消息

在发送消息方,添加以下代码:

ts 复制代码
const ok = channel.emit('user:update', { id: 'u1', name: 'Alice' });
if (!ok) {
	console.warn('消息发送失败');
}

4. 取消监听和销毁(可选)

ts 复制代码
const onToast = (payload: { message: string; type: 'success' | 'error' }) => {
	console.log(payload.message);
};

channel.on('toast:show', onToast);
channel.once('toast:show', (payload) => {
	console.log('仅触发一次:', payload.message);
});
channel.off('toast:show', onToast); // 移除指定监听器
channel.off('toast:show'); // 移除该事件全部监听器

channel.close(); // 清空监听并关闭底层通道

跨域通信

跨域通信可以使用内置适配器 PostMessageAdapter ,适合父页面与 iframe、弹窗窗口等基于 window.postMessage 的场景。

PostMessageAdapter 构造参数:

  • targetWindow: Window:目标窗口对象(如 iframe.contentWindowwindow.parent)。
  • targetOrigin: string:目标来源(例如 https://example.com,或开发环境 *)。

1. 父页面发送消息给 iframe

ts 复制代码
import { PostMessageAdapter, WebpageChannel } from 'webpage-channel';

type Events = {
	'auth:token': (payload: { token: string }) => void;
};

const iframe = document.getElementById('child-frame') as HTMLIFrameElement;
const adapter = new PostMessageAdapter(iframe.contentWindow!, 'https://child.example.com');
const channel = new WebpageChannel<Events>('iframe-channel', undefined, adapter);

channel.emit('auth:token', { token: 'abc123' });

2. iframe 接收消息

ts 复制代码
import { PostMessageAdapter, WebpageChannel } from 'webpage-channel';

type Events = {
	'auth:token': (payload: { token: string }) => void;
};

const adapter = new PostMessageAdapter(window.parent, 'https://parent.example.com');
const channel = new WebpageChannel<Events>('iframe-channel', undefined, adapter);

channel.on('auth:token', (payload) => {
	console.log('收到 token:', payload.token);
});

3. 注意事项

  • 生产环境请避免使用 * 作为 targetOrigin
  • PostMessageAdapter 内部会同时校验 e.origin === targetOrigine.source === targetWindow
  • WebpageChannel 的第一个参数 name ,父子页面必须一致,内部会校验,不一致则接收不到消息。

跨组件通信

该库抽离了 Event Bus 的实现,作为一个单独模块提供,因此可以只引入该模块,用于组件通信。

该模块提供了 ononceemitoffclear方法,WebpageChannel 内部使用的就是该模块。

1. 父组件

ts 复制代码
import { EventBus } from 'webpage-channel';

type Events = {
  ping: (payload: { value: number }) => void;
  pong: (payload: { result: string }) => void;
};

export const bus = new EventBus<Events>();

2. 兄弟组件 A

发送消息

ts 复制代码
import { bus } from 'parent'

bus.emit('ping', { value: 1 });

bus.emit('pong', { result: 'hello' })

3. 兄弟组件 B

接收消息

ts 复制代码
import { bus } from 'parent'

bus.on('ping', (payload) => {
    console.log('收到:', payload.value);
    
    // 触发后,主动移除该事件全部监听器
    bus.off('ping')
});

// 只触发一次
bus.once('pong', (payload) => {
    console.log('收到:', payload.result);
})

API

new WebpageChannel<T>(channelName, options?, adapter?)

创建一个频道实例。

  • channelName: string:频道名称。
  • options?: { ... }:可选配置。
  • adapter?: IWebpageChannelAdapter:可选适配器;不传时默认使用 BroadcastChannelAdapter

options 说明:

  • onError?: (e: Error) => void
    • 序列化、反序列化或事件分发过程中出现异常时触发。
  • onMessageError?: (e: MessageEvent) => void
    • 底层通道触发 messageerror 时触发。
  • serializeMessage?: (data) => string
    • 自定义序列化函数,默认 JSON.stringify
  • deserializeMessage?: (raw) => data
    • 自定义反序列化函数,默认 JSON.parse

channel.on(event, callback)

注册事件监听。

channel.once(event, callback)

注册一次性监听器,首次触发后会自动移除。

channel.emit(event, payload): boolean

发送事件并返回是否发送成功:

  • true:序列化与发送成功。
  • false:发送过程抛错(同时触发 onError)。
  • false:在调用 close() 之后再调用 emit 也会返回 false(同时触发 onError)。

channel.off(event, listener?)

  • listener:仅移除该函数引用。
  • 不传 listener:移除该事件全部监听器。

channel.clear()

清空当前实例的所有事件监听器。

channel.close()

清空监听器并关闭底层适配器。

调用后,就不能通信了。必须重新创建新实例,才可以通信。

适配器扩展

库通过 IWebpageChannelAdapter 抽象底层通信能力,你可以按需实现自己的适配器(例如 window.postMessageMessagePort 等)。

内置适配器

  • BroadcastChannelAdapter:默认适配器,适合同源多标签页/上下文通信。
  • PostMessageAdapter:适合父页面与 iframe、弹窗窗口等基于 window.postMessage 的场景。

自定义适配器示例

ts 复制代码
import { WebpageChannel, type IWebpageChannelAdapter } from 'webpage-channel';

class MyAdapter implements IWebpageChannelAdapter {
	postMessage(message: string) {
		// send
	}

	onMessage(callback: (message: string) => void) {
		// receive
	}

	onMessageError(callback: (e: MessageEvent) => void) {
		// message error
	}

	close() {
		// cleanup
	}
}

type Events = {
	ping: (payload: { time: number }) => void;
};

const channel = new WebpageChannel<Events>('my-channel', undefined, new MyAdapter());

序列化定制示例

ts 复制代码
type Events = {
	notify: (payload: { text: string }) => void;
};

const channel = new WebpageChannel<Events>('secure-channel', {
	serializeMessage(data) {
		return btoa(JSON.stringify(data));
	},
	deserializeMessage(raw) {
		return JSON.parse(atob(raw));
	},
	onError(err) {
		console.error('编解码或分发错误:', err);
	}
});

使用建议

  • 事件名保持稳定且语义化,推荐使用 模块:动作 命名。
  • 避免传输超大对象,尽量传必要字段。
  • 跨来源通信时请在适配器内严格校验 origin
  • 作为跨系统或跨上下文协议时,事件名和消息字段建议使用 string,不要使用 Symbol
  • 在页面卸载或模块销毁时调用 close() 释放资源。

其他

查看源码 GitHub

相关推荐
图扑软件2 小时前
图扑 HT 帧动画 | 3D 动态渲染设计与实现
前端·javascript·3d·动画·数字孪生
终端鹿2 小时前
Pinia 与 Vue Router 权限控制实战(衔接Pinia基础篇)
前端·javascript·vue.js
啥咕啦呛2 小时前
3个月前端转全栈计划
前端
BradyC2 小时前
laya编译内存溢出问题
前端
木斯佳2 小时前
前端八股文面经大全:阿里云AI应用开发一面(2026-03-20)·面经深度解析
前端·人工智能·阿里云·ai·智能体·流式打印
我叫黑大帅2 小时前
JS中的两大定时器
前端·javascript·面试
掘金安东尼3 小时前
⏰前端周刊第 458 期v2026.3.24
前端·javascript·面试
小霍同学3 小时前
NVM —— Node.js 版本管理工具
node.js
前端付豪3 小时前
实现必要的流式输出(Streaming)
前端·后端·agent