Skip to main content
dev 是日常浏览器扩展开发的命令:watch 模式、启动浏览器,以及感知上下文的更新行为。 dev 会运行开发流水线并监听你的项目文件。它会根据变更类型采取不同的更新策略:热模块替换(HMR)、硬重载,或者当变更需要时进行完整重启。

什么时候使用 dev

  • 构建特性并实时验证改动。
  • 在一个或多个浏览器目标里调试扩展行为。
生产产物用 build;生产构建 + 启动用 start;只运行已有构建产物用 preview 如果你的扩展放在 monorepo / 子模块里,请查看 extension.config.* 如何加载 env 文件(包括工作区根的回退):环境变量

dev 命令的能力

用法

如果省略路径,Extension.js 会使用当前工作目录。你也可以传入一个 GitHub tree URL(例如 https://github.com/user/repo/tree/main/path)。Extension.js 会下载该仓库并在本地副本上以开发模式运行。

最常用的 flag

下面这些覆盖了 80% 的场景。其余的请跳到 完整参考

参数与 flag

有两个已废弃的别名从 --help 中隐藏,但仍然可用:
  • --wait-format <pretty|json> 映射到 --output,并在 stderr 上警告一次。请把脚本迁移到 --output
  • --author--author-mode 映射到 --debug

Safari flag

这些 flag 只适用于 safariwebkit-based 目标。和其他目标一起传入其中任何一个都会以 E_INVALID_OPTION 退出,所以拼错不会静默地不生效。 对于 Safari 目标,dev 还会在第一次打包前运行工具链预检。缺少 Xcode 会以 E_SAFARI_TOOLCHAIN 快速失败。

父进程看门狗

--parent-pid 供派生 dev 的 harness 与 agent 使用,宿主崩溃不会泄漏服务器。它的值必须是正整数,其他任何值都会以 E_INVALID_OPTION 退出。看门狗每 2 秒轮询一次父进程。父进程消失后,dev 服务器会通过 SIGTERM 关闭;如果清理卡住,还有 5 秒的硬退出兜底。

端口如何确定

--port 是一个请求,而不是保证。请求的端口被占用时,dev 服务器会向上寻找最近的空闲端口。--port 0 会向 OS 要一个任意空闲端口。请从 ready.json 读取实际绑定的端口,而不是你传入的那个 flag。

自动化元数据(推荐用于脚本 / agent)

dev 运行时,Extension.js 会向这些位置写入机器可读的元数据:
  • dist/extension-js/<browser>/ready.json
  • dist/extension-js/<browser>/events.ndjson(每行一个 JSON)
对于自动化(Playwright、持续集成(CI)、AI agent),请优先使用这些文件,而不是解析终端日志。 ready.json 当作就绪契约来用:
  • 启动期间 status: "starting"
  • 构建编译完成时 status: "ready"
  • 启动 / 编译失败时 status: "error"
  • 会话关闭后 status: "stopped",因此一个已经死掉的会话不会再宣称自己 ready
  • service worker 连接后为 runtime: "attached"(并带上 executorAttachedAt)。act verb 应该等待它,而不是等待 ready
  • runId 唯一标识一次运行时会话
  • startedAt 标记运行时会话的起始时间戳
  • command 记录产生它的命令(devstartpreviewbuild
  • toolchainVersionextensionNameextensionVersion 记录是哪个 Extension.js 版本、为哪个扩展产出了这棵目录树——终端滚屏丢失后,这个文件仍是一份构建回执
  • port 是实际绑定的 dev 服务器端口,host 是客户端可以拨号的可连接主机(见绑定主机与可连接主机
  • controlPort / instanceId 定位 extension logs 与 act verb 使用的控制桥
  • cdpPort(Chromium)与 rdpPort(Gecko)暴露浏览器调试端口
  • profilePathbrowserPidextensionId 由浏览器启动器在启动后写入
完整的 schema、错误状态与两阶段就绪规则见 ready.json 契约 events.ndjson 只属于当前这次运行:新一次运行开始时文件会重置,且每条记录都盖有本次运行的 runId(与 ready.json 一致),消费者不会看到上一个会话的事件与当前会话交错。 会话行为异常时,运行 extension doctor——它按顺序检查契约、控制通道、令牌、执行器与浏览器,并指出第一个失败的环节和修复方法。

--no-browser 与就绪同步

--no-browser 只会禁用浏览器启动,但完整的 dev 循环仍然保留。dev 服务器依旧监听你的文件,并在每次重建后通过控制桥向扩展的 service worker 广播一次重载,因此即使没有被启动的浏览器在驱动,你的改动也会生效:
  • content script 的改动会就地重新注入到已经打开的匹配标签页(service worker 会用新的构建产物运行 chrome.scripting.executeScript),所以保存后页面就会更新,不需要手动刷新。之后新打开的标签页也会拿到新的构建产物,因为 service worker 会动态重新注册 content script(chrome.scripting.registerContentScripts)。
  • service worker / manifest 的改动会重启扩展。
所以在无头、持续集成(CI)以及远程 / dev container 工作流里,--no-browser 的表现和普通的 dev 会话一样:把构建好的 dist/<browser> 加载进任何你能控制的浏览器,它就会在你保存时持续更新。(如果你想要一个永不重载的静态 dev bundle,请用 --no-reload,见下文。) --no-browser 并不会在编译完成之前阻塞外部 runner。 对于 Playwright / CI / AI 工作流:
  1. extension dev --no-browser 作为长期运行的进程。
  2. extension dev --wait --browser=<browser> 作为就绪闸门。
  3. 只有在 status: "ready" 之后才启动外部浏览器自动化。
--wait 面向第二个进程(或 CI 步骤),在 error / 超时时以非零状态退出。 当 --wait 看到一个来自已死进程的过期 ready.jsonpid 已不存在)时,它会继续等待一个活跃的生产者。 --wait 需要一个本地项目路径。传入远程 URL 会以 E_ARGS 退出。 如果你在同一条命令中同时传入 --wait--no-browser--wait 优先。命令会以 wait-only 模式运行。

--output json 得到机器输出

--output json 会在 stdout 打印 schema-1 信封,每行一个 JSON 对象:
  • 普通的 dev 运行会在启动时打印一个 status: "started" 帧。它带有项目路径、浏览器列表、请求的端口以及 dev 服务器的 piddev 不会自行结束,所以后面不会再有结果帧。实时状态请读 ready.json
  • dev --wait 运行成功时会打印一个 status: "ready" 帧。它的 value.results 数组按浏览器给出完整的就绪契约。
  • 失败时会在进程以 1 退出之前打印一个带 error.code(例如 E_READY_TIMEOUT)的 ok: false 帧。

--no-reload:得到一个干净的 dev bundle

--no-reload 会跳过 content script 重注入包装器与重建时的重载派发。dev 下的 dist 会更接近生产 bundle,文件变更时已打开的标签页也不会被打扰。你需要自己重载扩展或页面来看到改动。 --no-reload 仅在 extension dev 上受支持。把它传给 startpreviewbuild 会以错误退出。内部上它会设置 EXTENSION_NO_RELOAD=true,方便 develop 进程在 CLI argv 之外读取。

日志 flag

这些 flag 仍是实验性的,可能在小版本之间变化。

共享的全局选项

也支持 全局 flag

monorepo 与工作区根

你可以把 dev(以及 build)指向一个 monorepo 的根目录,而不是扩展包本身。Extension.js 会检测工作区根,并自动解析其中的扩展包:
如果只找到一个扩展包,Extension.js 会解析它并打印:
如果存在多个候选,会列出来让你指向具体那一个:

示例

运行一个本地扩展

从 GitHub 运行一个远程扩展

把 GitHub tree URL 作为参数传入,即可在本地开发远程扩展:

在 Firefox 中运行

依次在多个浏览器中运行

在 Docker 或 dev container 中运行

当你在 Docker、dev container 或 GitHub Codespaces 中运行时,把 dev 服务器绑定到 0.0.0.0,宿主机才能访问到它:
搭配 --port 0 让 OS 自动选择一个可用端口:

绑定主机与可连接主机

--host 是 dev 服务器绑定的地址。浏览器(HMR 客户端与重载桥)需要的是一个它真正能连接的地址,两者并不总是同一个值:
  • --host 0.0.0.0 会绑定所有网络接口,但 0.0.0.0 并不是一个可连接的地址。Extension.js 会自动改为向浏览器公布 127.0.0.1,这正是常见的端口转发式 Docker / dev container / Codespaces 场景所需要的目标:浏览器跑在宿主机上,端口被转发到容器里。
  • 对于真正的远程场景(浏览器与 dev 服务器不在同一台机器上),请用 --public-host 传入浏览器能访问到的地址(局域网 IP 或主机名)。它会被传播到 HMR 客户端 URL、ready.json 以及烘焙进扩展里的重载桥。
--host 本身已经是一个具体地址时(例如 --host 192.168.1.50),这个值本来就可连接,会被直接使用。只有在绑定主机与浏览器面向的主机不同的时候,才需要 --public-host

用 Brave 作为自定义二进制运行

最佳实践

  • 浏览器兼容性: 在不同浏览器中测试你的扩展,确保在每个目标上都能工作。
  • polyfill: dev 中 polyfill 默认开启,所以 browser.* 调用在 Chromium-based 浏览器里也能工作。想要未经处理的原始 bundle 时,传 --no-polyfill
  • 自动化可靠性:dev 当作 watch 模式的伴侣(--no-browser + dev --wait);把 start 当作生产的伴侣(--no-browser + start --wait)。脚本与 CI 自动化使用 --output=json

下一步