首页 / WXT 浏览器扩展框架教程 / 一套代码跑所有浏览器

WXT 浏览器扩展框架教程

一套代码跑所有浏览器

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

WXT跨浏览器firefoxmanifest入口过滤目标浏览器

本节目标:掌握指定目标浏览器(target browser)与 manifest 版本的方法,学会按浏览器过滤入口、配置 per-browser 选项,避开 Firefox 的兼容坑。

指定目标浏览器

WXT 默认按 Chrome 构建,用 -b--browser)切换目标:

wxt            # 等同 wxt -b chrome
wxt -b firefox
wxt -b custom

dev 模式下指定 Firefox 会打开 Firefox,其他字符串默认开 Chrome(浏览器二进制可配置,如 §05 所述)。Edge、Opera 这类 Chromium 系浏览器,都可以用 -b <名字> 构建,产物按「浏览器-manifest 版本」分目录存放,互不干扰(§38)。

日常开发可以给 package.json 配好双浏览器脚本,一键切换:

{
  "scripts": {
    "dev": "wxt",
    "dev:firefox": "wxt -b firefox",
    "build:firefox": "wxt build -b firefox"
  }
}

运行时想区分浏览器,用内置环境变量(§27):

if (import.meta.env.BROWSER === 'firefox') {
  // 仅 Firefox 构建执行
}
if (import.meta.env.FIREFOX) {
  // 简写,等价于上面
}

指定 manifest 版本

--mv2--mv3 决定构建哪个 manifest 版本。默认:Firefox 和 Safari 走 MV2,其余浏览器走 MV3。MV2 与 MV3 的差异不只版本号:MV3 的后台是 service worker,会休眠(§10);MV2 是常驻后台页,内存一直占着。写代码时如果依赖「后台常驻」,要意识到同一个扩展在不同浏览器上行为可能不一样。

运行时用 import.meta.env.MANIFEST_VERSION 读取目标版本:

if (import.meta.env.MANIFEST_VERSION === 2) {
  // 仅 MV2 构建执行
}

按浏览器过滤入口

不是每个入口都适合所有浏览器。内容脚本用 include/exclude 控制:

export default defineContentScript({
  include: ['firefox'], // 只在 Firefox 构建

  main(ctx) {
    // ...
  },
});

HTML 入口用 meta 标签声明(§07 讲过 HTML 入口的 manifest 写法):

<meta name="manifest.exclude" content="['chrome']" />

嫌逐个入口写麻烦,还可以用配置里的 filterEntrypoints 直接列出允许构建的入口清单,一把梭:

// wxt.config.ts
export default defineConfig({
  filterEntrypoints: ['popup', 'background'],
});

注意它和开发时的 -e 参数(§05)分工不同:-e 是临时只想看某几个入口,filterEntrypoints 是写进配置的固定清单,适合「这个版本只发这几个功能」的发布控制。

per-browser 选项

同一入口在不同浏览器要不同行为?选项可以按浏览器给对象,而不是单个值:

export default defineContentScript({
  matches: {
    chrome: ['*://chrome.example.com/*'],
    firefox: ['*://firefox.example.com/*'],
  },
  runAt: {
    chrome: 'document_start',
    firefox: 'document_end',
  },
  world: {
    firefox: 'MAIN',
  },
});

匹配规则、注入时机、执行世界,都能逐浏览器定制,代码里不用写一堆 if。适合什么场景?比如某个网站只在 Chrome 上给了接口、Firefox 上要走另一套地址;或者只想在 Firefox 上使用主世界(Chrome 111 起、Firefox 128 起都支持 world: 'MAIN',§11 讲过内容脚本的执行世界),那就只在 Firefox 配置它。

manifest 按浏览器分支

manifest 本身也能按浏览器分支(§25)。真实项目的做法(mkext 模板):Firefox 分支里剥离 Chromium 专有字段,补上 gecko 配置:

// wxt.config.ts
export default defineConfig({
  manifest: ({ browser }) => {
    const isFirefox = browser === 'firefox';
    const manifest = {
      // ...公共字段
      key: '...', // Chromium 专有
      oauth2: { /* ... */ },
      browser_specific_settings: isFirefox
        ? { gecko: { id: 'xxx@example.com' } }
        : undefined,
    };

    if (isFirefox) {
      const { key, oauth2, ...firefoxManifest } = manifest;
      return firefoxManifest;
    }
    return manifest;
  },
});

Firefox 的坑

  • keyoauth2 是 Chromium 专有字段。Firefox 靠 browser_specific_settings.gecko.id 识别扩展,没有 Chrome 的 identity.getAuthToken()(§21 讲过权限要按浏览器核实)。留着这些字段,AMO 审核会告警,还会白白暴露 client id;
  • favicon 接口(§23)只有 Chromium 系有,Firefox 里要绕过或降级;
  • Firefox 走 AMO 审核,未知 manifest 字段、权限不符都会被点名;
  • Safari 更特殊:默认 MV2,上架要走 Apple 的流程(§39),一般最后适配。

跨浏览器验收清单

一套代码跑所有浏览器,不是「能构建」就行,要每个目标都过一遍:

  1. dev 模式逐个浏览器跑一遍,入口、弹窗、内容脚本都点一遍;
  2. 构建后检查各产物目录的 manifest,确认 per-browser 字段与权限正确(§38);
  3. 重点核对「只有某浏览器才有的东西」:mkext 项目的做法是构建后直接检查产物——Chrome 包里保留 identityoauth2key,Firefox 包里确认这三样已剥离,browser_specific_settings.gecko 已写入。

这步别省。商店审核是按浏览器分开审的,Chrome 过了不代表 Firefox 能过。发布流水线里也要把 chrome 和 firefox 的构建、打包都跑一遍(§39),任何一边失败都能在合并前发现,而不是等商店打回。

Note

产物目录名里就带着目标:.output/chrome-mv3/.output/firefox-mv2/。构建完先看目录名,再看里面的 manifest,两处对不上说明构建目标搞错了。

小结

-b 选浏览器、--mv2/--mv3 选版本,入口与 manifest 按目标分支,Firefox 专坑提前绕。下一部分进入工程化:测试与发布。