vitejs/vite
Three input boundaries feed the system: the config file (loaded by bundling, runner-import, or native import), `.env` files filtered by prefix, and HTTP/WS requests from the browser. Config flows into a single frozen `ResolvedConfig` that fans out into per-environment configs; each `DevEnvironment` then owns a `EnvironmentModuleGraph` as the authoritative in-memory state. A request's data path is URL → plugin container `resolveId`→`load`→`transform` → optional `ssrTransform` → cached `TransformResult` (code + map + etag + deps) stored on the module node and written back out through `send()`. The reverse direction — file change → HMR payload → WebSocket → browser — is the only push channel. In build mode the same plugin container is driven by Rolldown instead of by HTTP requests, and the terminal output boundary is `outDir` plus optional `manifest.json`/`ssr-manifest.json`.
- Config entry: `resolveConfig` merges inline config over the file config from `loadConfigFromFile`, then re-derives `mode`, filters plugins by `apply`, runs `config` hooks via `runConfigHook`, and force-injects `client` (and usually `ssr`) into `config.environments` before per-environment resolution.
- `loadConfigFromFile` picks one of three loaders selected by `--configLoader`: `bundleAndLoadConfigFile` (default — Rolldown-bundles the config then evaluates it), `runnerImportConfigFile` (uses Vite's own module runner), or `nativeImportConfigFile` (raw `import()` of a `file://` URL with a `?t=` cache-buster, or `fresh-import`).
- Env vars enter through `loadEnv(mode, envDir, prefixes = 'VITE_')`, which reads `.env`, `.env.local`, `.env.<mode>`, `.env.<mode>.local` (accepting FIFOs so 1Password-style pipes work), parses with `node:util` `parseEnv`, then expands with `dotenv-expand` against a *copy* of `process.env` so expansion doesn't mutate the global.
- `transformRequest(environment, url, options)` dedupes concurrent work through `environment._pendingRequests` keyed by timestamp-stripped URL, and records a `monotonicDateNow()` timestamp up front so a transform that races an invalidation is discarded rather than cached stale.
- Inside `loadAndTransform` the order is `pluginContainer.load(id)` → fall back to reading from disk → `pluginContainer.transform(code, id, ...)` → `ssrTransform(code, normalizedMap, url, originalCode, ...)` when the environment needs module-runner output.
- The transform result carries `{ code, map, ssr?, etag?, deps?, dynamicDeps? }`; the etag is what `cachedTransformMiddleware` matches against `if-none-match` via `moduleGraph.getModuleByEtag` to short-circuit with a 304.
- `importAnalysisPlugin` is the rewrite stage that converts every import specifier to a server-resolvable URL via its internal `normalizeUrl`, records importer/imported edges on the module graph, and computes `isSelfAccepting` from `import.meta.hot.accept` calls — the data the HMR propagation algorithm later consumes.
- HMR output is one of two payload shapes computed in `updateModules`: a `{type:'full-reload', triggeredBy, path}` message, or a `{type:'update', updates:[...]}` array whose entries carry `path`, `acceptedPath`, `explicitImportRequired`, `isWithinCircularImport` and `firstInvalidatedBy`.
- Client-side the payload arrives over a `normalizeModuleRunnerTransport`-wrapped WebSocket opened to `${protocol}://${socketHost}?token=${wsToken}` with subprotocol `vite-hmr`, and is dispatched by `handleMessage` into `hmrClient.queueUpdate(update)`.
- Static assets bypass the transform pipeline entirely: `servePublicMiddleware` is registered *before* `transformMiddleware` specifically so `publicDir` files are served as-is, and `serveRawFsMiddleware` handles `/@fs/`-prefixed absolute paths by slicing `FS_PREFIX` off the pathname.
- In bundled-dev mode the data path inverts: Rolldown produces output files that `BundledDev` stores as lazily-evaluated entries in `MemoryFiles` (including `BUNDLED_DEV_CLIENT_FILENAME`), and `memoryFilesMiddleware` serves from that map instead of from the module graph.
- Build-mode data flow is fanned out by environment: `createBuilder` re-runs `resolveConfigToBuild` once per environment name (unless `configBuilder.sharedConfigBuild`) with a `patchPlugins` pass that swaps in shared plugin instances for those marked `sharedDuringBuild`.
- Module-runner data flow closes the loop server→runtime: `ModuleRunner.directRequest` calls `this.transport.invoke('fetchModule', [...])`, the server answers from `fetchModule`, and the result is evaluated and cached in `EvaluatedModules` keyed by both id and url.
- The `ResolvedConfig` object is also the carrier for two derived security primitives computed once at resolve time: `webSocketToken` (72 random bits, base64url) and `fsDenyGlob` (a `picomatch` matcher built from `server.fs.deny` with `nocase` and `dot` enabled).