Documentation Index
Fetch the complete documentation index at: https://mintlify.com/zhcndoc/bun/llms.txt
Use this file to discover all available pages before exploring further.
Bun’s bundler supports Hot Module Replacement (HMR) for fast development iteration without losing application state.
What is HMR?
Hot Module Replacement (HMR) allows you to update modules in a running application without a full page reload. When you edit a file:
- Bun detects the change
- Rebuilds only the affected modules
- Sends the update to the browser
- The browser applies the update without refreshing
This preserves:
- Application state (form inputs, scroll position, etc.)
- React component state
- Global variables
- WebSocket connections
Watch mode
Enable watch mode to rebuild automatically on file changes:
bun build ./src/index.tsx --outdir ./dist --watch
This watches all files imported by your entry point and triggers a rebuild when any of them change.
Development server
For full HMR support, use Bun’s development server (coming soon):
const server = Bun.serve({
port: 3000,
async fetch(req) {
const url = new URL(req.url);
// Serve bundled assets with HMR
if (url.pathname === "/app.js") {
const result = await Bun.build({
entrypoints: ["./src/index.tsx"],
outdir: "./dist",
watch: true,
});
return new Response(result.outputs[0]);
}
// Serve HTML
return new Response(Bun.file("./index.html"));
},
});
console.log(`Dev server running at http://localhost:${server.port}`);
React Fast Refresh
React Fast Refresh is automatically enabled for .jsx and .tsx files:
import { useState } from "react";
export function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
</div>
);
}
When you edit this component:
- The component re-renders
- State is preserved (count value remains)
- No page reload occurs
Enable Fast Refresh explicitly:
bun build ./src/index.tsx --outdir ./dist --react-fast-refresh
Or via the JavaScript API:
await Bun.build({
entrypoints: ["./src/index.tsx"],
outdir: "./dist",
reactFastRefresh: true,
});
Module types
Hot updates
Modules can opt into hot updates using the import.meta.hot API:
if (import.meta.hot) {
import.meta.hot.accept(() => {
// Module was updated
console.log("Module reloaded!");
});
}
Self-accepting modules
A module can handle its own updates:
let data = loadData();
if (import.meta.hot) {
import.meta.hot.accept(() => {
// Reload data when this module updates
data = loadData();
});
}
export { data };
Accepting dependencies
A module can handle updates to its dependencies:
import { config } from "./config";
if (import.meta.hot) {
import.meta.hot.accept("./config", (newConfig) => {
// Config was updated
console.log("Config updated:", newConfig);
});
}
CSS HMR
CSS updates are applied without a page reload:
.button {
background: blue;
color: white;
}
When you change the background color:
.button {
background: red; /* Changed */
color: white;
}
The style updates immediately in the browser without losing application state.
Image HMR
Images are also hot-reloaded:
import logo from "./logo.png";
function App() {
return <img src={logo} alt="Logo" />;
}
When you replace logo.png, the new image appears without a page reload.
Configuration
Debouncing
Watch mode debounces file changes to avoid rebuilding too frequently:
await Bun.build({
entrypoints: ["./src/index.tsx"],
outdir: "./dist",
watch: {
// Wait 100ms after a file change before rebuilding
debounce: 100,
},
});
Ignored paths
Exclude paths from watch mode:
await Bun.build({
entrypoints: ["./src/index.tsx"],
outdir: "./dist",
watch: {
ignore: [
"**/node_modules/**",
"**/.git/**",
"**/dist/**",
],
},
});
Error handling
When a build error occurs:
- The error is displayed in the terminal
- The browser shows an error overlay (if using a dev server)
- The previous working code continues to run
- When you fix the error, HMR resumes
HMR in Bun is fast:
- Incremental rebuilds (only changed modules)
- Parallel processing
- Minimal browser updates (only changed code)
- No disk I/O for small changes
Examples
React application
import { useState } from "react";
import "./App.css";
export function App() {
const [count, setCount] = useState(0);
return (
<div className="app">
<h1>Counter: {count}</h1>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
</div>
);
}
import { createRoot } from "react-dom/client";
import { App } from "./App";
const root = createRoot(document.getElementById("root")!);
root.render(<App />);
bun build ./src/index.tsx --outdir ./dist --watch --react-fast-refresh
Vanilla JavaScript
import { render } from "./render";
import "./styles.css";
render();
if (import.meta.hot) {
import.meta.hot.accept("./render", (newRender) => {
newRender();
});
}
State management
class Store {
state = { count: 0 };
increment() {
this.state.count++;
}
}
const store = new Store();
if (import.meta.hot) {
// Preserve store state across HMR updates
if (import.meta.hot.data.store) {
Object.assign(store, import.meta.hot.data.store);
}
import.meta.hot.dispose((data) => {
data.store = store;
});
}
export { store };
Limitations
- HMR requires a development server (static files don’t support HMR)
- Not all changes can be hot-reloaded (e.g., changing module exports)
- Some state might be lost (e.g., closures, module-level variables)
- Full page reload is needed for:
- HTML changes
- Adding/removing files
- Changes to build configuration
- Changes to imported npm packages
Best practices
Keep state in React
Store application state in React components rather than module-level variables:
// Good: State in component
export function Counter() {
const [count, setCount] = useState(0);
return <div>{count}</div>;
}
// Bad: Module-level state (lost on HMR)
let count = 0;
export function Counter() {
return <div>{count}</div>;
}
Use HMR API for cleanup
Clean up side effects when a module is replaced:
const interval = setInterval(() => {
console.log("Tick");
}, 1000);
if (import.meta.hot) {
import.meta.hot.dispose(() => {
clearInterval(interval);
});
}
Organize for HMR
Split code into small modules that can be updated independently:
// Good: Small, focused modules
import { Button } from "./Button";
import { Input } from "./Input";
// Bad: Large modules that change frequently
import { Button, Input, Modal, Dropdown } from "./components";
Troubleshooting
Changes not detected
Make sure the file is imported by your entry point:
// Entry point must import changed files
import "./styles.css"; // This file will be watched
Full reload on every change
Check if your modules are self-accepting:
if (import.meta.hot) {
import.meta.hot.accept(); // Add this
}
State lost on update
Use React Fast Refresh for components or implement state preservation:
if (import.meta.hot) {
import.meta.hot.dispose((data) => {
data.myState = currentState;
});
import.meta.hot.accept(() => {
if (import.meta.hot.data.myState) {
restoreState(import.meta.hot.data.myState);
}
});
}