Skip to main content
在日常瀏覽器擴充功能開發中使用 dev,享有監看模式、瀏覽器啟動與情境感知的更新行為。 dev 會執行開發管線並監看你的專案檔案。它會依變更內容套用不同的更新策略:Hot Module Replacement (HMR)、硬重新載入,或必要時的完整重啟。

何時使用 dev

  • 開發功能並即時驗證變更。
  • 在一個或多個瀏覽器目標中除錯擴充功能行為。
需要正式版產物請用 build,需要正式版建置 + 啟動請用 start,只執行既有建置輸出請用 preview。 如果你的擴充功能放在 monorepo/submodule 中,請了解 extension.config.* 如何載入環境檔案(包含 workspace 根目錄的後備):環境變數。

Dev 指令功能

用法

如果省略路徑,Extension.js 會使用目前的工作資料夾。你也可以傳入 GitHub 樹狀 URL(例如 https://github.com/user/repo/tree/main/path)。Extension.js 會下載儲存庫,並對本地副本執行開發模式。

最常用的旗標

這些涵蓋 80% 的情境。其餘可參見 完整參考。

引數與旗標

有兩個已淘汰的別名雖然從 --help 中隱藏,但仍可運作:
  • --wait-format <pretty|json> 會對應到 --output,並在 stderr 警告一次。請把腳本遷移到 --output。
  • --author 與 --author-mode 會對應到 --debug。

Safari 旗標

這些旗標只適用於 safari 與 webkit-based 目標。與其他目標一起傳入其中任何一個都會以 E_INVALID_OPTION 結束,所以打錯字不會靜默地失效。 對 Safari 目標,dev 還會在第一次打包前執行工具鏈預檢。缺少 Xcode 會以 E_SAFARI_TOOLCHAIN 快速失敗。 從 4.1.20 起,Safari 工作階段就是一個完整的 dev 工作階段。你在 Safari 設定中啟用擴充功能之後,extension logs 可以讀取它,控制通道會接上,而且每次儲存都會在 Safari 中重新載入擴充功能。請見 建置 Safari 擴充功能。

父程序看門狗

--parent-pid 是給衍生 dev 的 harness 與代理使用的,宿主當掉不會外洩伺服器。它的值必須是正整數,其他任何值都會以 E_INVALID_OPTION 結束。看門狗每 2 秒輪詢一次父程序。父程序消失後,dev 伺服器會透過 SIGTERM 關閉;如果清理卡住,還有 5 秒的強制結束後備。

埠號如何決定

--port 是一個請求,不是保證。當請求的埠號被占用時,開發伺服器會往上找最近的空閒埠號。--port 0 會向作業系統要一個任意空閒埠號。請從 ready.json 讀取實際綁定的埠號,而不是你傳入的那個旗標。

自動化中介資料(建議用於腳本/代理)

當 dev 執行時,Extension.js 會發出機器可讀的中介資料於:
  • dist/extension-js/<browser>/ready.json
  • dist/extension-js/<browser>/events.ndjson(以換行分隔的 JSON)
進行自動化(Playwright、持續整合 CI、AI 代理)時,優先讀取這些檔案,而不是解析終端機記錄。 把 ready.json 視為就緒契約:
  • 啟動中:status: "starting"
  • 建置編譯完成:status: "ready"
  • 啟動/編譯失敗:status: "error",瀏覽器根本沒有啟動(code: "browser_launch_failed")或在擴充功能載入之前就結束(code: "browser_exited")時也是
  • 工作階段關閉後:status: "stopped",因此已經死掉的工作階段不會再宣稱自己 ready
  • service worker 連上之後為 runtime: "attached"(並附上 executorAttachedAt)。act verb 應該等待它,而不是等待 ready
  • 從 4.1.20 起,最後一個已連線的擴充功能情境斷線後為 runtime: "detached"(並附上 executorDetachedAt)。executorAttachedAt 會作為來源記錄保留,重新連線後該值會回到 "attached"
  • runId 唯一識別一次執行階段工作階段
  • startedAt 標示執行階段開始時間
  • command 記錄產生它的指令(dev、start、preview 或 build)
  • toolchainVersion、extensionName 與 extensionVersion 記錄是哪個 Extension.js 版本、為哪個擴充功能產出了這棵目錄樹——終端機捲動紀錄消失後,這個檔案仍是一份建置回執
  • port 是實際綁定的開發伺服器埠號,host 則是用戶端可以撥號的可連線主機(參見綁定主機與可連線主機)
  • controlPort / instanceId 定位 extension logs 與 act verb 使用的控制橋
  • cdpPort(Chromium)與 rdpPort(Gecko)公開瀏覽器的除錯埠號
  • profilePath、browserPid 與 extensionId 由瀏覽器啟動器在啟動後寫入
完整的 schema、錯誤狀態與兩階段就緒規則,請見 ready.json 契約。 events.ndjson 只屬於目前這次執行:新一次執行開始時檔案會重設,且每筆記錄都蓋上本次執行的 runId(與 ready.json 一致),消費端不會看到上一個工作階段的事件與目前的交錯。 工作階段行為異常時,執行 extension doctor——它依序檢查契約、控制通道、權杖、執行器與瀏覽器,並指出第一個失敗的環節與修復方式。

阻止瀏覽器啟動

兩個旗標聽起來很像,作用卻不同:
--no-browser 是無頭、CI 與遠端工作的執行模式,也是 Playwright E2E 工作流程所依賴的旗標。它還有設定寫法 commands.dev.noBrowser: true,以及環境變數寫法 EXTENSION_CLI_NO_BROWSER=1。 --no-open 只是啟動時的一個細節。當你希望瀏覽器停在它原本顯示的頁面上,而不讓 Extension.js 在上面為你的擴充功能開啟分頁時,就用它。dev、start 與 preview 都接受這兩個旗標。

--no-browser 與就緒同步

--no-browser 只會關閉瀏覽器啟動,但完整的開發迴圈仍然保留。開發伺服器依然監看你的檔案,並在每次重建後透過控制橋向擴充功能的 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> 載入任何你能控制的瀏覽器,它就會在你存檔時持續更新。(如果你想要永不重新載入的靜態開發包,請用 --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 看到死掉程序(pid 已不存在)留下的過期 ready.json 時,它會持續等待一個仍在運作的產生者。 --wait 需要本地專案路徑。傳入遠端 URL 會以 E_ARGS 結束。 在同一次指令中同時傳入 --wait 與 --no-browser 會以 E_INVALID_OPTION 結束,因為 --wait 只讀取另一個程序寫出的契約。請像上面的步驟那樣把它們當成兩個程序執行。 從 4.1.21 開始,當 --wait 在產生者還在寫入 ready.json 時讀到它,會把這個寫到一半的檔案當作又一種暫態,繼續輪詢。到 4.1.20 為止,一次撕裂讀取會讓等待以 JSON 解析錯誤失敗。始終無法解析的檔案仍會以 E_READY_TIMEOUT 結束,逾時訊息會點明原因:The last read of the file failed to parse as JSON,後面接著解析器自己的錯誤。

用 --output json 取得機器輸出

--output json 會在 stdout 印出 schema-1 信封,每行一個 JSON 物件:
  • 一般的 dev 執行會在工作階段建立之後、緊接在 starting 訊框之前,把一個 status: "started" 訊框當作 stdout 的第一行印出。它帶有專案路徑、瀏覽器清單、請求的埠號,以及開發伺服器的 pid。dev 不會自行結束,所以後面不會再有結果訊框。接下來的 生命週期訊框(starting、compiled、ready 以及之後的訊框)同樣寫到 stdout,給人看的進度行則寫到 stderr,因此 stdout 的每一行都能以 JSON 解析。即時狀態請讀 ready.json。
  • 在工作階段建立之前就被拒絕的執行只會印出一個 ok: false 訊框,前面沒有 started 訊框。這包括缺少 manifest(E_MANIFEST_NOT_FOUND)、manifest 無法解析(E_MANIFEST_INVALID)、設定檔載入失敗(E_CONFIG_LOAD),以及遠端 URL 沒有提供可用的封存檔(E_REMOTE_ZIP_INVALID、E_REMOTE_FETCH_TIMEOUT 或 E_REMOTE_DOWNLOAD)。
  • --chromium-binary 或 --gecko-binary 固定的執行檔有問題時,伺服器不會停止。這包括路徑不存在、檔案無法執行,以及執行檔在 10 秒內沒有回應版本探測。契約會進入 error,代碼為 browser_launch_failed,並帶 browserLaunchFailedCode: "E_BROWSER_BINARY_INVALID"。失敗訊框帶著該 readyCode,其中 status: "usage",error.code: "E_BROWSER_BINARY_INVALID",對該工作階段執行的 dev --wait 也會以同樣的狀態與代碼回應。
  • 固定的執行檔可以執行但系統無法啟動它時,伺服器同樣保持運作。失敗訊框的 status: "failed",error.code: "E_READY_ERROR_STATUS",readyCode 為 browser_launch_failed。
  • dev --wait 成功時會印出一個 status: "ready" 訊框。它的 value.results 陣列會逐一瀏覽器帶出完整的就緒契約。
  • 失敗時會在程序以 1 結束前,印出一個帶有 error.code(例如 E_READY_TIMEOUT)的 ok: false 訊框。處於 error 狀態的契約會指明原因:固定的執行檔遭拒時是帶 status: "usage" 的 E_BROWSER_BINARY_INVALID,瀏覽器根本沒有啟動時是 E_BROWSER_LAUNCH,在擴充功能載入之前就結束時是 E_BROWSER_EXITED,編譯錯誤是 E_COMPILE。完整清單見 ready.json 契約。

用 --no-reload 取得乾淨的開發包

--no-reload 會略過 content script 的重新注入包裝,以及重建時的 reload dispatch。開發版的 dist 會更接近正式版打包,並且當檔案變更時不會擾動已開啟的分頁。請自行重新載入擴充功能或頁面以套用變更。 --no-reload 只在 extension dev 支援。把它傳給 start、preview 或 build 會以錯誤結束。它在內部會設定 EXTENSION_NO_RELOAD=true,讓 develop 程序能從 CLI argv 之外讀取它。 dev 建置會輸出 cheap-module-source-map 類型的 source map,描述的是你的原始碼:原始的 TypeScript 與精確的行號,涵蓋 content script、傳統多檔案群組、background 以及兩個 manifest 版本下的頁面。不會使用任何 eval 變體,因此 bundle 在你自己的 CSP 下執行。

記錄旗標

這些旗標仍是實驗性的,可能在小版本之間變動。

共用全域選項

也支援 全域旗標.

Monorepo 與 workspace 根目錄

你可以將 dev(以及 build)指向 monorepo 的根目錄,而不是擴充功能套件本身。Extension.js 會偵測 workspace 根目錄並自動解析其中的擴充功能套件:
當剛好找到一個擴充功能套件時,Extension.js 會解析它並印出:
若有多個候選,會列出讓你指定要使用的那一個:

pnpm workspace 成員從根目錄安裝

從 4.1.18 開始,當專案是 pnpm workspace 的成員且相依套件缺少時,自動安裝會從 workspace 根目錄執行,而不是從套件資料夾執行。安裝範圍過濾為該成員及其 workspace 相依套件:
鎖定檔與 linker 配置仍然屬於 workspace,所以結果與在根目錄執行 pnpm install 一致。安裝執行之前,工作階段會印出一行 info:

範例

執行本地擴充功能

從 GitHub 執行遠端擴充功能

將 GitHub 樹狀 URL 當作引數,即可在本機開發遠端擴充功能:

在 Firefox 中執行

依序在多個瀏覽器中執行

在 Docker 或 dev container 中執行

當你在 Docker、dev container 或 GitHub Codespaces 中執行時,將開發伺服器綁定到 0.0.0.0,主機才連得到:
搭配 --port 0 讓作業系統自動挑選可用埠號:

綁定主機與可連線主機

--host 是開發伺服器綁定的位址。瀏覽器(HMR 用戶端與重新載入橋接)需要的是一個它真的能連線的位址,兩者未必是同一個值:
  • --host 0.0.0.0 會綁定所有網路介面,但 0.0.0.0 並不是可連線的位址。Extension.js 會自動改為向瀏覽器公告 127.0.0.1,這正是常見的埠號轉發式 Docker/dev container/Codespaces 設定所需要的目標:瀏覽器跑在主機上,埠號被轉發進容器。
  • 對於真正的遠端設定(瀏覽器與開發伺服器不在同一台機器上),請用 --public-host 傳入瀏覽器連得到的位址(區網 IP 或主機名稱)。它會被傳播到 HMR 用戶端 URL、ready.json,以及烘焙進擴充功能裡的重新載入橋接。
當 --host 本身已經是具體位址時(例如 --host 192.168.1.50),那個值本來就可連線,會被直接使用。只有在綁定主機與瀏覽器面對的主機不同時,才需要 --public-host。 開發伺服器會檢查每個請求的 Host 標頭,作為對 DNS 重新綁定的防禦。它回應 localhost、IP 位址、綁定主機與 --public-host。用其他任何名稱送來的請求都會得到 403,純文字內文寫明被拒絕的主機與修正方式,終端機上也會為每個被拒絕的主機印出一行。用 --allowed-hosts 放行更多名稱,它是逗號分隔的清單,前導的點允許所有子網域,也可以寫在 extension.config.js 的 commands.dev.allowedHosts 裡(陣列或逗號分隔的字串)。當開發伺服器是透過 docker 服務名稱、隧道主機名稱或 mDNS 名稱被連到、而瀏覽器撥號的目標不該改變時,就用它。
單獨的 --host 0.0.0.0 不會放行所有名稱。綁定位址與放行的名稱是兩個獨立的設定。

將 Brave 作為自訂執行檔執行

最佳實務

  • 瀏覽器相容性: 在不同瀏覽器中測試,確認每個目標都能正常運作。
  • 使用 polyfill: dev 中 polyfill 預設開啟,所以 browser.* 呼叫在 Chromium 系列瀏覽器中也能運作。想要未經處理的原始打包時,請傳入 --no-polyfill。
  • 自動化可靠性: 把 dev 視為監看模式的搭配(--no-browser + dev --wait)。把 start 視為正式版的搭配(--no-browser + start --wait)。對腳本與 CI 自動化,使用 --output=json。

後續步驟