熱模組替換 (HMR) 允許您在應用程式執行時更新模組,而無需 完整頁面重新載入。這保留了應用程式的 狀態並改善開發體驗。
當使用 Bun 的全端開發伺服器時,預設會啟用 HMR。
import.meta.hot
API 參考
Bun 實作了用戶端 HMR API,其模型仿效 Vite 的 import.meta.hot
API。可以使用 if (import.meta.hot)
進行檢查,在生產環境中進行 tree-shaking
if (import.meta.hot) {
// HMR APIs are available.
}
然而,通常不需要此檢查,因為 Bun 會在生產版本中 dead-code-eliminate 對所有 HMR API 的呼叫。
// This entire function call will be removed in production!
import.meta.hot.dispose(() => {
console.log("dispose");
});
為了使此功能正常運作,Bun 強制這些 API 在沒有間接性的情況下被呼叫。這表示以下操作無效
// INVALID: Assigning `hot` to a variable
const hot = import.meta.hot;
hot.accept();
// INVALID: Assigning `import.meta` to a variable
const meta = import.meta;
meta.hot.accept();
console.log(meta.hot.data);
// INVALID: Passing to a function
doSomething(import.meta.hot.dispose);
// OK: The full phrase "import.meta.hot.<API>" must be called directly:
import.meta.hot.accept();
// OK: `data` can be passed to functions:
doSomething(import.meta.hot.data);
注意 — HMR API 仍在開發中。某些功能尚待完成。可以透過將 Bun.serve
中的 development
選項設定為 { hmr: false }
來停用 HMR。
方法 | 註解 | |
---|---|---|
✅ | hot.accept() | 表示可以優雅地替換熱更新。 |
✅ | hot.data | 在模組評估之間持續保存資料。 |
✅ | hot.dispose() | 新增一個回呼函式,在即將替換模組時執行。 |
❌ | hot.invalidate() | |
✅ | hot.on() | 附加事件監聽器 |
✅ | hot.off() | 從 on 中移除事件監聽器。 |
❌ | hot.send() | |
🚧 | hot.prune() | 注意:回呼目前永遠不會被呼叫。 |
✅ | hot.decline() | 為了匹配 Vite 的 import.meta.hot 而設計的 No-op |
import.meta.hot.accept()
accept()
方法表示模組可以熱替換。當呼叫 不帶參數時,表示可以透過簡單地 重新評估檔案來替換此模組。在熱更新之後,此模組的導入器將會 自動修補。
import { getCount } from "./foo.ts";
console.log("count is ", getCount());
import.meta.hot.accept();
export function getNegativeCount() {
return -getCount();
}
這為 index.ts
導入的所有檔案建立了一個熱重載邊界。這表示每當儲存 foo.ts
或其任何依賴項時, 更新將會冒泡到 index.ts
並重新評估。導入 index.ts
的檔案然後將被修補以導入新版本的 getNegativeCount()
。如果僅更新 index.ts
,則僅重新評估一個檔案, 並且重複使用 foo.ts
中的計數器。
這可以與 import.meta.hot.data
結合使用,以傳輸狀態 從先前的模組到新的模組。
當沒有模組呼叫 import.meta.hot.accept()
(並且沒有 React Fast Refresh 或外掛程式為您呼叫它)時,當檔案 更新時,頁面將會重新載入,並且主控台警告會顯示哪些檔案已失效。如果更依賴完整頁面重新載入更有意義,則可以忽略此警告 。
帶有回呼
當提供一個回呼時,import.meta.hot.accept
的功能將與 Vite 中的功能相同。它不會修補此模組的導入器,而是會呼叫 帶有新模組的回呼。
export const count = 0;
import.meta.hot.accept(newModule => {
if (newModule) {
// newModule is undefined when SyntaxError happened
console.log("updated: count is now ", newModule.count);
}
});
建議使用不帶參數的 import.meta.hot.accept()
,因為它通常使您的程式碼更容易理解。
接受其他模組
import { count } from "./foo";
import.meta.hot.accept("./foo", () => {
if (!newModule) return;
console.log("updated: count is now ", count);
});
表示可以接受依賴項的模組。當依賴項更新時,將使用新模組呼叫回呼。
帶有多個依賴項
import.meta.hot.accept(["./foo", "./bar"], newModules => {
// newModules is an array where each item corresponds to the updated module
// or undefined if that module had a syntax error
});
表示可以接受多個依賴項的模組。此變體接受依賴項陣列,其中回呼將接收更新的模組,對於任何有錯誤的模組則接收 undefined
。
import.meta.hot.data
import.meta.hot.data
在熱 替換期間維護模組實例之間的狀態,從而實現從先前版本到新版本的資料傳輸。當 寫入 import.meta.hot.data
時,Bun 也會將此模組標記為 能夠自我接受(相當於呼叫 import.meta.hot.accept()
)。
import { createRoot } from "react-dom/client";
import { App } from "./app";
const root = import.meta.hot.data.root ??= createRoot(elem);
root.render(<App />); // re-use an existing root
在生產環境中,data
會內聯為 {}
,表示它不能用作狀態持有者。
建議對有狀態的模組使用上述模式,因為 Bun 知道它可以將生產環境中的 {}.prop ??= value
縮減為 value
。
import.meta.hot.dispose()
附加一個 on-dispose 回呼。這會在
- 模組被另一個副本替換之前呼叫(在載入下一個模組之前)
- 在模組分離之後(移除所有對此模組的導入,請參閱
import.meta.hot.prune()
)
const sideEffect = setupSideEffect();
import.meta.hot.dispose(() => {
sideEffect.cleanup();
});
此回呼不會在路由導航或瀏覽器標籤頁關閉時呼叫。
傳回 Promise 將延遲模組替換,直到模組被處置。 所有 dispose 回呼都會並行呼叫。
import.meta.hot.prune()
附加一個 on-prune 回呼。當移除對此模組的所有導入 時,會呼叫此回呼,但該模組先前已載入。
這可用於清理在載入模組時建立的資源 。與 import.meta.hot.dispose()
不同,這與 accept
和 data
更能搭配使用以管理有狀態的資源。管理 WebSocket
的完整範例
import { something } from "./something";
// Initialize or re-use a WebSocket connection
export const ws = (import.meta.hot.data.ws ??= new WebSocket(location.origin));
// If the module's import is removed, clean up the WebSocket connection.
import.meta.hot.prune(() => {
ws.close();
});
如果改用 dispose
,則 WebSocket 會在每次 熱更新時關閉並重新開啟。當導入的 檔案更新時,這兩個版本的程式碼都會防止頁面重新載入。
import.meta.hot.on()
和 off()
on()
和 off()
用於監聽來自 HMR 運行時的事件。事件名稱帶有前綴,以便外掛程式彼此之間不會衝突。
import.meta.hot.on("bun:beforeUpdate", () => {
console.log("before a hot update");
});
當檔案被替換時,其所有事件監聽器都會自動移除。
所有內建事件的列表
事件 | 發射時機 |
---|---|
bun:beforeUpdate | 在套用熱更新之前。 |
bun:afterUpdate | 在套用熱更新之後。 |
bun:beforeFullReload | 在完整頁面重新載入發生之前。 |
bun:beforePrune | 在呼叫 prune 回呼之前。 |
bun:invalidate | 當模組因 import.meta.hot.invalidate() 而失效時 |
bun:error | 當發生建置或運行時錯誤時 |
bun:ws:disconnect | 當 HMR WebSocket 連線遺失時。這可能表示開發伺服器離線。 |
bun:ws:connect | 當 HMR WebSocket 連線或重新連線時。 |
為了與 Vite 相容,上述事件也可以透過 vite:*
前綴而不是 bun:*
來使用。