執行環境套件管理器打包器測試執行器程式碼檢查格式化指南參考部落格意見回饋

熱過載

Bun 開發伺服器的熱模組替換(HMR)

熱模組替換(HMR)可以在不重新載入整個頁面的情況下更新執行中應用程式的模組,同時保留應用程式狀態。

import.meta.hot API 參考

Bun 實現了一個客戶端 HMR API,其設計參考了 Vite 的 import.meta.hot API。你可以通過 if (import.meta.hot) 檢查它,在生產環境中該程式碼會被搖樹最佳化移除。

index.ts
if (import.meta.hot) {
  // HMR API 可用。
}

通常不需要進行此檢查,因為 Bun 會在生產建置中消除對所有 HMR API 的呼叫。

index.ts
// 整個函式呼叫都會在生產環境中被移除。
import.meta.hot.dispose(() => {
  console.log("dispose");
});

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() 方法表示模組可以進行熱替換。不帶引數呼叫時,表示 Bun 可以通過重新求值該檔案來替換此模組。熱更新後,Bun 會自動修補該模組的匯入者。

index.ts
// 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,然後重新求值。接著,Bun 會修補匯入 index.ts 的檔案,使其匯入新版 getNegativeCount()。如果你只更新 index.ts,Bun 只會重新求值該檔案,並複用 foo.ts 中的計數器。

將此功能與 import.meta.hot.data 結合使用,可以將狀態從之前的模組傳遞到新模組。

帶回調

傳入回撥時,import.meta.hot.accept 的行為與在 Vite 中相同。它不會修補此模組的匯入者,而是使用新模組呼叫回撥。

index.ts
export const count = 0;

import.meta.hot.accept(newModule => {
  if (newModule) {
    // 當出現語法錯誤時 newModule 會是 undefined
    console.log("updated: count is now ", newModule.count);
  }
});

接受其它模組

index.ts
import { count } from "./foo";

import.meta.hot.accept("./foo", newModule => {
  if (!newModule) return;

  console.log("updated: count is now ", count);
});

表示可以接受某個相依項的模組。相依項更新時,Bun 會使用新模組呼叫回撥。

多相依

index.ts
import.meta.hot.accept(["./foo", "./bar"], newModules => {
  // newModules holds the updated module at its index and
  // undefined for the other dependencies
});

此變體接受一個相依項陣列。回撥會收到一個陣列,其中更新後的模組位於對應索引處,其它相依項對應的位置為 undefined。

import.meta.hot.data

import.meta.hot.data 會在熱替換過程中,將模組先前版本的狀態傳遞給新版本。向 import.meta.hot.data 寫入內容也會將模組標記為自行接受(等同於呼叫 import.meta.hot.accept())。

index.tsx
import { createRoot } from "react-dom/client";
import { App } from "./app";

const root = (import.meta.hot.data.root ??= createRoot(elem));
root.render(<App />); // 複用已有 root

在生產環境中,Bun 會將 data 內聯為 {},因此不能將其用作狀態持有器。

import.meta.hot.dispose()

新增一個解除安裝回撥。Bun 會在以下情況呼叫它:

  • 模組即將被替換(即新模組載入前)
  • 模組被解除安裝(所有對該模組的匯入被移除,見 import.meta.hot.prune())
index.ts
const sideEffect = setupSideEffect();

import.meta.hot.dispose(() => {
  sideEffect.cleanup();
});

傳回一個 promise 會延遲模組替換,直到模組完成解除安裝。Bun 會並行呼叫所有解除安裝回撥。

import.meta.hot.prune()

新增一個清理回撥。當該模組的所有匯入都被移除,但模組之前已載入時,Bun 會呼叫它。

可以使用它來清理模組載入時建立的資源。與 import.meta.hot.dispose() 不同,它與 accept 和 data 配合管理有狀態資源時更加合適。以下是一個管理 WebSocket 的完整範例:

index.ts
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 執行環境的事件。事件名稱帶有字首,因此外掛之間不會發生衝突。

index.ts
import.meta.hot.on("bun:beforeUpdate", () => {
  console.log("熱更新前");
});

替換檔案時,Bun 會自動移除該檔案的所有事件監聽器。

內建事件

事件觸發時機
bun:beforeUpdate熱更新應用前。
bun:afterUpdate熱更新應用後。
bun:beforeFullReload完整頁面重新載入前。
bun:beforePrune清理回撥呼叫前。
bun:invalidate使用 import.meta.hot.invalidate() 使模組失效時。
bun:error發生建置或執行環境錯誤時。
bun:ws:disconnectHMR WebSocket 連線斷開時。這可能表示開發伺服器已離線。
bun:ws:connectHMR WebSocket 連線或重新連線時。