@teres/iframe-bridge
一个基于 Penpal 的轻量 iframe 通信桥,帮助宿主页面与子应用(iframe)安全、稳定地进行双向方法调用与状态同步。
为什么需要它
- 将跨项目的交互(如语言切换、导航、关闭、就绪通知)抽象为清晰的 API。
- 在 Monorepo 或多项目场景下,提供“宿主 ↔ 子应用”的统一通信约定。
- 屏蔽 Penpal 细节,专注业务方法与类型定义。
安装与版本
- 已在工作空间内作为包使用(
packages/iframe-bridge)。 - 依赖:
penpal@^6.2.1。
目录结构
packages/iframe-bridge/
├── package.json
├── README.md
└── src/
├── index.ts # 入口聚合与导出(不含业务逻辑)
├── types.ts # 对外类型:HostApi、ChildApi
├── path.ts # 路径转换与是否嵌入判断
├── client.ts # 子端到父端的连接与客户端方法
├── host.ts # 宿主侧创建与管理与子端的连接
└── logger.ts # 统一日志工具
核心概念
- Host(宿主):包含
iframe的父页面。 - Child(子端):被嵌入在
iframe中的子应用。子端暴露方法给宿主调用。 - Penpal:在宿主与子端之间建立安全的双向 RPC(方法调用)连接。
快速上手
宿主侧(Host)示例:创建连接并缓存子端引用
import { createPenpalHostBridge } from '@teres/iframe-bridge';
// iframeRef 指向你页面中的 <iframe>
const { child, destroy } = await createPenpalHostBridge({
iframe: iframeRef.current!,
methods: {
// 暴露给“子端”可调用的宿主方法
navigate: (path: string) => {
// 宿主导航逻辑
},
close: () => {
// 关闭或返回逻辑
},
agentReady: (agentId?: string) => {
// 子端就绪通知处理
},
},
});
// child 是一个 Promise,解析后可拿到子端暴露的方法(例如 changeLanguage)
await child; // 也可以缓存后续使用
// 组件卸载或需要重建连接时
await destroy();
在你的项目中(如 src/pages/ragflow/iframe.tsx)通常会把 child 引用存到一个管理器,供语言切换等场景调用。
子端侧(Child)示例:暴露方法并与宿主交互
import Bridge from '@teres/iframe-bridge';
// 在子应用初始化时调用(例如根组件 useEffect 中)
Bridge.initChildBridge({
// 暴露给“宿主”可调用的子端方法
methods: {
changeLanguage: async (lang: string) => {
// 你的 i18n 切换逻辑,例如:
// await i18n.changeLanguage(lang);
},
},
});
// 主动与宿主交互(可选)
Bridge.clientReady();
Bridge.clientNavigate('/some/path');
Bridge.clientClose();
API 参考
类型(types.ts)
HostApinavigate(path: string): voidclose(): voidagentReady(agentId?: string): void
ChildApichangeLanguage(lang: string): Promise<void>
路径与嵌入(path.ts)
isEmbedded(): boolean判断当前窗口是否运行在 iframe 中(且需要通信)。toHostPath(childPath: string): string将子端路径转换为宿主侧路由前缀路径。toChildPath(hostPath: string): string将宿主路径映射回子端路径。
子端客户端方法(client.ts)
getClientHostApi(): Promise<HostApi>建立并缓存与宿主的连接。clientNavigate(path: string): Promise<void>请求宿主导航。clientClose(): Promise<void>请求宿主关闭或返回。clientReady(agentId?: string): Promise<void>通知宿主子端就绪。initChildBridge(options: { methods: Partial<ChildApi> }): Promise<void>在子端暴露方法供宿主调用(如changeLanguage)。
宿主桥接(host.ts)
createPenpalHostBridge({ iframe, methods })iframe: HTMLIFrameElementmethods: Partial<HostApi>暴露给子端可调用的宿主方法。- 返回:
{ child: Promise<ChildExposed>, destroy: () => Promise<void> }child:解析后可获得子端暴露的方法(如changeLanguage)。destroy:销毁连接与事件监听。
语言联动最佳实践
- 子端在初始化中通过
initChildBridge暴露changeLanguage。 - 宿主在创建连接后缓存
child引用。 - 宿主语言切换组件在变更时调用:
const child = await childPromise; // 取到缓存的子端引用
await child.changeLanguage(nextLang);
调试与排错
- 确保 iframe 与宿主同源或允许跨源通信(Penpal 支持跨源,但需正确 URL)。
- 连接建立需要子端加载完成;在子端
initChildBridge之前调用子端方法会报错或超时。 - 若需要观察连接过程,可在
logger.ts中启用调试日志或在业务代码中打点。 - 组件卸载时务必调用
destroy(),避免内存泄漏或事件残留。
设计说明
- 入口
index.ts仅做导出聚合,具体逻辑按职责拆分到types/logger/path/client/host。 - 子端侧会缓存一次宿主连接,避免重复握手,提高性能与稳定性。
常见问题
- 调用时机过早:请在子端完成初始化(
initChildBridge)后再由宿主调用子端方法。 - 路径映射:
toHostPath/toChildPath默认适配当前项目前缀,如需自定义可扩展配置模块。 - 异步方法:所有跨端调用均为异步,注意
await与错误处理。
若你需要把路径前缀改为可配置项或扩展更多方法(如主题切换、会话同步),欢迎提 issue 或继续迭代。