mcpField
B
bun-hot-reloading

bun-hot-reloading

github.com/secondsky开发240401 次安装5 周前更新
代码仓库

关于 bun-hot-reloading

这项开发技能介绍 Bun 的 `--watch` 与 `--hot` 模式,帮助开发者按需选择重启进程或原位重载模块,并处理 HTTP 服务、测试和状态保留。

主要功能

  1. 01比较 `--watch` 与 `--hot`
  2. 02文件变更时重启进程
  3. 03保留状态并原位重载模块
  4. 04使用 `import.meta.hot` 回调
  5. 05以监听模式运行测试

适用场景

  1. 01开发时重启 Bun 应用
  2. 02无状态丢失地重载 Bun HTTP 服务
  3. 03源文件变更后重新运行测试

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/secondsky/claude-skills --skill bun-hot-reloading

安装后在 Claude Code 中输入/bun-hot-reloading确认已加载。

详细介绍

Bun 热重载

Bun 内置了热重载功能,可加快开发周期。

监视模式与热模式

功能--watch--hot
行为重启进程重新加载模块
状态重载时丢失得以保留
速度约 20ms 重启即时重载
使用场景任意文件类型Bun.serve HTTP

监视模式(--watch)

文件发生变化时重启整个进程。

# Basic watch mode
bun --watch run src/index.ts

# Watch specific script
bun --watch run dev

# Watch with test runner
bun --watch test
package.json 脚本
{
  "scripts": {
    "dev": "bun --watch run src/index.ts",
    "dev:server": "bun --watch run src/server.ts",
    "test:watch": "bun --watch test"
  }
}
监视行为
  • 自动监视导入的文件
  • 任意 .ts、.tsx、.js、.jsx 文件发生变化时触发
  • 也会监视 .json 导入
  • 以全新状态重启

热模式(--hot)

在原进程中重新加载模块,而不重启进程。

bun --hot run src/server.ts
HTTP 服务热重载
// src/server.ts
let counter = 0; // State preserved across hot reloads

export default {
  port: 3000,
  fetch(req: Request) {
    counter++;
    return new Response(`Request #${counter}`);
  },
};
bun --hot run src/server.ts

修改 server.ts 时,模块会即时重载,同时 counter 保留其值。

使用热重载的 Bun.serve
// src/server.ts
const server = Bun.serve({
  port: 3000,
  fetch(req) {
    return new Response("Hello!");
  },
});

// Hot reload handler
if (import.meta.hot) {
  import.meta.hot.accept(() => {
    console.log("Hot reload!");
  });
}

console.log(`Server running on port ${server.port}`);

import.meta.hot API

// Check if hot reload is available
if (import.meta.hot) {
  // Accept updates to this module
  import.meta.hot.accept();

  // Accept with callback
  import.meta.hot.accept((newModule) => {
    console.log("Module updated:", newModule);
  });

  // Cleanup before reload
  import.meta.hot.dispose(() => {
    // Close connections, clear intervals, etc.
    clearInterval(myInterval);
  });

  // Decline hot reload (force full restart)
  import.meta.hot.decline();

  // Invalidate this module (trigger parent reload)
  import.meta.hot.invalidate();
}

HTTP 服务模式

类 Express 模式
// src/server.ts
import { createApp } from "./app";

const app = createApp();

const server = Bun.serve({
  port: 3000,
  fetch: app.fetch,
});

// Hot reload: recreate app
if (import.meta.hot) {
  import.meta.hot.accept((newModule) => {
    // Reload with new fetch handler
    server.reload({
      fetch: newModule.default.fetch,
    });
  });
}
有状态服务
// src/server.ts
// Store in globalThis to survive reloads
globalThis.connections ??= new Set();

const server = Bun.serve({
  port: 3000,
  fetch(req) {
    return new Response(`Connections: ${globalThis.connections.size}`);
  },
  websocket: {
    open(ws) {
      globalThis.connections.add(ws);
    },
    close(ws) {
      globalThis.connections.delete(ws);
    },
  },
});

if (import.meta.hot) {
  import.meta.hot.accept();
}

自定义监视实现

// dev-server.ts
import { watch } from "fs";

const srcDir = "./src";
let server: ReturnType<typeof Bun.serve> | null = null;

async function startServer() {
  // Dynamic import with cache busting
  const module = await import(`./src/server.ts?t=${Date.now()}`);

  if (server) {
    // Bun 1.2+: server.stop() returns a Promise — await it so the old
    // socket is fully torn down before we rebind the same port.
    await server.stop();
  }

  server = Bun.serve(module.default);
  console.log(`Server started on port ${server.port}`);
}

// Initial start
await startServer();

// Watch for changes
watch(srcDir, { recursive: true }, async (event, filename) => {
  if (filename?.endsWith(".ts") || filename?.endsWith(".tsx")) {
    console.log(`\n[${event}] ${filename}`);
    await startServer();
  }
});

console.log("Watching for changes...");

WebSocket 实时重载

服务端
// src/dev-server.ts
const clients = new Set<ServerWebSocket>();

const server = Bun.serve({
  port: 3000,
  fetch(req, server) {
    if (req.headers.get("upgrade") === "websocket") {
      server.upgrade(req);
      return;
    }

    // Inject reload script in dev
    const html = `
      <!DOCTYPE html>
      <html>
        <body>
          <h1>Hello!</h1>
          <script>
            const ws = new WebSocket('ws://localhost:3000');
            ws.onmessage = (e) => {
              if (e.data === 'reload') location.reload();
            };
          </script>
        </body>
      </html>
    `;
    return new Response(html, {
      headers: { "Content-Type": "text/html" },
    });
  },
  websocket: {
    open(ws) {
      clients.add(ws);
    },
    close(ws) {
      clients.delete(ws);
    },
  },
});

// Notify clients on file change
watch("./src", { recursive: true }, () => {
  clients.forEach((ws) => ws.send("reload"));
});

Vite 集成

用于带 HMR 的前端开发:

// vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";

export default defineConfig({
  plugins: [react()],
  server: {
    port: 5173,
    hmr: true,
  },
});
# Use Bun to run Vite
bunx --bun vite

使用监视模式进行测试

# Watch tests
bun --watch test

# Watch specific file
bun --watch test src/utils.test.ts

# With bail (stop on first failure)
bun --watch test --bail

环境检测

// Check if running with --hot
const isHot = !!import.meta.hot;

// Check if running with --watch
const isWatch = process.env.BUN_WATCH === "1";

// Development mode
const isDev = process.env.NODE_ENV !== "production";

if (isDev) {
  console.log("Running in development mode");
  console.log(`Hot reload: ${isHot}`);
  console.log(`Watch mode: ${isWatch}`);
}

常见问题

状态未保留
// ❌ State lost on hot reload
let cache = new Map();

// ✅ State preserved on hot reload
globalThis.cache ??= new Map();
const cache = globalThis.cache;
未执行清理
// ❌ Interval keeps running after reload
setInterval(() => console.log("tick"), 1000);

// ✅ Clean up on dispose
const interval = setInterval(() => console.log("tick"), 1000);

if (import.meta.hot) {
  import.meta.hot.dispose(() => {
    clearInterval(interval);
  });
}
模块未重载
// ❌ Import not watched
const config = require("./config.json");

// ✅ Use import for watching
import config from "./config.json";

常见错误

错误原因修复方法
Changes not detected文件未导入检查导入链
State lost使用了 --watch使用 --hot 或 globalThis
Port in use服务未停止重新绑定前执行 await server.stop()
Memory leak未进行清理使用 dispose 回调

何时加载参考资料

遇到以下情况时,加载 references/advanced-hmr.md:

  • 自定义 HMR 协议
  • 模块联邦
  • 复杂的状态管理

遇到以下情况时,加载 references/debugging.md:

  • HMR 无法正常工作
  • 状态问题
  • 性能调试

评论

支持 Markdown