首页 / WXT 浏览器扩展框架教程 / 消息通信:各环境之间怎么说话

WXT 浏览器扩展框架教程

消息通信:各环境之间怎么说话

本教程共 45 篇 · 第 17 篇 · 更新于 2026-08-13 · 约 4 分钟阅读

WXT消息通信messaging类型安全ProtocolMapwebext-core

本节目标:搞懂扩展里各环境(弹窗、后台、内容脚本、选项页)是怎么互相传话的,原生 API 有哪些痛点,以及 WXT 推荐的 @webext-core/messaging 怎么把消息变成类型安全的协议。

弹窗、后台、内容脚本、选项页,它们各自活在不同的环境里,彼此不共享内存。扩展要协作,靠的是「消息通信」。这一章和下一章(存储)是姊妹篇:消息负责「临时传话」,存储负责「长期记账」,见 §18。

原生消息模型

浏览器提供的原始能力分两种:

  • 一次性消息browser.runtime.sendMessage 发出去,对方 browser.runtime.onMessage 收到后回个响应。发给指定标签页的内容脚本时用 browser.tabs.sendMessage
  • 长连接browser.runtime.connect 建立 Port,双方可以持续互发。

原生 API 能用,但新手很容易踩坑:消息类型是裸字符串,拼错一个字母运行时才知道;收发双方靠「约定」同步数据结构,没有类型检查;异步回响应答稍不注意就出现「Could not establish connection. Receiving end does not exist」或 sendResponse 失效的问题。官方文档(Chrome 的 messaging 指南、MDN 的 content script 通信章节)把这些基础讲得很细,WXT 文档也直接指向它们,这里不再重复。

WXT 的推荐:装一个封装库

WXT 官方 messaging 页的核心建议就一句:原生 API 太难用,装一个封装库吧。它列了几个社区方案:

  • @webext-core/messaging:轻量、类型安全的封装,WXT 生态最常用
  • webext-bridge:开箱即用,上手快
  • trpc-chrome:tRPC 适配器,适合已有 tRPC 栈的团队
  • @webext-core/proxy-service:在后台执行函数、处处可调用,面向服务化场景

本章重点讲 @webext-core/messaging,它是 mkext 等生产项目验证过的选择。

ProtocolMap:把消息变成协议

这个库的核心思想是:先声明一份「协议」,再用协议生成收发函数。协议用 ProtocolMap 类型描述——每个消息名对应一个「入参 → 返回值」的函数签名:

// lib/messaging.ts
import { defineExtensionMessaging } from "@webext-core/messaging";

interface Messages {
  getUser: () => User | null;
  getDomainRatings: (domains: string[]) => DomainRatingsResponse;
}

export const { sendMessage, onMessage } = defineExtensionMessaging<Messages>();
  • sendMessage:发消息,入参和返回值都有类型推导
  • onMessage:注册监听,收到的数据自动带类型

收发双方都从这个文件导入,消息名、参数、返回结构全部对齐。写错键名、传错参数,TypeScript 在编译期就报错,而不是线上运行时才炸。

收发的基本用法

发送方(比如内容脚本):

const response = await sendMessage("getDomainRatings", [domain]);

接收方(比如后台):

onMessage("getDomainRatings", async (domains) => {
  // 在这里做真正的工作:查接口、读缓存
  return { ratings: {...}, status: "ok" };
});

onMessage 的处理函数返回什么,发送方就收到什么;处理函数抛错,发送方的 Promise 也会以错误结束。跨环境传递时数据会经过结构化克隆,函数、DOM 节点这类东西传不过去。

Note

官方文档:https://webext-core.aklinker1.io/messaging/。库本身不依赖 WXT,纯原生扩展也能用;WXT 只是推荐它。

信任边界:入参当 unknown 校验

类型安全解决的是「自己的代码写错」,解决不了「别人的代码使坏」。内容脚本运行在网页里,页面上的任何脚本都可以向扩展发消息。所以跨上下文收到的消息,一律当作不可信输入

mkext 的 code review 计划里专门有一条:消息入参按 unknown 校验,不能无条件信任。实践中可以先用类型收窄或运行时校验库(如 Zod)验证结构,再执行副作用操作;敏感操作还要核对 sender 身份,比如只响应来自自己扩展页面的消息。

别手写消息总线

有些教程会教你自己实现一个 MessageBus:一个 Map 存事件、一套 on/emit 方法。在小项目里它跑得起来,但本质是在重复造轮子——类型安全、超时处理、错误包装、跨环境适配全都要自己补,而这些正是 @webext-core/messaging 已经解决好的问题。除非你有非常特殊的定制需求,否则直接站在封装库的肩膀上。

小结

  • 原生消息 API 能用但裸奔:字符串类型、无类型检查、异步坑多。
  • @webext-core/messaging 用 ProtocolMap 把消息声明成协议,收发自动类型安全。
  • 跨上下文消息不可信,入参按 unknown 校验,敏感操作核对发送者。
  • 消息只管「传话」,要持久保存的状态请交给存储(§18)。