熱重載
使用 graft start --watch
時,被監看的外掛模組或相依項目一有變更,受影響的外掛就會換成新的程式碼。行程持續執行,所有並非從外掛模組載入的內容也是如此,例如環境中的服務。
| 本頁章節 | 回答的問題 |
|---|---|
| 儲存時會發生什麼 | 儲存檔案後 Graft 會做什麼? |
| 什麼會觸發重載 | 哪些檔案和變更會算數? |
| 重載不適用的情況 | 我的變更為什麼沒有生效? |
| 外掛模組的規則 | 如何寫出重載良好的程式碼? |
| 限制 | 熱重載做不到什麼? |
| 變更偵測 | 變更是如何被發現的? |
| 租約與觀察 | 哪些檔案被監看,監看到何時? |
儲存時會發生什麼
Section titled “儲存時會發生什麼”- 檢查。 Graft 在合併原生事件路徑後檢查被監看的檔案。單有事件不會觸發重載:檔案內容或被追蹤的本機解析結果必須真的有變。儲存完全相同的位元組,或變更無關的檔案,都不會重載外掛。
- 使失效。 自動重載會使已變更的模組實例及其遞移匯入者(包括環)失效。只有受影響的項目會被重新匯入。共用同一個舊 Plugin 物件的項目會一起檢查;未變更的相依項目和獨立的項目保留既有的實例。
- 驗證並取代。 所有候選項目都必須通過匯入、匯出身分和解析檢查,才會由一筆
Graft.replace交易發布這些取代。受影響的 fork 依照一般的撤回順序重新啟動,共用相依項目在整批取代中保持一致。 - 或保留舊程式碼。
候選項目匯入失敗或沒有通過檢查時,不取代任何外掛。被取代的外掛啟動失敗,或在
activationTimeout(預設 30 秒)內沒有啟動時,replace會回滾,舊外掛重新執行。失敗會記錄完整的 cause;逾時警告會指出未啟動的外掛。
候選項目失敗時
Section titled “候選項目失敗時”| 情況 | 行為 |
|---|---|
| 匯入或檢查失敗 | 舊外掛繼續執行。HMR 保留已變更的模組和失敗的觀察,留給下一次變更。 |
| 新外掛啟動失敗 | 結果為 RolledBack,舊外掛重新執行。HMR 保留已變更的模組,留給下一次變更。 |
| 外掛項目被刪除 | 從重載中剔除。它既有的 fork 會繼續執行,直到被卸載。 |
| 重載期間收到事件 | 事件仍然保留,供下一輪使用。 |
| 寫入不完整 | 重載可能失敗。Graft 記錄完整的 cause 並繼續監看;之後的內容變更即使沒有原生事件,也能使它恢復。 |
重載嘗試傳回 Reloaded、RolledBack 或 Skipped 之後,CLI 會呼叫
loader.retry。這會重試失敗的項目,並重新整理整棵樹中的 !env
值,因此修好壞掉的外掛後,不必重新啟動行程就能讓它啟動。
公開的 hmr.reload
一律強制重新匯入每個已註冊的項目及其被追蹤的相依項目。沒有任何項目時,它也會推進匯入視圖,因此失敗的第一次匯入可以重試。這與
watch 執行的增量重載不同。
什麼會觸發重載
Section titled “什麼會觸發重載”| 屬性 | 值 |
|---|---|
| 檔案 | 設定檔所在目錄下的 .js、.mjs、.cjs、.ts、.mts 和 .cts,包括子目錄。 |
| 排除 | node_modules 中的所有內容。 |
| YAML | 不在此處理。YAML 的編輯由載入器負責。 |
| 防抖 | 相近的檔案事件會防抖 50 ms。 |
| 新項目 | 透過設定新增外掛,本身不會觸發重載。 |
| 解析 | 每個模組實例保有自己的解析視圖。舊程式碼較晚發出的動態匯入(包括啟用的清理)不會切換到全域最新的版本。 |
| 動態匯入 | 被重用的模組保留其相依邊。從未執行過的動態匯入無法事先發現。 |
重載不適用的情況
Section titled “重載不適用的情況”有些變更無法安全地套用。HMR 會讓所有舊外掛繼續執行,並回報原因。
| 結果/原因 | 起因 | 處理方式 |
|---|---|---|
Skipped / restart-required |
有版本的模組匯入 node:module 或 module,或者從已持有租約的項目可到達 CommonJS 或原生相依項目。 |
重新啟動行程。 |
Skipped / restart-required |
入口或輔助符號連結現在指向不同的目標。 | 重新啟動行程。 |
Skipped / ambiguous-exports |
兩個項目先前匯出同一個 Plugin 物件,現在卻匯出不同的物件。會回報模組 URL。 | 重新啟動,或恢復共用的匯出。 |
Skipped / identity-conflict |
取代的身分與執行期的別名不相容,例如 P → Q, Q → R。會回報模組 URL。 |
重新啟動。 |
Failed |
匯入或檢查失敗。其值包含完整的 Cause。 |
修正程式碼。監看繼續進行。 |
細節:
- 匯出有歧義時,只要共用的匯出恢復就能復原。驗證會在發布別名或變更任何 fork 之前,拒絕整批取代。
node:module和module。 HMR 採取保守作法,要求重新啟動。這會在createRequire呼叫可能命中快取之前,先記錄原生載入能力,並同時涵蓋require(esm)與 CommonJS。即使只是該模組中無關的工具函式,也會讓整張圖脫離熱重載。需要重載的相依項目請使用 ESM 匯入。- 原生載入回報。 即使沒有檔案通知,監看器也會回報已知的需求,包括外掛在啟動後才取得該能力的情況。未變更的週期性回報會被抑制,而且這項警告不代表發生了重載。
- 符號連結。 本機檔案入口及其可到達的相依邊,會保留匯入它的模組實例、要求的路徑和實體目標。同一張被擁有的圖,會在匯入前、取代前和輪詢期間檢查。輪詢即使在位元組相同、原生事件遺失的情況下,也能偵測到目標變更。HMR 不會清除 Node 的解析快取,這項檢查也不會重新解析裸套件或套件的 export map。
外掛模組的規則
Section titled “外掛模組的規則”| 規則 | 原因 |
|---|---|
| 匯入時不得有副作用。 | 重載時 Graft 會重新匯入受影響的模組,而只有 apply 在 Graft 能夠關閉的 scope 內執行。請在 apply 中開啟連線、啟動伺服器和設定計時器。 |
| 使用 ECMAScript 模組。 | CommonJS 和原生相依項目需要重新啟動。Graft 依模組實例追蹤相依項目,包括 apply 期間發出的動態匯入。只要已持有租約的項目可以到達這類相依項目,重載就會傳回 Skipped / restart-required。 |
預期 node_modules 是共用的。 |
那裡的套件不會重載,而且 Effect 和 Graft 必須只有一份。唯一的例外是外掛本身所載入的模組:來自套件的外掛會被重新匯入,但該套件的其他檔案不會。 |
| 執行 Node.js 22.19 或更新版本。 | 這與 CLI 的 Effect Node 轉接器的要求一致,並且提供 module.registerHooks。 |
| 限制 | 說明 |
|---|---|
| 長時間工作階段記憶體會增長 | Node 的 ESM 快取不會逐出有版本的模組實例。增量重載建立的實例較少,但仍會累積。HMR 會依租約和目前的匯入視圖回收原始碼摘要、相依觀察和可達性快取;精簡的路由視圖會保留到工作階段關閉。行程記憶體沒有硬性上限。 |
| 沒有監督 | HMR 不會監督或重新啟動子行程。需要時請重新啟動行程。 |
| 不適用於正式環境 | 請勿在正式環境使用 --watch。 |
| 遠端外掛改為重新啟動 | graft start --watch 會在 local/worker 遠端設定項目的契約或實作檔案變更時重新啟動它們。宿主不觀察安裝在網路 runner 上的實作。 |
| 卡住的匯入會阻擋重載 | 永遠不會結束的匯入(例如永遠不會 resolve 的頂層 await)會拖住之後的重載和關閉,因為動態匯入無法取消。 |
| 機制 | 行為 |
|---|---|
| 原生事件 | 即使 stat 沒有變,也會強制檢查內容。 |
| 輪詢 | 每秒檢查一次中繼資料和本機解析結果。stat 快速路徑可避免讀取中繼資料未變的檔案。 |
| 閒置稽核 | HMR 至少每十秒稽核一次內容,因此大小與時間戳都相同的儲存,即使漏掉通知也仍會被偵測到。 |
| 基準 | 實際載入的位元組的 SHA-256 摘要,因此在匯入與監看器安裝之間發生的儲存也能被偵測到。 |
| 被監看的目錄 | 對被觀察到的相依圖中的目錄做非遞迴監看,即使 recursive: true 也是如此。HMR 不會走訪無關的目錄或 node_modules,已載入的 dist 模組仍然可以被觀察。不存在的路徑使用最近的既有上層目錄,這個集合每秒協調一次。 |
| 並行 | 檢查遵循被監看的路徑、recursive 和 filter,並在串流關閉時停止。讀取在工作階段鎖之外以有上限的並行度執行;只有在觀察版本仍然相符時,才接受結果。 |
觀察基準:
- 每次觀察都屬於一次匯入嘗試及其實際的原始碼位元組。
- 新的嘗試之後,輪詢以這些位元組作為基準,包括檔案在重載期間回到先前快照的情況。
- 如果失敗的嘗試讀取過原始碼位元組,它會推進基準,因此未變更的失敗不會造成輪詢重試迴圈。
監看器失敗:
- CLI 會記錄監看器的 I/O 失敗,並在一秒後重新安裝監看器。
- 原生監看器的產生者會在來源清理之後、進入背景串流合併之前,觀察其完整的
Exit。混合了中斷的失敗會在那裡連同完整的Cause一併記錄;只有觀察者主動發起且來源相符的取消不記錄,資源清理自身產生的中斷仍會報告。一般的後端失敗仍會傳播給消費者,不會重複記錄。 - 在取消期間結束的失敗,會在恢復中斷之前以完整的
Exit交接。HMR 會記錄取消本來會遮蔽的原始失敗,包括其完整的Cause。
只要設定項目存在,CLI 就會替每個模組保留註冊。
| 事件 | 效果 |
|---|---|
| 項目被移除或停用 | 釋放它的租約。最後一個租約關閉後,重載不再匯入該項目。 |
| 項目處於等待中或失敗 | 保留它的租約,因此變更仍然可以使它恢復。 |
| 項目被卸載 | 它及其獨佔的 CommonJS 相依項目不能阻擋其餘的項目。 |
| 某個路徑的最後一個擁有者被釋放 | 該路徑離開輪詢。 |
| 重載成功 | 只有不再能從啟用中或等待中的項目到達的觀察才會被撤銷;被重用的相依實例仍保持被擁有。 |
| 有擁有者的路徑發生真正的 I/O 錯誤 | 監看串流失敗。 |
輪詢遵循租約。它涵蓋每個存活項目正在執行的相依圖,以及其最新尚未提交的匯入嘗試,包括失敗的匯入和重試所需、但不存在的本機檔案。
失敗的第一次匯入會保留一個由設定項目擁有的觀察租約,與失敗時立即關閉的候選資源 scope 分開。因此即使原生通知遺失,修正模組也能透過輪詢恢復。重試會取代觀察世代;移除或停用則會關閉它。
Remote 項目的觀察集合不會隨啟動或重試失敗而關閉,詳見遠端項目的復原行為。
在 CLI 之外使用 HMR,請參閱 HMR API。