一个轻量级、类型友好的浏览器端消息通信库。
简介
提供统一的事件 API(on、once、emit、off等),用于在不同网页上下文之间通信,例如:多标签页、iframe 与 worker 场景。也可以用于跨组件通信。
默认基于 BroadcastChannel,并支持通过适配器扩展到 postMessage 等通信方式。
代码实现不到 200 行,单元测试覆盖率 100% 。
特性
- 轻量易用:
on、once、emit、off即可完成事件收发。 - 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.contentWindow、window.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 === targetOrigin和e.source === targetWindow。WebpageChannel的第一个参数name,父子页面必须一致,内部会校验,不一致则接收不到消息。
跨组件通信
该库抽离了 Event Bus 的实现,作为一个单独模块提供,因此可以只引入该模块,用于组件通信。
该模块提供了 on、once、emit、off 、 clear方法,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.postMessage、MessagePort 等)。
内置适配器
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。