esbuild’s watch mode lets you rebuild your project in milliseconds whenever a file changes, giving you an instant feedback loop for development.
Here’s how it works in practice. Let’s say you have a simple index.js file:
// index.js
console.log("Hello, world!");
And an index.html to include it:
<!DOCTYPE html>
<html>
<head>
<title>esbuild Watch</title>
</head>
<body>
<script src="dist/bundle.js"></script>
</body>
</html>
You’d typically start esbuild in watch mode like this:
npx esbuild index.js --bundle --outfile=dist/bundle.js --servedir=. --watch
Now, open index.html in your browser. The first time, esbuild bundles index.js into dist/bundle.js. The --servedir=. flag tells esbuild to serve files from the current directory, and --watch makes it re-run the build whenever a file changes.
If you change index.js to:
// index.js
console.log("Hello again!");
And save, you’ll see esbuild recompile almost instantly in your terminal. Refreshing your browser will then show "Hello again!".
The magic is in how esbuild efficiently tracks file system changes and only recompiles what’s necessary. It doesn’t re-parse your entire project or re-run every single plugin. Instead, it maintains an in-memory representation of your module graph and updates it incrementally. When a file changes, esbuild identifies which modules are affected by that change and rebuilds only those parts of the graph, then generates the output. This granular approach is what makes it so fast.
The core levers you control are the input files, output file, and the directory to serve. The --servedir flag is crucial for development because it often implies a development server, which esbuild can also provide. When used with --watch, esbuild starts a simple HTTP server that automatically reloads the browser page when the output files change, thanks to a small WebSocket client injected into the served HTML. This eliminates the need for external tools like live-server or browser-sync for basic cases.
Consider what happens when you have multiple entry points or complex dependencies. esbuild handles this by building a dependency tree. When a leaf node in that tree (a source file) is modified, esbuild traverses upwards, invalidating and recomputing only the nodes that depend on the changed file. The --splitting flag, for instance, can create multiple output bundles. If you change a shared utility module, esbuild will correctly identify all the output bundles that import it and rebuild only those, rather than everything.
The one thing most people don’t realize is that esbuild’s watch mode can also be used programmatically. You can import esbuild as a module and use its build function with the watch option set to true. This gives you fine-grained control over the build process, allowing you to integrate esbuild’s rapid rebuilding into custom development workflows, test runners, or even live-editing environments beyond simple browser reloads. You can listen for build events and trigger other actions, like running linters or deploying changes to a local development server.
The next step after mastering instant rebuilds is understanding how to leverage esbuild’s plugin API for more advanced transformations and optimizations during the watch process.