Skip to content

熱模塊替換 (HMR) 允許您在運行的應用程序中更新模塊,而無需完全重新加載頁面。這保留了應用程序狀態並改善了開發體驗。

NOTE

使用 Bun 的全棧開發服務器時,默認啟用 HMR。

import.meta.hot API 參考

Bun 實現了 modeled after Vite 的 import.meta.hot API 的客戶端 HMR API。可以使用 if (import.meta.hot) 檢查,在生產環境中進行 tree-shaking。

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

但是,此檢查通常不需要,因為 Bun 會在生產構建中死代碼消除所有 HMR API 的調用。

ts
// 整個函數調用將在生產環境中移除!
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() 方法表示模塊可以熱替換。當不帶參數調用時,表示可以通過重新評估文件來替換此模塊。熱更新後,此模塊的導入器將自動修補。

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 並重新評估。導入 index.ts 的文件將修補為導入新版本的 getNegativeCount()。如果僅更新 index.ts,則只有一個文件將重新評估,並且 foo.ts 中的計數器將重用。

這可以與 import.meta.hot.data 結合使用,將狀態從上一個模塊轉移到新模塊。

帶回調

當提供一個回調時,import.meta.hot.accept 的功能與 Vite 中的功能相同。它不會修補此模塊的導入器,而是使用新模塊調用回調。

ts
export const count = 0;

import.meta.hot.accept(newModule => {
  if (newModule) {
    // 發生 SyntaxError 時 newModule 為 undefined
    console.log("updated: count is now ", newModule.count);
  }
});

接受其他模塊

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

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

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

表示可以接受依賴的模塊。當依賴更新時,將使用新模塊調用回調。

與多個依賴一起使用

ts
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())。

tsx
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()
ts
const sideEffect = setupSideEffect();

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

返回 promise 將延遲模塊替換,直到模塊被處理。所有 dispose 回調並行調用。

import.meta.hot.prune()

附加 on-prune 回調。當此模塊的所有導入都被移除時調用,但模塊之前已加載。

這可用於清理在模塊加載時創建的資源。與 import.meta.hot.dispose() 不同,這與 acceptdata 更好地配對以管理有狀態資源。管理 WebSocket 的完整示例:

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 運行時的事件。事件名稱以前綴開頭,以便插件不會相互沖突。

ts
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:*` 使用。

Bun學習網由www.bunjs.com.cn整理維護