首页 / WXT 浏览器扩展框架教程 / 扩展 API 怎么调:wxt/browser 与类型安全

WXT 浏览器扩展框架教程

扩展 API 怎么调:wxt/browser 与类型安全

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

WXT扩展APIbrowser类型安全跨浏览器

本节目标:学会用统一的 browser 变量调用扩展 API,弄懂类型从哪来、API 缺失时怎么检测,以及为什么入口点顶层不能碰浏览器 API。

各浏览器的 API 不统一

扩展能用的能力由浏览器提供,统称扩展 API(extension API)。麻烦在于各家给的入口不一样:Chrome 挂在全局 chrome 对象上,Firefox 挂在 browser 上;风格也不同,Chrome 老接口偏回调式,Firefox 偏 Promise 式。想一套代码跑所有浏览器,得有人先做统一。

统一入口:wxt/browser

WXT 把两家合并成一个变量,从 wxt/browser 导入:

import { browser } from 'wxt/browser';

browser.action.onClicked.addListener(() => {
  // 用户点击了工具栏图标
});

browser 本质上是运行时浏览器提供的 browserchrome 全局对象的直接导出。这意味着你可以放心写 Promise 风格 API,MV2、MV3 通用,Chromium、Firefox、Safari 都跑得动。

习惯写 chrome.* 的老代码要注意:Firefox 也提供 chrome.* 兼容命名空间,但支持不完整、行为风格各异,直接调容易踩坑。统一走 browser 最省心。

Tip

默认开启的自动导入会帮你省掉 import 语句,直接写 browser 就能用。自动导入机制见 §29。

如果你希望 browser 走 webextension-polyfill 的包装(统一所有 API 为 Promise 风格),可以安装 @wxt-dev/webextension-polyfill 并按它的安装指南配置。不装也没关系,默认实现已经够日常使用。

类型从哪来

WXT 的 browser 类型基于 @types/chrome。所有 API 类型集中在 Browser 命名空间里,写回调参数时直接引用:

import { type Browser } from 'wxt/browser';

function handleMessage(message: unknown, sender: Browser.runtime.MessageSender) {
  // sender 携带来源标签页、框架等信息
}

WXT 会在 .wxt/ 目录里生成类型文件(如 §29 所述)。编辑器里悬停 browser,就能看到每个 API 的签名和注释;改完代码跑一次 wxt prepare,类型就保持最新。

特性检测:类型不会替你兜底

有些 API 受清单版本、浏览器和权限影响,运行时可能不存在。权限不足时,对应 API 是 undefined。类型系统假设所有 API 都存在,它帮不了你。判断 API 是否可用,要自己检测:

if (browser.runtime.onSuspend != null) {
  browser.runtime.onSuspend.addListener(() => {
    // 后台即将休眠
  });
}

可选链写起来更简洁:

browser.runtime.onSuspend?.addListener(() => {
  // ...
});

想兼容 MV2 和 MV3 里名字不同的同类 API,可以合并检测:

(browser.action ?? browser.browser_action).onClicked.addListener(() => {
  // MV3 是 action,MV2 是 browser_action
});

给 Firefox 补类型

@types/chrome 没有 Firefox 独有 API 的类型,比如 sidebarAction。想补,先安装 @wxt-dev/browser,再用 TypeScript 声明合并(declaration merging)扩展 Browser 命名空间:

// <srcDir>/browser-types.d.ts
import '@wxt-dev/browser';
import type { SidebarAction } from 'webextension-polyfill';

declare module '@wxt-dev/browser' {
  namespace Browser {
    export const sidebarAction: SidebarAction.Static;
  }
}

类型可以来自 webextension-polyfill、@types/firefox-webext-browser,也可以是你自己写的类型。

入口点顶层的坑

WXT 构建时会把入口文件先导入到 Node 环境(机制见 §06),那里没有浏览器提供的 chrome/browser 全局对象。WXT 用 fake-browser 兜了一层,但只实现了部分 API。所以在任何 JS/TS 入口点(后台、内容脚本、未列出脚本)的顶层调用浏览器 API,会直接报错:

✖ Command failed after 440 ms

 ERROR  Browser.action.onClicked.addListener not implemented.

修法很简单:把 API 调用挪进 main 函数。

// background.ts
browser.action.onClicked.addListener(() => { /* ... */ }); // 错:顶层

export default defineBackground(() => {
  browser.action.onClicked.addListener(() => { /* ... */ }); // 对:main 里
});

常用套路

拿到扩展页面的 URL,用 runtime.getURL,别硬编码 chrome-extension://...

const url = browser.runtime.getURL('/options.html');
await browser.tabs.create({ url });

查询当前活动标签页:

const [tab] = await browser.tabs.query({
  active: true,
  currentWindow: true,
});

能不能读到 urltitle 字段,取决于上下文和权限。想为这个需求加权限前,先查浏览器实际规则。

监听安装事件,做一次性初始化:

export default defineBackground(() => {
  browser.runtime.onInstalled.addListener(() => {
    // 只执行一次的初始化
  });
});
Note

监听器要在后台启动时同步注册。别把它藏在一次可能还没完成的远程请求之后,否则事件会漏掉。

小结

  • 统一从 wxt/browser 导入 browser,Promise 风格跨浏览器可用。
  • 能力差异用特性检测,别硬编码浏览器判断。
  • 浏览器 API 只能在入口的 main 里调用(原理见 §06)。