Skip to main content
不必为每个浏览器维护单独的 manifest 文件。 Extension.js 允许你用前缀在同一份文件里声明浏览器特定的值,然后在编译时只输出与当前目标匹配的字段。

为什么重要

浏览器之间在 manifest 的几个关键区域仍有差异,比如后台脚本配置和厂商元数据。带前缀的字段让你能保留一份源 manifest.json,同时为 Chromium 系和 Firefox 系目标生成各自正确的产物。

工作原理

Extension.js 扫描 manifest 的键,并按所选浏览器解析带前缀的条目。前缀要么指定一个引擎家族,要么指定单个浏览器:
  • chromium: 覆盖所有 Chromium 系目标(chromiumchromeedgechromium-based,分支 braveoperavivaldiyandex,以及 Safari 产物)
  • chrome: 只覆盖 chromeedge: 只覆盖 edge
  • firefox:gecko: 覆盖所有 Gecko 系目标(firefoxgecko-based,以及分支 waterfoxlibrewolf)
当某个带前缀的键匹配当前目标时,Extension.js 会在输出的 manifest 中把它重写为不带前缀的键。分支目标会继承其引擎家族的前缀,因此当你面向 bravewaterfox 这样的分支时,一份只带 chromium:/firefox: 键的 manifest 仍能被正确解析。精确的浏览器名称前缀也会匹配它自己的目标(例如运行 --browser=brave 时的 brave:)。

Chromium 系浏览器(Chrome、Edge 等)

Firefox

这样,service_worker 只会出现在 Chromium 系产物里,而 Firefox 产物里则保留 background.scripts 支持的前缀映射: 精确的浏览器名称前缀(例如 chrome:edge:brave:waterfox:)只有在你面向同一个浏览器时才会解析。它胜过所属的家族前缀,因此为 chrome 构建时 chrome: 胜过 chromium: Safari 产物继承 Chromium 家族,因为转换器消费的是 Chrome 形态的 manifest。chromium: 键适用于 Safari,但 chrome:edge: 键不适用。只想覆盖 Safari 时请使用 safari:(或 webkit:),它们优先于 chromium: 键。 Safari 没有实现的键与权限会从 Safari 构建里自动移除,并且从 4.1.20 起,构建会为每个被移除的键打印一行,说明是哪个键、为什么被移除。content_scripts[].world 会被保留,因为从 Safari 18 起 Safari 就支持它。详见 构建 Safari 扩展 它适用于 manifest 中任意层级的任何字段,包括 permissionscontent_scriptsbackground

只面向一个 Chromium 厂商

chromium: 是家族前缀。chrome:edge: 各自只指定一个浏览器,因此一个字段可以只发布到一个商店,而不进入另一个商店。例如,Chrome Web Store 的 key 不能进入 Edge Add-ons 的包:
extension build --browser=chrome 会输出 keyextension build --browser=edge 不会包含它。 不要写 edge:key。Edge Add-ons 会拒绝任何在 manifest 中包含 key 的包,因此该字段在 Edge 构建里没有用处。扩展 id 由 Partner Center 分配。从 4.1.20 起,生产模式的 Edge 构建会丢弃 key 并打印一行说明原因,开发模式的构建会保留它,因为此时稳定的 id 有用,而且不涉及任何商店。 前缀匹配的是你用 --browser 请求的浏览器,而不是实际启动的二进制。当 Extension.js 退回到另一个浏览器二进制时,前缀仍按请求的目标解析。
从 Extension.js 4.1.19 起,chrome:edge: 成为精确的浏览器前缀。在 4.1.18 及更早版本中,它们适用于每一个 Chromium 系目标。如果构建丢弃了 4.1.18 会应用的 chrome:edge: 键,构建会打印一条点名该键的警告。把该键重命名为 chromium: 即可保留原来的覆盖范围。

优先级:三层结构

当多个键设置同一个字段时,胜出者由层级决定,而不是由它在文件中的位置决定:
  1. 不带前缀的普通键是基础。
  2. 家族前缀(Chromium 目标上的 chromium:,Gecko 目标上的 firefox:gecko:)覆盖普通键。
  3. 精确前缀覆盖前两者。精确指的是该前缀点名了确切的目标,比如为 chrome 构建时的 chrome:,或为 brave 构建时的 brave:。在 Safari 和 webkit 系目标上,safari:webkit: 都属于精确前缀。
源码顺序只在同一层级内部用来打破平局。看这个例子:
chrome 构建会输出 "Chrome"chrome: 点名了确切的目标,因此位于精确层,胜过 chromium:。为 edgechromiumbrave 构建则输出 "Family",因为 chrome: 不适用于这些目标。 只有同一层级里有两个前缀时才会平局,比如 waterfox 这样的 Gecko 分支上的 firefox:gecko:,或 Safari 目标上的 safari:webkit:。源码顺序靠后的键胜出。 只要带前缀的键匹配,它总是覆盖同名的普通键,无论两者在文件中的先后位置。

前缀在任意层级都会解析

解析器会遍历整棵 manifest 树,包括数组。content_scripts 条目内部或任意嵌套对象内部的带前缀键,都按与顶层键相同的三层规则解析。

同一个解析器也驱动入口发现

前缀解析不只作用于输出的 JSON。同一个解析器会在 script 和 HTML 入口发现之前运行,因此 firefox:background 脚本或带前缀的页面只有在匹配的目标上才会成为被编译的入口。

分支与 *-based 别名

家族分类先匹配一份已知分支清单,再退回到子串检查。chromeedgebraveoperavivaldiyandex 按名字归类为 Chromium 系,任何其他包含 chromium 的名字也一样。firefoxwaterfoxlibrewolf 按名字归类为 Gecko 系,任何其他包含 geckofirefox 的名字也一样。这就是 chromium-basedgecko-based 别名,以及基于它们构造的任意 *-based 名字,都能继承所属家族带前缀键的原因。

带前缀的 manifest_version 需要一个不带前缀的兜底值

chrome:edge: 是精确前缀,所以只作用于某一个厂商的 manifest_version 会让其他所有构建都没有这个字段。下面这份 manifest 给了 Firefox 和 Chrome 一个版本号,却没有给 Edge:
没有 manifest_version 的 manifest 任何浏览器都不会加载。从 4.1.21 开始,构建会用一条点名被丢弃键的错误拒绝这种情况,例如 chrome:manifest_version applies only to Chrome builds, so the edge build has no manifest_version.。到 4.1.20 为止,构建会照常写出 manifest,浏览器随后才拒绝它。 请写一个不带前缀的 manifest_version 作为基础值,只给例外情况加前缀:
当整个 Chromium 家族需要共用一个与普通键不同的值时,使用 chromium:manifest_version

最佳实践

  • 共享默认值保持无前缀:把通用字段写在普通 manifest 键中,只对浏览器差异的部分加前缀。
  • 行为分歧时再加前缀:当运行时要求不同时再使用浏览器前缀。
  • 在持续集成 (CI) 中按目标分别构建:分别生成并验证每个浏览器的产物(dist/<browser>),以便尽早发现兼容性回归。
  • 用 MDN 验证:在添加仅特定浏览器可用的设置前,使用 MDN Web Docs 确认支持情况。

下一步