熱模塊替換 (HMR) 允許您在運行的應用程序中更新模塊,而無需完全重新加載頁面。這保留了應用程序狀態並改善了開發體驗。
NOTE
使用 Bun 的全棧開發服務器時,默認啟用 HMR。import.meta.hot API 參考
Bun 實現了 modeled after Vite 的 import.meta.hot API 的客戶端 HMR API。可以使用 if (import.meta.hot) 檢查,在生產環境中進行 tree-shaking。
if (import.meta.hot) {
// HMR APIs 可用。
}但是,此檢查通常不需要,因為 Bun 會在生產構建中死代碼消除所有 HMR API 的調用。
// 整個函數調用將在生產環境中移除!
import.meta.hot.dispose(() => {
console.log("dispose");
});NOTE
HMR API 仍在進行中。某些功能缺失。HMR 可以在 `Bun.serve` 中通過將 development 選項設置為 `{ hmr: false }` 來禁用。API 方法
| 方法 | 狀態 | 注釋 |
|---|---|---|
hot.accept() | ✅ | 表示可以優雅地替換熱更新。 |
hot.data | ✅ | 在模塊評估之間持久化數據。 |
hot.dispose() | ✅ | 添加在模塊即將被替換時運行的回調函數。 |
hot.invalidate() | ❌ | |
hot.on() | ✅ | 附加事件監聽器 |
hot.off() | ✅ | 從 on 移除事件監聽器。 |
hot.send() | ❌ | |
hot.prune() | 🚧 | 注意:回調當前從未調用。 |
hot.decline() | ✅ | 無操作以匹配 Vite 的 import.meta.hot |
import.meta.hot.accept()
accept() 方法表示模塊可以熱替換。當不帶參數調用時,表示可以通過重新評估文件來替換此模塊。熱更新後,此模塊的導入器將自動修補。
// index.ts
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 的功能與 Vite 中的功能相同。它不會修補此模塊的導入器,而是使用新模塊調用回調。
export const count = 0;
import.meta.hot.accept(newModule => {
if (newModule) {
// 發生 SyntaxError 時 newModule 為 undefined
console.log("updated: count is now ", newModule.count);
}
});接受其他模塊
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 是一個數組,其中每個項目對應於更新的模塊
// 或者如果該模塊有語法錯誤則為 undefined
});表示可以接受多個依賴的模塊。此變體接受依賴數組,其中回調將接收更新的模塊,對於任何有錯誤的模塊則為 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 />); // 重用現有根在生產環境中,data 內聯為 {},這意味著它不能用作狀態持有者。
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";
// 初始化或重用 WebSocket 連接
export const ws = (import.meta.hot.data.ws ??= new WebSocket(location.origin));
// 如果模塊的導入被移除,清理 WebSocket 連接。
import.meta.hot.prune(() => {
ws.close();
});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 連接或重新連接時。 |
NOTE
為了與 Vite 兼容,上述事件也可通過 `vite:*` 前綴而不是 `bun:*` 使用。