Skip to main content
让同一个扩展代码库在不同浏览器与环境间运行,而不必硬编码任何值。

浏览器扩展中的环境变量

浏览器扩展会发布到用户机器上运行。这让配置与密钥之间的界线比普通 Web 应用更加严格。任何安装你扩展的人都能读取你打进 JavaScript、HTML 或编译后的 manifest.json 中的所有内容。 Extension.js 通过两种环境加载路径来处理这件事。一种用于编译后的扩展产物(感知浏览器与模式)。另一种用于在 Node 中加载 extension.config.* 两条路径都很重要,取决于你在哪里读取变量。

模板示例

new-env

new-env template screenshot 通过一个读取 EXTENSION_PUBLIC_* 值的新标签页扩展,看看环境变量是如何工作的。
代码仓库:extension-js/examples/new-env

content-env

content-env template screenshot 在注入到网页中的 content script 内使用环境变量。
代码仓库:extension-js/examples/content-env

工作原理

扩展产物(编译时)

构建扩展时,编译器会按以下顺序在扩展包目录中选择一份与之匹配的 env 文件:
  1. .env.[browser].[mode](例如 .env.chrome.development)
  2. .env.[browser]
  3. 上述两项的引擎家族变体(见下方说明)
  4. .env.[mode]
  5. .env.local
  6. .env
浏览器作用域的文件按引擎家族解析,与浏览器专属 manifest 字段一致。在精确的浏览器名称之后,Chromium 家族目标(chromiumchromeedgechromium-based、各 Chromium 分支以及 Safari 构建)还会依次尝试 .env.chromium.env.chrome.env.edge.env.chromium-based(各自的 .[mode] 变体优先)。Gecko 家族目标还会尝试 .env.firefox.env.gecko-based。因此一份 .env.chromium 文件即可覆盖 chromeedgebrave 构建,而精确的浏览器文件总是优先于家族文件。 如果存在 .env.defaults,Extension.js 总是先合并它,然后再合并所选文件的变量。最终,系统 process.env 对重叠的键拥有最高优先级。
选择的是上述列表中的单个 env 文件(加上 .env.defaults),而不是对所有文件做完整级联。.env.example 仅作为文档示例,永远不会作为值来源被加载。
如果项目旁存在 env 文件,但没有一个与当前浏览器和模式匹配,构建会打印警告,列出找到的文件与查找过的候选文件名。这样浏览器后缀的拼写错误会立刻暴露,而不是在没有值注入的情况下悄悄完成构建。 如果项目旁找不到匹配文件,Extension.js 会从最近的 workspace 根目录重复同样的搜索。所谓 workspace 根目录,是离当前位置最近、包含 pnpm-workspace.yaml 的祖先目录。位于 monorepo 中的扩展可以共享根级 env 文件来注入产物 Monorepo 约束: 仅当祖先目录中存在 pnpm-workspace.yaml 这个标记文件时,workspace 回退才会执行。如果你使用的是仅依赖 package.json "workspaces" 字段的纯 npm 或 Yarn workspaces,则不会触发自动根目录查找。这种情况下,请把 env 文件放在扩展包旁边,或在仓库根目录加上 pnpm-workspace.yaml

extension.config.*(Node 中,在你的配置运行之前)

extension.config.js / .mjs / .cjs 在 Node 中运行。在 Extension.js 求值该文件之前,会预先把一小组文件加载进 process.env,以便配置可以读取它们:
  1. .env.defaults(存在时合并)
  2. 然后是以下文件中首个存在的那个:.env.development.env.local.env
这一预加载步骤不会使用浏览器作用域的文件,比如 .env.chrome.env.chrome.development。需要时,请通过 shell 或持续集成 (CI) 流水线中的普通 process.env 设置。 你也可以依赖编译期的产物 env(上文已说明) 将 EXTENSION_PUBLIC_* 注入扩展代码。 Workspace 回退: 如果扩展包目录中找不到上述任何文件,则会在最近的、包含 pnpm-workspace.yaml 的目录中执行同样的预加载(约束与上文一致)。 之所以分成两条路径,是因为读取配置文件时与浏览器无关,而打包器知道当前的浏览器与模式。

内置环境变量

Extension.js 在编译时注入内置变量,因此你总能在扩展代码里拿到当前的浏览器与模式。 上述所有内置变量同时可通过 process.env.*import.meta.env.* 读取。

环境变量清单

公开/运行时变量(用户定义)

静态占位符变量

内置/别名变量

CLI 与开发服务器运行控制变量

遥测控制变量

完整退出契约请参见遥测与隐私

浏览器传输调优变量

这些变量会覆盖内部 Chrome DevTools Protocol (CDP) 与 Remote Debugging Protocol (RDP) 的超时设置。它们对慢速持续集成 (CI) 环境、Docker 容器或调试不稳定的浏览器连接很有用。

浏览器特定的环境变量

以下规则适用于编译期/产物 env 选择(见上文扩展产物)。它们不适用于 Node 中针对 extension.config.* 的窄范围预加载。 需要为每个浏览器使用不同的值吗?Extension.js 支持浏览器作用域的 env 文件,比如 .env.chrome(Chrome 扩展环境变量) 与 .env.firefox(Firefox 扩展环境变量)。你也可以把浏览器和模式组合起来,针对单个构建变体:
  • .env.chrome.development:只有在以开发模式运行 Chrome 时,Extension.js 才应用它。
  • .env.firefox.production:只有在以生产模式构建 Firefox 时,Extension.js 才应用它。
优先级顺序:
  • .env.[browser].[mode]
  • .env.[browser]
  • 引擎家族变体(例如任何 Chromium 家族目标都会匹配 .env.chromium)
  • .env.[mode]
  • .env.local
  • .env
你很少需要为每个厂商单独建一个文件。由于解析按引擎家族进行,.env.chromium 即可覆盖 chromeedge 及各 Chromium 分支,.env.firefox 覆盖各 Gecko 分支。只有当某个目标需要与家族其他成员不同的值时,才添加精确的浏览器文件(例如 .env.edge)。

示例文件

自定义环境变量

你可以在项目根目录的 env 文件中定义自定义变量。
Extension.js 只会把以 EXTENSION_PUBLIC_ 为前缀的变量注入 JavaScript 产物(process.env / import.meta.env)。
重要: Extension.js 不会把没有 EXTENSION_PUBLIC_ 前缀的变量注入 JS 产物。
但输出的 .json / .html 文件中的占位符可以解析 $EXTENSION_* 标记,所以不要在静态资产模板里引用密钥。

使用环境变量

你可以在 manifest.json、locale 文件、HTML、JavaScript/TypeScript 文件中使用环境变量。

1. 在 manifest.json

manifest.json 本身不支持环境变量,但 Extension.js 会在构建期替换支持的占位符。例如:
编译时,Extension.js 会把 $EXTENSION_PUBLIC_API_KEY 替换为解析后的 env 值。

2. 在 locale 文件中

当值应当随环境而变时,也可以在 locale 文件中使用占位符。例如:
Extension.js 在输出资产时,会把 $EXTENSION_PUBLIC_SITE_URL 这样的占位符替换为解析后的值。

3. 在 HTML 文件中

也可以在静态 HTML 文件中(例如 pages/ 下)使用占位符:
编译时,Extension.js 会替换输出 HTML 中的 $EXTENSION_PUBLIC_API_KEY

4. 在 JSX 组件中

在 React/JSX/TS 文件中,用 process.env 读取 env 值:
Extension.js 会在编译期内联这些值,并且可以按浏览器/模式而变化。

import.meta 支持

对于 ECMAScript Module (ESM) 工作流,Extension.js 同样支持 import.meta.env
service_worker.mjs
对于注入的 env 键,import.meta.envprocess.env 是对等的。 读取任何 env 文件都未定义的键会得到 undefined,而不会崩溃:Extension.js 会把裸的 import.meta.env 定义为包含所有已注入变量的对象(与 Vite 行为一致),因此 import.meta.env.MISSING_KEYconst {FOO} = import.meta.env 这样的解构在任何输出格式下都是安全的。

浏览器扩展构建中的密钥

浏览器扩展会在用户的机器上运行。任何安装了你的扩展的人都能查看你打进 JavaScript、HTML 或编译后的 manifest.json 中的任意值。请把扩展包视为公开内容。 几条规则要遵守:
  • 永远不要把 API 密钥、签名密钥或鉴权令牌放在 EXTENSION_PUBLIC_* 后面。这个前缀意味着该值可以安全发布,而不是用来隐藏它。
  • 不要在静态 manifest.json、locale 或 HTML 模板中放敏感值对应的 $EXTENSION_* 占位符。它们会在构建期展开,最终出现在产物里。
  • 把任何特权操作放到你自己的后端,扩展在运行时再调用它。
  • 对于持续集成 (CI),把构建期密钥放在 process.env(不要提交 env 文件),并把可共享的安全默认值放在 .env.defaults

最佳实践

  • 只暴露必须发布的值: 只给客户端安全的键加 EXTENSION_PUBLIC_ 前缀。
  • .env.defaults 共享团队默认值: 在保留可预测默认值的同时,允许本地/系统覆盖。
  • 不要把密钥放进静态占位符: 避免在 HTML/JSON 模板里放敏感的 $EXTENSION_* 标记。
  • 版本控制卫生: 提交 .env.example 作为文档(构建永远不会把它作为值来源读取),并忽略真实的 env 文件(.env.env.local、浏览器/模式变体)。

下一步