Bun

熱重載

熱模組替換 (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-hmr-usage.ts
// 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() 方法表示模組可以熱替換。當呼叫 不帶參數時,表示可以透過簡單地 重新評估檔案來替換此模組。在熱更新之後,此模組的導入器將會 自動修補。

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()(並且沒有 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() 不同,這與 acceptdata 更能搭配使用以管理有狀態的資源。管理 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:* 來使用。