Claude-skills bun-hot-reloading
Use when implementing hot reloading with Bun (--hot, --watch), HMR, or automatic code reloading during development. Covers watch mode, hot mode, and HTTP server reload.
install
source · Clone the upstream repo
git clone https://github.com/secondsky/claude-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/secondsky/claude-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/bun/skills/bun-hot-reloading" ~/.claude/skills/secondsky-claude-skills-bun-hot-reloading && rm -rf "$T"
manifest:
plugins/bun/skills/bun-hot-reloading/SKILL.mdsource content
Bun Hot Reloading
Bun provides built-in hot reloading for faster development cycles.
Watch Mode vs Hot Mode
| Feature | | |
|---|---|---|
| Behavior | Restart process | Reload modules |
| State | Lost on reload | Preserved |
| Speed | ~20ms restart | Instant reload |
| Use case | Any file type | Bun.serve HTTP |
Watch Mode (--watch)
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
package.json Scripts
{ "scripts": { "dev": "bun --watch run src/index.ts", "dev:server": "bun --watch run src/server.ts", "test:watch": "bun --watch test" } }
Watch Behavior
- Watches imported files automatically
- Triggers on any
,.ts
,.tsx
,.js
change.jsx - Also watches
imports.json - Restarts with fresh state
Hot Mode (--hot)
Reloads modules in-place without restarting the process.
bun --hot run src/server.ts
HTTP Server Hot Reload
// 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.
Bun.serve with Hot Reload
// 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 Server Patterns
Express-like Pattern
// 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, }); }); }
Stateful Server
// 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(); }
Custom Watch Implementation
// 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...");
WebSocket Live Reload
Server
// 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 Integration
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
Testing with Watch
# 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
Environment Detection
// 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}`); }
Common Issues
State Not Preserved
// ❌ State lost on hot reload let cache = new Map(); // ✅ State preserved on hot reload globalThis.cache ??= new Map(); const cache = globalThis.cache;
Cleanup Not Running
// ❌ 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); }); }
Module Not Reloading
// ❌ Import not watched const config = require("./config.json"); // ✅ Use import for watching import config from "./config.json";
Common Errors
| Error | Cause | Fix |
|---|---|---|
| File not imported | Check import chain |
| Using | Use or globalThis |
| Server not stopped | Implement server.stop() |
| No cleanup | Use dispose callback |
When to Load References
Load
references/advanced-hmr.md when:
- Custom HMR protocols
- Module federation
- Complex state management
Load
references/debugging.md when:
- HMR not working
- State issues
- Performance debugging