"Vite isn't magic. Vite is a Node HTTP server that intercepts module requests, transforms them on the fly, and uses esbuild as its compiler. Once you see that, configuring it stops being mysterious."
What Vite Actually Is
From the Node runtime's perspective:
- You run
vite. Node starts a process. - The process spins up an HTTP server (using
connector similar middleware framework underneath). - Your browser requests
http://localhost:5173/src/main.tsx. - Vite's HTTP middleware intercepts the request, reads the file from disk, transforms it (TS → JS via esbuild, JSX → JS via esbuild, CSS imports → JS that injects styles), and serves the result.
- The browser sees what looks like a tree of native ES modules. No bundling has occurred. Just on-the-fly Node-side transformation.
That's it. Hot module replacement is the same thing plus a WebSocket — when a file changes, Vite tells the browser "re-fetch this module," and the browser does, swapping the new code in without a page reload.
Why It Feels Fast
Production builds are different — for prod, Vite uses Rollup (which uses esbuild's transforms) to produce a proper bundle. The dev vs prod asymmetry is the design: dev is unbundled-for-speed, prod is bundled-for-shipping. The same source code produces both.
The Plugin API
Vite plugins are Rollup-compatible plugins with extra hooks for dev-server interception. Common hooks:
resolveId(id, importer)— turn an import specifier into a path.load(id)— return the file contents for a given id.transform(code, id)— modify the code as it passes through.handleHotUpdate(ctx)— control what happens when a file changes during HMR.
A SVG-as-React-component plugin: resolveId recognizes .svg imports, load reads the SVG, transform wraps it in a React component. 40 lines of plugin, drops a whole category of bundler config.
The Two Caches That Confuse People
Vite maintains two caches that don't always invalidate when you expect:
- Dependency pre-bundle (
node_modules/.vite/deps) — Vite pre-bundles your npm dependencies via esbuild on the first dev startup, then serves them as cached files. Speeds up cold starts. Invalidated on package.json changes — but stale caches happen when you swap branches. - Transform cache (in-memory) — transformed module content cached between requests. Usually fine; sometimes a stale cache after plugin config changes.
The fix when Vite "acts weird" after a dep change: rm -rf node_modules/.vite && pnpm dev. This isn't a Vite bug; it's the cost of caching aggressively.
SSR Mode
Vite has a less-publicized SSR mode where it transforms code for Node consumption (not the browser). Used by Vike, Astro, modern SvelteKit. The same plugin pipeline runs, but the output targets Node's module loader. This is how full-stack frameworks share code between client and server — same Vite, different output target.