the graph.
The dev server is the build.
Before Vite, every change you made to a JavaScript file in development triggered a small re-bundle: the bundler walked the import graph, transformed each module, glued them together, and shipped the result. On a small project this took half a second. On a large one it took ten. The cost grew with the codebase, even though the actual change was a single line in a single file. Webpack's HMR softened it; it did not solve it.
Vite changed the contract. In development, the browser fetches your modules natively over import; the dev server is a small middleman that transforms each file on first request, caches the result, and watches the disk. When you save, Vite walks the reverse-edges of the graph above — finds every module that imports the changed one, every module that imports those, and so on — and tells the browser to swap exactly that subset out. The graph above is what Vite is doing in your terminal, in real time, every time you press save.
The dev server walks the same graph you do.
What HMR actually is.
Hot module replacement is a discipline as much as a feature. The dev server can swap modules; the modules have to consent to being swapped. Most modules do nothing — they let the change bubble up to a parent that knows how to re-render. A boundary is a module that says stop, I'll handle it from here. CSS files are boundaries: they self-update. React components, with React Refresh, are boundaries: they patch their tree without losing state. Plain JS files generally are not — a change ripples up until it hits one.
In the graph above, click utils.js. The patch propagates from utils to api.js, to App.jsx, to main.js, and finally to the entry — at which point Vite gives up and triggers a full reload. Click styles.css instead: the patch goes nowhere. The CSS knows how to swap itself in place. The same edit, on a different file, has a completely different blast radius.
Two engines under the hood.
The honest secret of Vite is that it is two pieces of software, glued. In development, when you ask for the latest 70-byte change to a file you just typed, Vite calls esbuild — a Go binary that compiles TypeScript and JSX faster than your file system can deliver bytes. In production, when you ship, Vite hands the whole graph to rollup — a tree-shaking bundler with a decade of optimization work in it. You never call either tool by name. They are wrapped inside one config file and one verb: vite build.
Most of the specimens in this series are deployed exactly that way. three.js, tone.js, rapier, transformers.js — all imported by Vite-built shells, hot-reloaded on save, then bundled by Rollup for the production drop. The build pipeline above is the one this page came out of. The graph is real.
The graph is real.