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整理维护