首页 / Bun 入门教程 / 热重载与开发服务器

Bun 入门教程

热重载与开发服务器

本教程共 34 篇 · 第 6 篇 · 更新于 2026-08-06

Bun热重载watchhotBun.serve开发服务器

本节目标:

  • 区分 bun --watch(硬重启)与 bun --hot(软重载)两种模式
  • bun --hot 让 HTTP 服务在不重启进程的情况下刷新代码
  • 了解 Bun 全栈开发服务器的默认 HMR 行为
  • 会用 import.meta.hot 做基础的生命周期管理

写代码时,最影响心流的就是「改一行就要手动重启」。Bun 内置了两种自动重载机制,都不需要 nodemon 之类的外部工具。这一节把它们讲清楚,并说清什么场景该用哪一个。

6.1 两种重载:硬重启 vs 软重载

Bun 提供两种模式,关键区别在「改完文件后进程要不要重来」:

模式命令行为适用
--watchbun --watch file.ts硬重启:进程退出并重新启动,环境变量与 CLI 参数保持不变测试、一次性脚本、需要干净状态重启的场景
--hotbun --hot file.ts软重载:不清退进程,只更新内部模块缓存,全局状态保留长时间运行的服务、HTTP 服务器、需要保活状态的场景

简单记忆:--watch 是「关了再开」,--hot 是「原地换血」。

6.2 --watch:硬重启

适合 bun test 或运行普通脚本:

bun --watch index.tsx
bun --watch test

--watch 模式下,Bun 会追踪所有被 import 的文件,一旦其中任何一个发生变化,就带着相同的 CLI 参数与环境变量重新启动进程。如果进程崩溃,--watch 还会尝试重启。

Bun 的文件监听用的是操作系统原生 API(如 Linux 的 inotify、macOS/BSD 的 kqueue),而不是靠轮询,所以在大型项目里也能保持低开销、高灵敏度。文档里提到它做了若干优化:提高文件描述符的 rlimit、静态分配路径缓冲区、复用文件描述符等。

Tip

多个 bun build --watch 并行时,某个实例清屏可能掩盖其他实例的错误。加 --no-clear-screen(或环境变量 BUN_CONFIG_NO_CLEAR_TERMINAL_ON_RELOAD=true)可避免清屏,配合 concurrently 这类工具更好用。

6.3 --hot:软重载(HMR)

对需要长期运行的服务,--hot 更有价值。它不会重启整个进程,而是检测到文件变化后更新内部模块缓存:

bun --hot server.ts

从入口文件出发,Bun 会登记所有被 import 的源文件(不含 node_modules 里的),并监听它们。文件变化时执行「软重载」:所有文件被重新求值,但全局状态(特别是 globalThis保留

declare global {
  var count: number;
}

globalThis.count ??= 0;
console.log(`Reloaded ${globalThis.count} times`);
globalThis.count++;

setInterval(() => {}, 1000000); // 防止进程退出

每保存一次文件,globalThis.count 就加一——因为进程没重启,globalThis 上的值被保留了下来。这正是 --hot--watch 最本质的差异:前者保活、后者归零。

Note

bun --hot服务端热重载,对应 Node 生态里「改后端代码不重启进程」的体验。它和浏览器里的 HMR(改 React 组件页面不刷新)不是一回事。

想要浏览器端热更新,有两条路:用 Bun 的全栈开发服务器(前端 HMR 默认开启,见 6.5),或者前端另起一个 Vite。

6.4 HTTP 服务器的热更新

当你用 Bun.serve() 跑 HTTP 服务时,--hot 的效果最明显:保存文件后,Bun 会不重启进程地替换 fetch 处理器,刷新速度几乎瞬时。

Bun.serve({
  port: 3000,
  fetch(req) {
    return new Response("Hello world");
  },
});
bun --hot run index.ts

软重载的实现上,Bun 会重置内部的 require 缓存与 ES 模块注册表、同步跑一次垃圾回收、从头重新转译并求值代码。官方文档坦言这一步目前不是增量编译——没改的文件也会被重新转译,属于起步版实现。

Note

传统文件监听器(如 nodemon)的做法是「重启整个进程」,于是 HTTP 服务器和数据库连接等状态对象都会丢失,重启还要重新监听端口、重新建立连接。bun --hot 的「原地换血」避免了这些开销,因此刷新特别快。代价是全局状态会累积(比如上面 globalThis.count 一直涨),这既是特性也是需要注意的点:如果你的代码依赖「每次运行都从零开始」,那 --watch 反而更合适。

下面给一个完整的可运行示例,直观感受 --hot 下的服务端刷新:

// server.ts
Bun.serve({
  port: 3000,
  fetch(req: Request) {
    const url = new URL(req.url);
    if (url.pathname === "/count") {
      globalThis.count = (globalThis.count ?? 0) + 1;
      return new Response(`hit ${globalThis.count} times`);
    }
    return new Response("edit me and save!");
  },
});

console.log("server ready, try: bun --hot run server.ts");

运行 bun --hot run server.ts 后,反复请求 /count,计数会持续累加(因为进程没重启);而如果你改了 fetch 里的返回文案再保存,新逻辑会立即生效,且计数不会被清零。把同样的代码换成 bun --watch,每次保存都会把计数归零——这正是两种模式的区别所在。

6.5 全栈开发服务器的默认 HMR

如果你用的是 Bun 的全栈开发服务器(基于 Bun.serve 配合前端入口),HMR 默认开启。也就是说,前端模块的改动会触发浏览器侧的热模块替换,无需额外配置。要显式关闭,可在 Bun.serve 里设置 development: { hmr: false }

这套客户端 HMR 基于 import.meta.hot API,设计上对齐 Vite 的 import.meta.hot,方便从 Vite 生态迁移。生产构建时,Bun 会做死代码消除,把 HMR 相关调用整段移除,因此不必担心它们污染线上代码。

6.6 import.meta.hot 基础用法

最常见的两个用法是 accept()data

import.meta.hot.accept(); // 声明本模块可被热替换

accept() 表示「这个模块更新时可以被就地替换」。当被 accept 的模块或其依赖保存后,更新会向上冒泡、重新求值,并自动 patch 它的导入方。

import.meta.hot.data 用于在「旧模块」与「新模块」之间搬运状态:

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

const root = (import.meta.hot.data.root ??= createRoot(elem));
root.render(<App />); // 复用同一个 root,不重建

dispose() 则用于在模块被替换前做清理(如关闭连接、移除监听):

const sideEffect = setupSideEffect();

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

import.meta.hot 的 API 必须直接以完整短语调用,不能先赋值给变量再调用,否则 Bun 无法识别。例如 const hot = import.meta.hot; hot.accept();无效的;必须写 import.meta.hot.accept();data 可以传给函数,但 hot 本身不能。hot.invalidate()hot.send() 在当前版本尚未实现。

此外还有 import.meta.hot.on() / off() 用于监听 HMR 运行时事件,事件名带 bun: 前缀(为兼容 Vite 也支持 vite: 前缀)。常用事件包括:bun:beforeUpdate(热更新应用前)、bun:afterUpdate(应用后)、bun:beforeFullReload(整页刷新前)、bun:error(构建或运行时出错)、bun:ws:connect / bun:ws:disconnect(HMR WebSocket 连接状态)。当某文件被替换时,它上面的事件监听会被自动移除,无需手动清理。

import.meta.hot.on("bun:beforeUpdate", () => {
  console.log("about to hot-update");
});

6.7 小结与选型建议

  • 跑测试、临时脚本、需要干净重启 → 用 bun --watch
  • 跑 HTTP 服务、需要保活全局状态、追求瞬时刷新 → 用 bun --hot
  • 全栈开发服务器默认开启前端 HMR,无需配置;想关用 development: { hmr: false }
  • import.meta.hot 用于精细化控制热替换生命周期,对齐 Vite 习惯,但调用形式有约束、部分方法未实现。
  • 记住 --hot 不刷新浏览器页面,它只管服务端代码替换。
Tip

一个实用组合:bun --hot run index.ts 起后端服务,前端用 Vite 起 HMR。两者各管一边,开发体验接近「全栈热更新」。若想更省心,直接用 Bun 全栈开发服务器,前端 HMR 已内置。