CodeVetter · Repo Unpack · Behavior traces
vitejs/vite
Four flows are worth tracing: dev-server startup, a module request, a file-change HMR round trip, and a production build. Startup is notable for deferring `buildStart` and optimizer init until the HTTP `listen` call is intercepted. The module request path is a long but linear middleware chain. HMR distinguishes config/env changes (full server restart) from module changes (graph propagation). The build path is a per-environment loop over Rolldown.
- Dev startup: `bin/vite.js` → `dist/node/cli.js` → the default cac action → `createServer()` → `_createServer` → `disableCache()` → `resolveConfig(inlineConfig,'serve')` → `resolveHttpServer` → `createWebSocketServer` → chokidar watcher → per-environment `createEnvironment`/`init` → middleware registration → `server.listen()`.
- Startup defers expensive work by monkey-patching the HTTP server: `httpServer.listen` is replaced with a wrapper that awaits `initServer(true)` first and re-emits any error as an `'error'` event, so `buildStart` and `environment.listen(server)` run exactly once before the socket binds.
- In middleware mode there is no HTTP server at all — `httpServer` is `null`, `resolveHttpServer` is skipped, and `initServer(false)` is awaited inline at the end of `_createServer`.
- The middleware chain is registered in a fixed order: `timeMiddleware` (only when `process.env.DEBUG`) → `rejectInvalidRequestMiddleware` → `corsMiddleware` → `hostValidationMiddleware` → user `configureServer` hooks → `cachedTransformMiddleware` → `proxyMiddleware` → `baseMiddleware` → `/__open-in-editor` → HMR ping → `servePublicMiddleware` → `transformMiddleware` → `serveRawFsMiddleware` → `serveStaticMiddleware` → `htmlFallbackMiddleware` → post hooks → `indexHtmlMiddleware` → `notFoundMiddleware` → `errorMiddleware`.
- `configureServer` hooks run in two phases around the internal middlewares: the hook's synchronous body runs before `cachedTransformMiddleware` is added, while any function it *returns* is pushed to `postHooks` and invoked after the static/HTML-fallback middlewares but before `indexHtmlMiddleware`, which is what lets user middleware pre-empt `index.html`.
- A module request runs `transformMiddleware` → `environment.transformRequest(url)` → `transformRequest` (pending-request dedupe) → `doTransform` (`pluginContainer.resolveId`) → `loadAndTransform` (`pluginContainer.load`, then `pluginContainer.transform`) → optional `ssrTransform` → `send()` with the etag.
- `EnvironmentPluginContainer.resolveId` lazily triggers `this.buildStart()` and awaits `_buildStartPromise` on the first resolution, so for non-client environments the `buildStart` hook fires on first request rather than at server boot.
- File change → `watcher.on('change')` → `onFileChange` → `handleHMRUpdate(type, file, server)`, which first checks whether the file is `config.configFile`, a member of `config.configFileDependencies`, or an env file for the current mode — any of those triggers `restartServerWithUrls(server)` instead of an HMR update.
- `handleHMRUpdate` snapshots `server.environments` and defines `isStale()` comparing identity, because a `hotUpdate`/`handleHotUpdate` plugin hook may restart the server mid-transaction and invalidate the whole HMR pass.
- Edits to Vite's own client directory can't be hot-updated: if the changed file starts with `normalizedClientDir`, every environment immediately gets `hot.send({ type:'full-reload', path:'*' })` and the function returns.
- `updateModules` runs `propagateUpdate(mod, traversedModules, boundaries)` per changed module, invalidates via `moduleGraph.invalidateModule`, and escalates to full reload on any dead end, on an empty `modules` array, on `circular import invalidate`, or when a client-environment `.html` file changes and none of its modules are of type `js`.
- Production build: `cli.ts` build action → `createBuilder(inlineConfig, null)` → `builder.buildApp()` → sorted `buildApp` plugin hooks with `configBuilder.buildApp(builder)` interleaved before `order:'post'` hooks → fallback loop calling `builder.build(environment)` for any environment still `!isBuilt` → `buildEnvironment` → `rolldown()` → `bundle.write()` → `builder.runDevTools()`.
- Server shutdown runs `closeServer(reason)` which `Promise.allSettled`s watcher close, ws close, every `environment.close()`, `closeHttpServer()` and `_ssrCompatModuleRunner?.close()`, and only *afterwards* invokes the sorted `closeServer` plugin hooks with `{ reason }`.
- Browser reconnection is handled separately from HMR: `client.ts` opens a second WebSocket with the `vite-ping` subprotocol, which the server's `shouldHandle` allows from any origin without a token and which `handleUpgrade` closes immediately with code 1000 so the pinging client is never added to `wss.clients`.