Bun Hot Reloading by secondsky/claude-skills
npx skills add https://github.com/secondsky/claude-skills --skill 'Bun Hot Reloading'Bun 提供内置的热重载功能,以加速开发周期。
| 功能特性 | --watch | --hot |
|---|---|---|
| 行为 | 重启进程 | 重载模块 |
| 状态 | 重载时丢失 | 保留 |
| 速度 | 约 20ms 重启 | 即时重载 |
| 使用场景 | 任意文件类型 | Bun.serve HTTP 服务 |
当文件变更时,重启整个进程。
# 基础监听模式
bun --watch run src/index.ts
# 监听特定脚本
bun --watch run dev
# 配合测试运行器使用
bun --watch test
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
{
"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 导入原地重载模块,无需重启进程。
bun --hot run src/server.ts
// src/server.ts
let counter = 0; // 状态在热重载间得以保留
export default {
port: 3000,
fetch(req: Request) {
counter++;
return new Response(`请求 #${counter}`);
},
};
bun --hot run src/server.ts
当你修改 server.ts 时,模块会即时重载,而 counter 的值保持不变。
// src/server.ts
const server = Bun.serve({
port: 3000,
fetch(req) {
return new Response("Hello!");
},
});
// 热重载处理器
if (import.meta.hot) {
import.meta.hot.accept(() => {
console.log("热重载!");
});
}
console.log(`服务器运行在端口 ${server.port}`);
// 检查热重载是否可用
if (import.meta.hot) {
// 接受对此模块的更新
import.meta.hot.accept();
// 带回调地接受更新
import.meta.hot.accept((newModule) => {
console.log("模块已更新:", newModule);
});
// 重载前的清理工作
import.meta.hot.dispose(() => {
// 关闭连接、清除定时器等
clearInterval(myInterval);
});
// 拒绝热重载(强制完全重启)
import.meta.hot.decline();
// 使此模块失效(触发父模块重载)
import.meta.hot.invalidate();
}
// src/server.ts
import { createApp } from "./app";
const app = createApp();
const server = Bun.serve({
port: 3000,
fetch: app.fetch,
});
// 热重载:重新创建应用
if (import.meta.hot) {
import.meta.hot.accept((newModule) => {
// 使用新的 fetch 处理器重载
server.reload({
fetch: newModule.default.fetch,
});
});
}
// src/server.ts
// 存储在 globalThis 中以在重载后存活
globalThis.connections ??= new Set();
const server = Bun.serve({
port: 3000,
fetch(req) {
return new Response(`连接数: ${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() {
// 动态导入,附带缓存破坏
const module = await import(`./src/server.ts?t=${Date.now()}`);
if (server) {
server.stop();
}
server = Bun.serve(module.default);
console.log(`服务器已启动,端口 ${server.port}`);
}
// 初始启动
await startServer();
// 监听变更
watch(srcDir, { recursive: true }, async (event, filename) => {
if (filename?.endsWith(".ts") || filename?.endsWith(".tsx")) {
console.log(`\n[${event}] ${filename}`);
await startServer();
}
});
console.log("正在监听变更...");
// 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;
}
// 在开发环境中注入重载脚本
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);
},
},
});
// 文件变更时通知客户端
watch("./src", { recursive: true }, () => {
clients.forEach((ws) => ws.send("reload"));
});
用于前端开发并支持 HMR:
// vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [react()],
server: {
port: 5173,
hmr: true,
},
});
# 使用 Bun 运行 Vite
bunx --bun vite
# 监听测试
bun --watch test
# 监听特定文件
bun --watch test src/utils.test.ts
# 配合 bail 参数(在首次失败时停止)
bun --watch test --bail
// 检查是否以 --hot 模式运行
const isHot = !!import.meta.hot;
// 检查是否以 --watch 模式运行
const isWatch = process.env.BUN_WATCH === "1";
// 开发模式
const isDev = process.env.NODE_ENV !== "production";
if (isDev) {
console.log("运行在开发模式");
console.log(`热重载: ${isHot}`);
console.log(`监听模式: ${isWatch}`);
}
// ❌ 热重载时状态丢失
let cache = new Map();
// ✅ 热重载时状态保留
globalThis.cache ??= new Map();
const cache = globalThis.cache;
// ❌ 重载后定时器继续运行
setInterval(() => console.log("tick"), 1000);
// ✅ 在 dispose 时清理
const interval = setInterval(() => console.log("tick"), 1000);
if (import.meta.hot) {
import.meta.hot.dispose(() => {
clearInterval(interval);
});
}
// ❌ 导入未被监听
const config = require("./config.json");
// ✅ 使用 import 以支持监听
import config from "./config.json";
| 错误 | 原因 | 修复方法 |
|---|---|---|
变更未检测到 | 文件未被导入 | 检查导入链 |
状态丢失 | 使用了 --watch | 使用 --hot 或 globalThis |
端口被占用 | 服务器未停止 | 实现 server.stop() |
内存泄漏 | 未进行清理 | 使用 dispose 回调 |
当遇到以下情况时,加载 references/advanced-hmr.md:
当遇到以下情况时,加载 references/debugging.md:
每周安装量
–
代码仓库
GitHub 星标数
90
首次出现
–
安全审计
Bun provides built-in hot reloading for faster development cycles.
| Feature | --watch | --hot |
|---|---|---|
| Behavior | Restart process | Reload modules |
| State | Lost on reload | Preserved |
| Speed | ~20ms restart | Instant reload |
| Use case | Any file type | Bun.serve HTTP |
Restarts the entire process when files change.
# Basic watch mode
bun --watch run src/index.ts
# Watch specific script
bun --watch run dev
# Watch with test runner
bun --watch test
{
"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 change.json importsReloads modules in-place without restarting the process.
bun --hot run src/server.ts
// 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
When you modify server.ts, the module reloads instantly while counter keeps its value.
// 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}`);
// 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();
}
// 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) {
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...");
// 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"));
});
For frontend development with 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";
| Error | Cause | Fix |
|---|---|---|
Changes not detected | File not imported | Check import chain |
State lost | Using --watch | Use --hot or globalThis |
Port in use | Server not stopped | Implement server.stop() |
Memory leak | No cleanup | Use dispose callback |
Load references/advanced-hmr.md when:
Load references/debugging.md when:
Weekly Installs
–
Repository
GitHub Stars
90
First Seen
–
Security Audits
React 组合模式指南:Vercel 组件架构最佳实践,提升代码可维护性
109,600 周安装