# vitejs/vite — codebase unpack

> Canonical page: https://codevetter.com/unpack/vite

This is a deterministic Repo Unpack scan of vitejs/vite — a structural map, not a review, security audit, or maintainer-endorsed description.

- Scanned commit: 1544bb1b7ac92f775e9d0dea97954c99cc6cbd43 (2026-09-22)
- Last upstream commit: docs(config): correct the server.sourcemapIgnoreList default (#23462)
- Generated by codevetter 1.12.0 on 2026-09-22
- 2718 files scanned · 5.6 MB

## Languages

- JavaScript: 979 files
- TypeScript: 591 files
- JSON: 386 files
- CSS: 205 files
- HTML: 156 files
- Markdown: 84 files
- Sass: 30 files
- Vue: 27 files

## Repository structure

- playground/ — 1756 files
- packages/ — 797 files
- docs/ — 116 files
- .github/ — 24 files
- scripts/ — 8 files
- patches/ — 3 files

Probable source entrypoint: `packages/` (inferred from the tree, not upstream docs)

## Package

- package.json: @vitejs/vite-monorepo — 32 declared dependencies

## Structural health signals

Average score 7/10 across 96 files; 36 hotspots. These are deterministic review leads, not proof of a bug.

## Recent history

- 1544bb1b7ac9 (2026-09-22) docs(config): correct the server.sourcemapIgnoreList default (#23462)
- 39330f489a0a (2026-09-22) fix(optimizer): don't skip imports whose binding starts with type (#23540)
- bd97903e326c (2026-09-22) docs(config): document preserveEntrySignatures defaults (#23469)
- 15569ce343b5 (2026-09-22) docs: fix typo builtin -> built-in in plugins guide (#23525)
- 9abd99bfdd31 (2026-09-21) refactor: remove duplicate configurations (#23532)

## Overview

This is the Vite monorepo (`@vitejs/vite-monorepo`) at v8.3.0 — a pnpm workspace shipping three published packages: `vite` (the native-ESM dev server + Rolldown-powered bundler), `create-vite` (the scaffolding CLI), and `@vitejs/plugin-legacy` (SystemJS/Babel downlevel output for old browsers). The `vite` package is the bulk of it: a `cac`-driven CLI over four commands (dev/build/optimize/preview), a `connect` middleware stack that transforms modules on demand and pushes HMR over an authenticated WebSocket, and a Rollup-compatible plugin container that now runs on Rolldown/Oxc rather than Rollup+esbuild. What's distinctive at this commit is the Environment API — dev and build are both fanned out across named environments (`client`, `ssr`, custom) each with its own module graph, plugin container and optimizer — plus an experimental `bundledDev` mode that swaps the per-module transform pipeline for a lazy Rolldown dev bundle served from memory. Correctness is enforced almost entirely by a 83-playground / 109-spec Playwright+Vitest integration suite that runs every fixture in serve, bundled-dev, and build modes.

## System map

Three publishable packages under `packages/`, a 83-directory `playground/` that doubles as the integration test corpus, and a VitePress `docs/` site deployed to Netlify. The `vite` package's runtime splits four ways: `src/node` (server, build, config, plugins — Node only), `src/client` (browser HMR client and error overlay), `src/module-runner` (SSR/runtime module evaluation), and `src/shared` (code compiled into both sides). All bundling — of Vite itself, of user configs, of dep pre-bundles, and of production output — goes through Rolldown; Rollup remains only as a type source and devDependency. There is no database or persistent store; the only on-disk state is the dep-optimizer cache under `cacheDir` and build output under `outDir`.
- The published CLI entry is `packages/vite/bin/vite.js`, a thin launcher that enables source maps, installs an `unhandledRejection` trap, parses `-d/--debug`, `-f/--filter` and `--profile` out of `process.argv` *before* importing `../dist/node/cli.js`, and calls `module.enableCompileCache()` with a 10s `flushCompileCache` timer. — [packages/vite/bin/vite.js](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/bin/vite.js) [packages/vite/package.json#L20-22](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/package.json#L20-L22)
- `packages/vite/src/node/cli.ts` registers exactly four `cac` commands: the default `[root]` (aliased `serve` and `dev`), `build [root]`, `optimize [root]` (marked deprecated in its own description), and `preview [root]`; each dynamically `import()`s its implementation module so the dev path never loads `build.ts`. — [packages/vite/src/node/cli.ts#L199-491](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/cli.ts#L199-L491)
- The package's public API surface is `packages/vite/src/node/index.ts`, which re-exports `createServer`, `build`, `createBuilder`, `preview`, `optimizeDeps`, `defineConfig`/`resolveConfig`/`loadConfigFromFile`, `runnerImport`, `fetchModule`, `createServerHotChannel`, `transformWithOxc`, `preprocessCSS`, and `parseAst`/`parseAstAsync` aliased from Rolldown. — [packages/vite/src/node/index.ts#L8-106](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/index.ts#L8-L106)
- `package.json` `exports` publishes four entry points — `.` → `dist/node/index.js`, `./module-runner` → `dist/node/module-runner.js`, `./internal` → `dist/node/internal.js`, and raw `./dist/client/*` — with `./types/internal/*` explicitly nulled to keep internal types unreachable. — [packages/vite/package.json#L34-46](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/package.json#L34-L46)
- Vite builds itself with Rolldown, not Rollup: `rolldown.config.ts` emits separate browser bundles for `src/client/env.ts` → `dist/client/env.mjs`, `src/client/clientEntry.ts` → `dist/client/client.mjs`, and `src/client/bundledDevClient.ts` → `dist/client/bundledDevClient.mjs`, plus a Node ESM bundle with `node/[name].js` entries and `node/chunks/` shared chunks. — [packages/vite/rolldown.config.ts#L14-80](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/rolldown.config.ts#L14-L80) [packages/vite/package.json#L48-54](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/package.json#L48-L54)
- The dev server is a `connect()` app, not a framework: `_createServer` builds `middlewares = connect()`, wraps it in `resolveHttpServer`, attaches `createWebSocketServer`, and creates a `chokidar` watcher over root + `config.configFileDependencies` + env files + `publicDir`. — [packages/vite/src/node/server/index.ts#L568-597](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/index.ts#L568-L597)
- Runtime state is owned per-environment, not globally: `_createServer` iterates `config.environments` calling `environmentOptions.dev.createEnvironment(name, config, { ws })` and `environment.init({ watcher, previousInstance })`, so each of `client`/`ssr`/custom gets its own `DevEnvironment` with its own `moduleGraph` and `pluginContainer`. — [packages/vite/src/node/server/index.ts#L598-617](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/index.ts#L598-L617) [packages/vite/src/node/server/environment.ts#L56-225](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/environment.ts#L56-L225)
- The legacy flat `server.moduleGraph` / `server.pluginContainer` / `server.transformRequest` APIs still exist but are getters that call `warnFutureDeprecation(config, 'removeServerModuleGraph')` etc. and delegate to the client environment via a `ModuleGraph` facade in `mixedModuleGraph.ts`. — [packages/vite/src/node/server/index.ts#L620-710](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/index.ts#L620-L710) [packages/vite/src/node/server/mixedModuleGraph.ts](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/mixedModuleGraph.ts)
- Production builds go through `createBuilder` → `builder.buildApp()` → per-environment `buildEnvironment()`, which calls `resolveRolldownOptions(environment, chunkMetadataMap)` then `rolldown(...)` and `bundle.write|generate`; `--watch` instead returns a Rolldown `watch()` watcher wired to BUNDLE_START/BUNDLE_END/ERROR events. — [packages/vite/src/node/build.ts#L840-940](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/build.ts#L840-L940) [packages/vite/src/node/build.ts#L1834-1900](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/build.ts#L1834-L1900)
- The pnpm workspace spans `packages/*`, `playground/**`, nested `packages/**/__tests__/**`, and `docs`, with `overrides: { vite: 'workspace:*' }` so every playground resolves the local build, and patched deps for `sirv@3.0.2`, `chokidar@3.6.0` and `dotenv-expand@13.0.0`. — [pnpm-workspace.yaml](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/pnpm-workspace.yaml) [patches/](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/patches/)
- Build/test commands are all at the root: `pnpm build` (`pnpm -r --filter='./packages/*' run build`), `pnpm test` = `test-unit && test-serve && test-build`, plus `test-serve-bundled` which sets `VITE_TEST_BUNDLED_DEV=1`, and `ci-publish` (`tsx scripts/publishCI.ts`). — [package.json#L18-40](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/package.json#L18-L40) [CONTRIBUTING.md#L137-166](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/CONTRIBUTING.md#L137-L166)
- Docs are a separate VitePress app (`@vitejs/monorepo-docs`) with a 592-line `docs/.vitepress/config.ts`, deployed by Netlify via `command = "pnpm ci-docs"` publishing `docs/.vitepress/dist`, gated by `scripts/docs-check.sh` as the ignore predicate. — [netlify.toml](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/netlify.toml) [docs/package.json](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/docs/package.json) [docs/.vitepress/config.ts](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/docs/.vitepress/config.ts)
- External integrations are optional peer deps resolved lazily: `sass`/`sass-embedded`/`less`/`stylus`/`sugarss` for CSS, `terser` for minification, `jiti`/`tsx` for config loading, and `@vitejs/devtools` (loaded via `await import('@vitejs/devtools/integration')` inside `builder.runDevTools()`). — [packages/vite/package.json#L131-165](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/package.json#L131-L165) [packages/vite/src/node/build.ts#L1899-1915](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/build.ts#L1899-L1915)
- The repo now depends on hosted build-cache infrastructure: `@voidzero-dev/vite-task-client` supplies `disableCache()` (called first thing in `_createServer` because dev output can't be replayed) and `getEnv('NODE_ENV')`/`getEnvs` used during config and env resolution. — [packages/vite/src/node/server/index.ts#L12-13](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/index.ts#L12-L13) [packages/vite/src/node/server/index.ts#L522-524](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/index.ts#L522-L524) [packages/vite/src/node/env.ts#L5](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/env.ts#L5)

## Feature catalog

Vite's user-facing surface is four CLI commands, a programmatic JS API, a Rollup-superset plugin hook system, and a browser-side HMR runtime with an error overlay. Around that sit optional subsystems: dependency pre-bundling, CSS preprocessing and modules, worker/WASM/asset handling, `import.meta.glob`, SSR module running, and the legacy-browser plugin. `create-vite` is an independent interactive scaffolder with eleven framework families. The most recently added features are the multi-environment builder (`--app` / `builder: {}`), console forwarding from browser to terminal, and `--experimentalBundle` bundled dev.
- `vite dev` accepts `--host`, `--port`, `--open [path]`, `--cors`, `--strictPort`, `--force` (bust the optimizer cache) and `--experimentalBundle`, then prints URLs and installs CLI shortcuts, adding a `p` shortcut to toggle the CPU profiler when launched under `--profile`. — [packages/vite/src/node/cli.ts#L200-306](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/cli.ts#L200-L306)
- `vite build` exposes `--target`, `--outDir`, `--assetsDir`, `--assetsInlineLimit`, `--ssr [entry]`, `--sourcemap [inline|hidden]`, `--minify [oxc|terser|esbuild]` (default `oxc`), `--manifest`, `--ssrManifest`, `--emptyOutDir`, `-w/--watch`, and `--app` which is sugar for `builder: {}`. — [packages/vite/src/node/cli.ts#L310-380](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/cli.ts#L310-L380)
- Interactive terminal shortcuts during dev are `r` restart server, `u` show server url, `o` open in browser, `c` clear console, `q` quit; preview mode registers only `o` and `q`. — [packages/vite/src/node/shortcuts.ts#L128-190](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/shortcuts.ts#L128-L190)
- The dev server exposes built-in HTTP endpoints beyond module serving: `/__open-in-editor` (via `launch-editor-middleware`), a `viteHMRPingMiddleware` answering `accept: text/x-vite-ping` with 204, and `/@fs/` raw filesystem serving guarded by the allow-list. — [packages/vite/src/node/server/index.ts#L1031-1058](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/index.ts#L1031-L1058) [packages/vite/src/node/constants.ts#L115](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/constants.ts#L115)
- A dev proxy is available via `server.proxy`, backed by `http-proxy-3`, supporting `rewrite`, `configure`, webpack-dev-server-style `bypass`, WebSocket proxying, and `rewriteWsOrigin` to rewrite the Origin header on WS upgrades. — [packages/vite/src/node/server/middlewares/proxy.ts#L13-60](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/middlewares/proxy.ts#L13-L60) [packages/vite/src/node/server/middlewares/proxy.ts#L107-180](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/middlewares/proxy.ts#L107-L180)
- The browser client ships an error overlay implemented as the custom element `vite-error-overlay` (`class ErrorOverlay extends HTMLElement`), gated by the injected `__HMR_ENABLE_OVERLAY__` define and creatable/clearable from `handleMessage`. — [packages/vite/src/client/overlay.ts#L216-305](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/client/overlay.ts#L216-L305) [packages/vite/src/client/client.ts#L343-360](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/client/client.ts#L343-L360)
- Dependency pre-bundling is a full subsystem: `scanImports` computes entries and runs a Rolldown scanner, `runOptimizeDeps` writes a `_metadata.json` cache under `getDepsCacheDir(environment)`, and `loadCachedDepOptimizationMetadata` / `cleanupDepsCacheStaleDirs` manage reuse and GC across runs. — [packages/vite/src/node/optimizer/index.ts#L284-520](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/optimizer/index.ts#L284-L520) [packages/vite/src/node/optimizer/scan.ts#L116-320](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/optimizer/scan.ts#L116-L320) [packages/vite/src/node/constants.ts#L222](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/constants.ts#L222)
- The built-in plugin pipeline provides asset handling, CSS (`cssPlugin`/`cssPostPlugin`/`cssAnalysisPlugin`), HTML (`htmlInlineProxyPlugin`/`buildHtmlPlugin`), web workers (`webWorkerPlugin`, `workerImportMetaUrlPlugin`), WASM (`wasmHelperPlugin`), `define`, `import.meta.glob` (`importGlobPlugin`), dynamic-import variables, and module-preload polyfill. — [packages/vite/src/node/plugins/index.ts#L42-150](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/plugins/index.ts#L42-L150)
- Browser console forwarding to the terminal is opt-in via `server.forwardConsole.enabled`, which conditionally inserts `forwardConsolePlugin({ environments: ['client'] })` into the pipeline and is picked up client-side through the `__SERVER_FORWARD_CONSOLE__` define. — [packages/vite/src/node/plugins/index.ts#L120-123](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/plugins/index.ts#L120-L123) [packages/vite/src/client/client.ts#L26-45](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/client/client.ts#L26-L45) [packages/vite/src/shared/forwardConsole.ts](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/shared/forwardConsole.ts)
- SSR is served by `fetchModule(environment, url, importer)`, which short-circuits `data:` URLs and Node builtins to `{externalize, type:'builtin'}`, external URLs to `type:'network'`, and otherwise resolves bare specifiers through `tryNodeResolve` with `externalConditions`. — [packages/vite/src/node/ssr/fetchModule.ts#L24-70](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/ssr/fetchModule.ts#L24-L70)
- `vite/module-runner` is a separately published runtime: `class ModuleRunner` with `evaluatedModules`, a pluggable `transport` that `invoke('fetchModule', ...)`s the server, optional HMR via `createHMRHandlerForRunner`, and `cachedRequest`/`directRequest` for circular-dependency-aware evaluation. — [packages/vite/src/module-runner/runner.ts#L38-340](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/module-runner/runner.ts#L38-L340) [packages/vite/package.json#L37](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/package.json#L37)
- `create-vite` v9.2.1 is an interactive prompt-driven scaffolder over a `FRAMEWORKS` table covering vanilla, vue, react, preact, lit, svelte, solid and qwik, each with TS/JS variants plus links to external starters (Nuxt, Vike, Official Vue Starter), materialised from the sibling `template-*` directories. — [packages/create-vite/src/index.ts#L80-160](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/create-vite/src/index.ts#L80-L160) [packages/create-vite/package.json](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/create-vite/package.json)
- `@vitejs/plugin-legacy` v8.2.3 returns a plugin array (`legacyConfigPlugin`, `legacyGenerateBundlePlugin`, `legacyPostPlugin`) that runs `detectPolyfills`, transforms modern chunks to SystemJS via `@babel/plugin-transform-modules-systemjs`, injects `systemjs/dist/s.min.js`, and emits a `systemjs-importmap` script tag. — [packages/plugin-legacy/src/index.ts#L274-460](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/plugin-legacy/src/index.ts#L274-L460) [packages/plugin-legacy/src/index.ts#L650-790](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/plugin-legacy/src/index.ts#L650-L790) [packages/plugin-legacy/src/index.ts#L911-1070](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/plugin-legacy/src/index.ts#L911-L1070)
- Experimental full-bundle dev mode (`--experimentalBundle` → `config.experimental.bundledDev`) replaces the transform/static middlewares with `triggerLazyBundlingMiddleware` + `memoryFilesMiddleware` and is driven by the `BundledDev` class holding a `MemoryFiles` map and a Rolldown dev engine started with `experimental.devMode = { lazy: true }`. — [packages/vite/src/node/server/index.ts#L1049-1060](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/index.ts#L1049-L1060) [packages/vite/src/node/server/bundledDev.ts#L28-130](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/bundledDev.ts#L28-L130) [packages/vite/src/node/server/bundledDev.ts#L396-410](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/bundledDev.ts#L396-L410)

## Data flow

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. — [packages/vite/src/node/config.ts#L1489-1610](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/config.ts#L1489-L1610)
- `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`). — [packages/vite/src/node/config.ts#L2423-2535](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/config.ts#L2423-L2535)
- 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. — [packages/vite/src/node/env.ts#L12-80](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/env.ts#L12-L80)
- `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. — [packages/vite/src/node/server/transformRequest.ts#L76-150](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/transformRequest.ts#L76-L150)
- 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. — [packages/vite/src/node/server/transformRequest.ts#L237-430](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/transformRequest.ts#L237-L430)
- 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. — [packages/vite/src/node/server/transformRequest.ts#L48-56](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/transformRequest.ts#L48-L56) [packages/vite/src/node/server/middlewares/transform.ts#L74-106](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/middlewares/transform.ts#L74-L106)
- `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. — [packages/vite/src/node/plugins/importAnalysis.ts#L225-340](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/plugins/importAnalysis.ts#L225-L340) [packages/vite/src/node/plugins/importAnalysis.ts#L500-600](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/plugins/importAnalysis.ts#L500-L600) [packages/vite/src/node/plugins/importAnalysis.ts#L840-880](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/plugins/importAnalysis.ts#L840-L880)
- 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`. — [packages/vite/src/node/server/hmr.ts#L683-790](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/hmr.ts#L683-L790)
- 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)`. — [packages/vite/src/client/client.ts#L46-60](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/client/client.ts#L46-L60) [packages/vite/src/client/client.ts#L187-230](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/client/client.ts#L187-L230)
- 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. — [packages/vite/src/node/server/index.ts#L1044-1059](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/index.ts#L1044-L1059) [packages/vite/src/node/server/middlewares/static.ts#L204-230](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/middlewares/static.ts#L204-L230)
- 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. — [packages/vite/src/node/server/bundledDev.ts#L28-130](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/bundledDev.ts#L28-L130) [packages/vite/src/node/server/bundledDev.ts#L371-390](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/bundledDev.ts#L371-L390) [packages/vite/src/node/server/middlewares/memoryFiles.ts](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/middlewares/memoryFiles.ts)
- 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`. — [packages/vite/src/node/build.ts#L1920-1975](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/build.ts#L1920-L1975)
- 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. — [packages/vite/src/module-runner/runner.ts#L245-340](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/module-runner/runner.ts#L245-L340) [packages/vite/src/node/ssr/fetchModule.ts#L24-70](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/ssr/fetchModule.ts#L24-L70)
- 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). — [packages/vite/src/node/config.ts#L2174-2212](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/config.ts#L2174-L2212)

## Behavior traces

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()`. — [packages/vite/src/node/cli.ts#L219-243](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/cli.ts#L219-L243) [packages/vite/src/node/server/index.ts#L512-620](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/index.ts#L512-L620)
- 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. — [packages/vite/src/node/server/index.ts#L1095-1136](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/index.ts#L1095-L1136)
- 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`. — [packages/vite/src/node/server/index.ts#L569-571](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/index.ts#L569-L571) [packages/vite/src/node/server/index.ts#L1128-1134](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/index.ts#L1128-L1134)
- 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`. — [packages/vite/src/node/server/index.ts#L979-1090](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/index.ts#L979-L1090)
- `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`. — [packages/vite/src/node/server/index.ts#L1000-1010](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/index.ts#L1000-L1010) [packages/vite/src/node/server/index.ts#L1071-1082](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/index.ts#L1071-L1082)
- 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. — [packages/vite/src/node/server/transformRequest.ts#L76-430](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/transformRequest.ts#L76-L430) [packages/vite/src/node/server/environment.ts#L300-303](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/environment.ts#L300-L303) [packages/vite/src/node/server/send.ts](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/send.ts)
- `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. — [packages/vite/src/node/server/pluginContainer.ts#L320-375](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/pluginContainer.ts#L320-L375) [packages/vite/src/node/server/index.ts#L1100-1106](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/index.ts#L1100-L1106)
- 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. — [packages/vite/src/node/server/index.ts#L960-968](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/index.ts#L960-L968) [packages/vite/src/node/server/hmr.ts#L411-452](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/hmr.ts#L411-L452)
- `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. — [packages/vite/src/node/server/hmr.ts#L418-425](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/hmr.ts#L418-L425)
- 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. — [packages/vite/src/node/server/hmr.ts#L454-466](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/hmr.ts#L454-L466)
- `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`. — [packages/vite/src/node/server/hmr.ts#L683-775](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/hmr.ts#L683-L775)
- 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()`. — [packages/vite/src/node/cli.ts#L340-370](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/cli.ts#L340-L370) [packages/vite/src/node/build.ts#L1855-1900](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/build.ts#L1855-L1900) [packages/vite/src/node/build.ts#L860-930](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/build.ts#L860-L930)
- 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 }`. — [packages/vite/src/node/server/index.ts#L628-660](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/index.ts#L628-L660)
- 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`. — [packages/vite/src/client/client.ts#L478-490](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/client/client.ts#L478-L490) [packages/vite/src/node/server/ws.ts#L169-212](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/ws.ts#L169-L212)

## Testing signals

Testing is Vitest-only, split into two configs. `vitest.config.ts` collects 68 unit specs from `**/__tests__/**/*.spec.[tj]s` inside `packages/`, running with `isolate: false` and a 20s timeout. `vitest.config.e2e.ts` collects 109 integration specs from `playground/**`, driving a real Playwright Chromium browser against real Vite servers. The integration suite is the primary correctness net and runs three times in CI — serve, bundled-dev, and build — across Node 20/22/24/26 on Ubuntu plus macOS and Windows. The weakest spot is that unit coverage of the largest files is thin relative to their size: `css.ts` (3685 lines) and `hmr.ts` (1193 lines) have no dedicated unit spec, and are covered only indirectly through playground fixtures.
- Unit tests use `vitest.config.ts` with `include: ['**/__tests__/**/*.spec.[tj]s']`, explicitly excluding `./playground/**` and `./playground-temp/**`, `testTimeout: 20000`, `isolate: false`, and an alias mapping `vite/module-runner` to the TypeScript source rather than `dist`. — [vitest.config.ts](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/vitest.config.ts)
- There are 68 unit spec files under `packages/**/__tests__/`, concentrated in `packages/vite/src/node/__tests__/` (config, build, dev, env, html, http, resolve, scan, utils, oxc, packages, shortcuts, external, environment, filterRegex, assetSource) plus `plugins/`, `optimizer/` and `utils/` subdirectories. — [packages/vite/src/node/__tests__/](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/__tests__/) [packages/vite/src/node/__tests__/config.spec.ts](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/__tests__/config.spec.ts)
- Integration tests use `vitest.config.e2e.ts` with `include: ['./playground/**/*.spec.[tj]s']`, `globalSetup: playground/vitestGlobalSetup.ts`, `setupFiles: playground/vitestSetup.ts`, `reporters: 'dot'`, and a timeout of 50s on CI / 30s locally (Infinity under `PWDEBUG`). — [vitest.config.e2e.ts#L36-78](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/vitest.config.e2e.ts#L36-L78)
- `vitestGlobalSetup.ts` launches a shared `chromium.launchServer()` (headed when `VITE_DEBUG_SERVE` is set, `--no-sandbox` on CI), shares the endpoint via `project.provide('wsEndpoint', ...)`, then copies only the playground directories actually referenced by the selected test files into `playground-temp/`. — [playground/vitestGlobalSetup.ts#L11-60](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/playground/vitestGlobalSetup.ts#L11-L60)
- `vitestSetup.ts` exposes the per-test harness — `page`, `viteTestUrl`, `browserLogs`, `browserErrors`, `serverLogs`, and mode flags `isBuild`/`isServe`/`isBundledDev`/`isBundled` — aliased in tests as `~utils`. — [playground/vitestSetup.ts#L64-120](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/playground/vitestSetup.ts#L64-L120) [vitest.config.e2e.ts#L38-43](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/vitest.config.e2e.ts#L38-L43)
- The same 109 playground specs are executed under three different modes selected by env vars: default serve, `VITE_TEST_BUNDLED_DEV=1` (`test-serve-bundled`), and `VITE_TEST_BUILD=1` (`test-build`), with `NODE_ENV` set to `production` only in the build run. — [package.json#L21-25](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/package.json#L21-L25) [vitest.config.e2e.ts#L4-6](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/vitest.config.e2e.ts#L4-L6) [vitest.config.e2e.ts#L70-73](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/vitest.config.e2e.ts#L70-L73)
- Known gaps in bundled-dev mode are tracked as an explicit 22-entry `bundledDevExclude` array in `vitest.config.e2e.ts` — including `fs-serve`, `csp`, `hmr-ssr`, `optimize-deps`, `tailwind` and all four `worker` variants — with the convention that partial failures use `test.skipIf(isBundledDev)` instead of listing the file. — [vitest.config.e2e.ts#L11-34](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/vitest.config.e2e.ts#L11-L34) [CONTRIBUTING.md#L155](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/CONTRIBUTING.md#L155)
- CI runs the full matrix Node 20/22/24/26 on ubuntu-latest plus macos-latest@24 and windows-latest@24.15.0 (pinned to work around nodejs/node#63638), with `fail-fast: false` and a 20-minute timeout per job. — [.github/workflows/ci.yml#L61-80](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/.github/workflows/ci.yml#L61-L80)
- The CI `test` job runs all four suites sequentially — `test-unit`, `test-serve`, `test-serve-bundled`, `test-build` — after a `pnpm run build`, and a separate `lint` job runs eslint, `pnpm format && git diff --exit-code`, `pnpm typecheck`, and `pnpm test-docs`. — [.github/workflows/ci.yml#L138-200](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/.github/workflows/ci.yml#L138-L200)
- CI skips the whole test matrix when only docs, `.github/**` (excluding `ci.yml`), `packages/create-vite/template**`, or `**.md` changed, via a `changed` job using `tj-actions/changed-files` with `only_changed`. — [.github/workflows/ci.yml#L34-59](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/.github/workflows/ci.yml#L34-L59)
- Flakiness is managed with environment knobs rather than retries in test code: `VITEST_SEGFAULT_RETRY: 3` and `NODE_OPTIONS: --max-old-space-size=6144` are set globally in the workflow env block. — [.github/workflows/ci.yml#L3-10](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/.github/workflows/ci.yml#L3-L10)
- A scheduled Semgrep scan (`--config auto`, weekly on Tuesdays) uploads SARIF to GitHub code scanning, but explicitly excludes `playground` and `__tests__` and disables the `detect-non-literal-regexp` rule — so dynamic regex construction in source is not flagged. — [.github/workflows/semgrep.yml](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/.github/workflows/semgrep.yml)
- Type-level tests exist as a separate mechanism: `src/node/__tests_dts__` and `src/module-runner/__tests_dts__` are compiled by the package `typecheck` script (`tsc -p src/node/__tests_dts__`), using `@type-challenges/utils`. — [packages/vite/package.json#L60](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/package.json#L60) [package.json#L46](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/package.json#L46)
- Type output is separately validated after bundling: `build-types` runs `rolldown.dts.config.ts` then `tsc --project tsconfig.check.json`, and CONTRIBUTING documents `pnpm run build-types-check` as the guard against d.ts leaking devDependency types. — [packages/vite/package.json#L55-58](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/package.json#L55-L58) [CONTRIBUTING.md#L66-72](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/CONTRIBUTING.md#L66-L72)

## Risk map

Vite's security model rests on four independent guards — the `server.fs` allow/deny list, WebSocket token authentication, host validation, and CORS origin restriction — and a bypass in any one of them exposes the developer's filesystem or dev server to a hostile web page. The code shows scar tissue from past CVEs: Windows 8.3 short-name detection, NTFS ADS colon rejection, `#`-in-URL rejection, and trailing-slash normalisation are all defensive patches with explanatory comments. Beyond security, the main structural risks are the very large monolithic modules (`css.ts` at 3685 lines, `config.ts` at 2961), the two-implementation split between classic dev and `bundledDev`, and a large surface of deprecated-but-live compatibility shims.
- `isFileLoadingAllowed` is the single chokepoint for filesystem access and carries three separate bypass mitigations: Windows 8.3 short-name paths (`PROGRA~1`, matched by `windowsShortNameSegmentRE`) are rejected outright, any `:` outside a drive letter is rejected as a possible NTFS alternate data stream, and trailing slashes are stripped before the deny check because `fs.readFile('/foo.png/')` resolves to `/foo.png`. — [packages/vite/src/node/server/middlewares/static.ts#L291-345](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/middlewares/static.ts#L291-L345)
- `server.fs.deny` defaults to only six patterns — `.env`, `.env.*`, `*.{crt,pem,key,p12,pfx,cer,der}`, `.npmrc`, `.yarnrc.yml`, `**/.git/**` — so any other secret-bearing file inside an allowed root (e.g. `credentials.json`, `.aws/`, `id_rsa` without extension) is servable when `fs.strict` passes. — [packages/vite/src/node/server/index.ts#L1229-1240](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/index.ts#L1229-L1240)
- `config.safeModulePaths` is an escape hatch checked *before* the `fs.allow` list in `isFileLoadingAllowed`, and it is populated at runtime by `DevEnvironment._registerInputsAsSafeModules()` — meaning build inputs silently widen the serving allow-list. — [packages/vite/src/node/server/middlewares/static.ts#L340-347](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/middlewares/static.ts#L340-L347) [packages/vite/src/node/server/environment.ts#L225-270](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/environment.ts#L225-L270) [packages/vite/src/node/config.ts#L2213](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/config.ts#L2213)
- `rejectInvalidRequestMiddleware` exists specifically because Node accepts `#` in request targets while the `server.fs.deny` checks assume it is absent — a 400 is returned for any URL containing `#`, and this middleware is the first in the chain. — [packages/vite/src/node/server/middlewares/rejectInvalidRequest.ts](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/middlewares/rejectInvalidRequest.ts) [packages/vite/src/node/server/index.ts#L985](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/index.ts#L985)
- WebSocket auth uses a per-process 72-bit token compared with `crypto.timingSafeEqual`, but the token travels as a URL query parameter (`?token=...`), which the comment acknowledges may land in server/middleware logs. — [packages/vite/src/node/server/ws.ts#L94-116](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/ws.ts#L94-L116) [packages/vite/src/node/config.ts#L2175-2181](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/config.ts#L2175-L2181) [packages/vite/src/client/client.ts#L46-56](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/client/client.ts#L46-L56)
- `config.legacy.skipWebSocketTokenCheck` is a live opt-out that disables token validation entirely inside `shouldHandle`, re-opening cross-site WebSocket hijacking for anyone who sets it. — [packages/vite/src/node/server/ws.ts#L180-183](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/ws.ts#L180-L183)
- Host validation is skipped whenever `server.https` is configured (`if (allowedHosts !== true && !serverConfig.https)`), on the reasoning that HTTPS is not DNS-rebinding-vulnerable — so a misissued or self-signed HTTPS dev setup loses that guard. — [packages/vite/src/node/server/index.ts#L993-998](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/index.ts#L993-L998)
- The WS upgrade path allows *any* non-browser client without a token: `shouldHandle` returns `true` when `req.headers.origin` is absent, with an inline comment justifying it for backward compat — meaning any local process that can open a socket can join the HMR channel. — [packages/vite/src/node/server/ws.ts#L185-200](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/ws.ts#L185-L200)
- Shell-out surface exists in the dev server itself: `server/index.ts` imports `execSync` from `node:child_process`, and `openBrowser.ts` spawns a browser process honouring the `BROWSER`/`BROWSER_ARGS` environment variables which `loadEnv` will populate from a project's `.env` file. — [packages/vite/src/node/server/index.ts#L1](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/index.ts#L1) [packages/vite/src/node/server/openBrowser.ts](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/openBrowser.ts) [packages/vite/src/node/env.ts#L68-74](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/env.ts#L68-L74)
- Config loading executes arbitrary project code by design through three paths (`bundleAndLoadConfigFile`, `runnerImportConfigFile`, `nativeImportConfigFile`), and `bundleConfigFile`/`loadConfigFromBundledFile` evaluate the bundled result — so opening an untrusted repo and running `vite` is code execution. — [packages/vite/src/node/config.ts#L2470-2480](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/config.ts#L2470-L2480) [packages/vite/src/node/config.ts#L2566-2800](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/config.ts#L2566-L2800)
- Blast-radius hotspots by size: `plugins/css.ts` (3685 lines), `config.ts` (2961), `utils.ts` (2022), `build.ts` (1986), `plugins/html.ts` (1753), `server/index.ts` (1496), `server/pluginContainer.ts` (1332) — none of which has a same-named unit spec except `config.spec.ts`, `build.spec.ts` and `html.spec.ts`. — [packages/vite/src/node/plugins/css.ts](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/plugins/css.ts) [packages/vite/src/node/config.ts](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/config.ts) [packages/vite/src/node/server/pluginContainer.ts](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/pluginContainer.ts) [packages/vite/src/node/__tests__/](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/__tests__/)
- The dev server now has two divergent implementations gated on one boolean: `config.experimental.bundledDev` swaps the entire serving strategy, skips `cachedTransformMiddleware`, skips `buildStart` at init, and short-circuits `handleHMRUpdate` with a `// TODO: support handleHotUpdate / hotUpdate` early return — so plugin HMR hooks silently do nothing in that mode. — [packages/vite/src/node/server/index.ts#L1012-1060](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/index.ts#L1012-L1060) [packages/vite/src/node/server/index.ts#L1097-1102](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/index.ts#L1097-L1102) [packages/vite/src/node/server/hmr.ts#L468-471](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/hmr.ts#L468-L471)
- A large deprecated-but-live compatibility layer runs on every access: `server.hot`, `server.pluginContainer`, `server.moduleGraph`, `server.transformRequest`, `server.warmupRequest`, `server.ssrLoadModule`, `server.ssrFixStacktrace`, `server.ssrRewriteStacktrace` and `server.reloadModule` all route through `warnFutureDeprecation` and a `ModuleGraph` bridging facade. — [packages/vite/src/node/server/index.ts#L620-756](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/index.ts#L620-L756) [packages/vite/src/node/deprecations.ts](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/deprecations.ts) [packages/vite/src/node/server/mixedModuleGraph.ts](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/mixedModuleGraph.ts)
- `vite optimize` is documented-dead in its own help string ('deprecated, the pre-bundle process runs automatically and does not need to be called') yet still resolves a full config and invokes `optimizeDeps(config, options.force, true)`. — [packages/vite/src/node/cli.ts#L385-420](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/cli.ts#L385-L420)
- The preview server reuses `hostValidationMiddleware(allowedHosts, true)` and `sirv(distDir, ...)` but has a much shorter middleware chain than the dev server and no `fs.allow` equivalent, since it serves a fixed `distDir` root. — [packages/vite/src/node/preview.ts#L131-270](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/preview.ts#L131-L270)
- `server.proxy`'s `rewriteWsOrigin` deliberately rewrites the browser-supplied `Origin` header on WebSocket upgrades, with an inline warning path when headers are already sent — a knob that defeats the target server's own origin checks if enabled carelessly. — [packages/vite/src/node/server/middlewares/proxy.ts#L36-60](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/middlewares/proxy.ts#L36-L60)

## Extension points

Vite's primary extension mechanism is the plugin object — a superset of Rolldown/Rollup's, adding `config`, `configEnvironment`, `configureServer`, `configurePreviewServer`, `transformIndexHtml`, `hotUpdate`, `buildApp`, and the environment-scoping fields `applyToEnvironment`/`sharedDuringBuild`. The ordered array returned by `resolvePlugins` is the canonical insertion-point table: user `pre`, `normal` and `post` plugins are spliced at three fixed positions between internal plugins. Beyond plugins, the extension surface includes the environment factory (`dev.createEnvironment`/`build.createEnvironment`), the hot-channel/transport abstraction, the CLI shortcut table, and the `create-vite` template directory convention.
- `resolvePlugins(config, prePlugins, normalPlugins, postPlugins)` is the ordering contract: user `pre` plugins land after the alias plugins, `normal` plugins after asset/CSS/worker handling but before `definePlugin`, and `post` plugins after the glob/dynamic-import plugins — with `clientInjectionsPlugin`, `cssAnalysisPlugin` and `importAnalysisPlugin` always last. — [packages/vite/src/node/plugins/index.ts#L42-150](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/plugins/index.ts#L42-L150)
- `getSortedPluginsByHook(hookName, plugins)` implements the per-hook `order: 'pre' | 'post'` sorting with three running indices, and `createPluginHookUtils` memoises the result per hook name into `getSortedPlugins`/`getSortedPluginHooks` on the resolved config. — [packages/vite/src/node/plugins/index.ts#L152-215](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/plugins/index.ts#L152-L215)
- `applyToEnvironment(environment)` lets a plugin opt in/out per environment or return a *replacement* plugin — as the built-in alias plugin does, swapping itself for Rolldown's native `nativeAliasPlugin` when `environment.config.isBundled` and no custom resolver is present. — [packages/vite/src/node/plugins/index.ts#L60-83](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/plugins/index.ts#L60-L83) [packages/vite/src/node/plugin.ts#L227-235](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/plugin.ts#L227-L235) [packages/vite/src/node/plugin.ts#L416-435](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/plugin.ts#L416-L435)
- `perEnvironmentPlugin(name, applyToEnvironment)` is the public helper for building environment-scoped plugins, exported from the package root alongside `perEnvironmentState`. — [packages/vite/src/node/plugin.ts#L447-456](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/plugin.ts#L447-L456) [packages/vite/src/node/index.ts#L35-36](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/index.ts#L35-L36)
- `buildApp` is the multi-environment build orchestration hook: `builder.buildApp()` walks `config.getSortedPlugins('buildApp')` and interleaves `configBuilder.buildApp(builder)` just before the first `order: 'post'` hook, letting frameworks control environment build order and interdependencies. — [packages/vite/src/node/build.ts#L1859-1893](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/build.ts#L1859-L1893) [packages/vite/src/node/plugin.ts#L346](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/plugin.ts#L346)
- `hotUpdate` is the environment-aware replacement for `handleHotUpdate`: `getSortedHotUpdatePlugins(environment)` selects the plugins and each hook receives a `HotUpdateOptions` with `{type, file, timestamp, read, server, modules}` and may return a filtered module list. — [packages/vite/src/node/server/hmr.ts#L372-410](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/hmr.ts#L372-L410) [packages/vite/src/node/server/hmr.ts#L475-525](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/hmr.ts#L475-L525) [packages/vite/src/node/plugin.ts#L117](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/plugin.ts#L117)
- Environments are created through configurable factories — `environmentOptions.dev.createEnvironment(name, config, { ws })` at server start and `config.build.createEnvironment(name, config)` in `createBuilder.setupEnvironment` — with `defaultCreateDevEnvironment`/`defaultCreateClientDevEnvironment` as the built-in defaults. — [packages/vite/src/node/server/index.ts#L600-617](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/index.ts#L600-L617) [packages/vite/src/node/build.ts#L1918-1922](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/build.ts#L1918-L1922) [packages/vite/src/node/config.ts#L261-280](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/config.ts#L261-L280)
- The HMR transport is pluggable on both ends: server-side via `normalizeHotChannel` and `createServerHotChannel()` (exported publicly), client/runtime-side via `normalizeModuleRunnerTransport` and `createWebSocketModuleRunnerTransport` in `src/shared/moduleRunnerTransport.ts`. — [packages/vite/src/node/server/hmr.ts#L212-260](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/hmr.ts#L212-L260) [packages/vite/src/node/server/hmr.ts#L1166](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/hmr.ts#L1166) [packages/vite/src/shared/moduleRunnerTransport.ts](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/shared/moduleRunnerTransport.ts) [packages/vite/src/node/index.ts#L73](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/index.ts#L73)
- `bindCLIShortcuts({ print, customShortcuts })` accepts an array of `CLIShortcut<Server>` objects (`{ key, description, action }`) merged with the built-in table — the mechanism the CLI itself uses to add the `p` profiler toggle. — [packages/vite/src/node/shortcuts.ts#L26-196](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/shortcuts.ts#L26-L196) [packages/vite/src/node/cli.ts#L272-303](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/cli.ts#L272-L303)
- Plugin hook filtering is an extension point with its own registry: `getCachedFilterForPlugin(plugin, hookName)` builds and WeakMap-caches `createIdFilter` / `createFilterForTransform` matchers from a hook's `filter: { id, code, moduleType }` object. — [packages/vite/src/node/plugins/index.ts#L217-270](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/plugins/index.ts#L217-L270) [packages/vite/src/node/plugins/pluginFilter.ts](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/plugins/pluginFilter.ts)
- `createIdResolver(config, options)` is exported as the supported way for plugins to reuse Vite's resolver, and `config.createResolver(options)` wraps it with per-environment `PartialEnvironment` instances for client and ssr. — [packages/vite/src/node/index.ts#L42](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/index.ts#L42) [packages/vite/src/node/config.ts#L2185-2202](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/config.ts#L2185-L2202) [packages/vite/src/node/idResolver.ts](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/idResolver.ts)
- `create-vite` extends purely by directory convention — each entry in the `FRAMEWORKS` variants table maps to a sibling `template-<name>` directory (`template-react-ts`, `template-svelte`, `template-qwik-ts`, …), so adding a template means adding a directory plus a table entry. — [packages/create-vite/src/index.ts#L80-200](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/create-vite/src/index.ts#L80-L200) [packages/create-vite/](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/create-vite/)
- The dev-server middleware chain is extensible only through `configureServer` (pre and post phases) and `configurePreviewServer`; there is no separate middleware registry, so ordering is controlled by which phase a plugin uses. — [packages/vite/src/node/server/index.ts#L1000-1082](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/index.ts#L1000-L1082) [packages/vite/src/node/plugin.ts#L101-200](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/plugin.ts#L101-L200)
- DevTools integration is a first-class optional extension: `loadDevToolsIntegrationPlugins(config, command, devtoolsOptions)` injects plugins during `resolveConfig`, and `resolveDevToolsConfig` / `isDevToolsOptionEnabled` gate it behind the optional `@vitejs/devtools` peer dependency. — [packages/vite/src/node/config.ts#L792-830](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/config.ts#L792-L830) [packages/vite/src/node/config.ts#L1571-1584](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/config.ts#L1571-L1584)

## Contributor handoff

This is a high-traffic OSS monorepo with strict conventions: pnpm only (enforced by `only-allow` in `preinstall`), oxfmt for formatting, eslint for `packages/*/{src,types}/**/*.ts`, and a `simple-git-hooks` pre-commit running lint-staged. Almost nothing can be verified without first running `pnpm build`, since the playground tests execute the built `dist/node/cli.js`. Read the CLI, server bootstrap, config resolver, and plugin pipeline before touching anything; treat the security guards and the deprecation shims as append-only unless the change is deliberate. The most common trap is editing `packages/vite/src/node` and testing without rebuilding, or adding a runtime dependency to the wrong section of `package.json`.
- Read first, in order: `packages/vite/src/node/cli.ts` (command surface), `packages/vite/src/node/server/index.ts` (`_createServer` + middleware order), `packages/vite/src/node/config.ts` (`resolveConfig`, `loadConfigFromFile`), `packages/vite/src/node/plugins/index.ts` (`resolvePlugins` ordering) and `packages/vite/src/node/build.ts` (`createBuilder`/`buildEnvironment`). — [packages/vite/src/node/cli.ts](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/cli.ts) [packages/vite/src/node/server/index.ts](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/index.ts) [packages/vite/src/node/config.ts](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/config.ts) [packages/vite/src/node/plugins/index.ts](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/plugins/index.ts) [packages/vite/src/node/build.ts](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/build.ts)
- Setup is `pnpm i` then `pnpm run build` at the repo root; while developing Vite itself, run `pnpm run dev` inside `packages/vite` for a watching Rolldown rebuild (`premove dist && pnpm build-bundle -w`). — [CONTRIBUTING.md#L7-18](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/CONTRIBUTING.md#L7-L18) [packages/vite/package.json#L48-49](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/package.json#L48-L49)
- pnpm is mandatory — root `preinstall` runs `npx only-allow pnpm` and `packageManager` pins `pnpm@12.4.2`; npm/yarn installs will be rejected. — [package.json#L8-9](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/package.json#L8-L9) [package.json#L96](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/package.json#L96)
- Verification commands: `pnpm test-unit` (fast, no browser), `pnpm test-serve [match]` / `pnpm test-build [match]` for targeted playground runs, `pnpm lint`, `pnpm format` (oxfmt), and `pnpm typecheck` (root `tsc -p scripts` plus every package's own typecheck). — [package.json#L18-26](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/package.json#L18-L26) [CONTRIBUTING.md#L137-166](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/CONTRIBUTING.md#L137-L166)
- Playground test matching is per-substring and only works on `test-serve`/`test-build`, not on `pnpm test` — e.g. `pnpm run test-serve assets` runs both `playground/assets` and `playground/assets-sanitize`. — [CONTRIBUTING.md#L157](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/CONTRIBUTING.md#L157)
- Trap: playground tests run against a real `vite` binary resolved through `viteBinPath` in `vitestSetup.ts`, so source edits in `packages/vite/src` are invisible to integration tests until `pnpm build` (or a running `pnpm dev` watcher) regenerates `dist`. — [playground/vitestSetup.ts#L77-80](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/playground/vitestSetup.ts#L77-L80) [CONTRIBUTING.md#L11-18](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/CONTRIBUTING.md#L11-L18)
- Trap: the e2e global setup copies playgrounds into `playground-temp/` and only copies the directories matched by the selected spec files — a new fixture directory that no spec references will not be copied, and `VITE_PRESERVE_BUILD_ARTIFACTS=1` (via `pnpm debug-build`) is needed to inspect the output. — [playground/vitestGlobalSetup.ts#L20-45](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/playground/vitestGlobalSetup.ts#L20-L45) [package.json#L28](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/package.json#L28)
- Dependency placement is a reviewed decision: `packages/vite/package.json` carries a literal `"//": "READ CONTRIBUTING.md to understand what to put under deps vs. devDeps!"` marker, and CONTRIBUTING has sections on `require()` usage, adding dependencies, and ensuring type support. — [packages/vite/package.json#L241](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/package.json#L241) [CONTRIBUTING.md#L44-72](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/CONTRIBUTING.md#L44-L72)
- Formatting and linting are enforced on commit: `simple-git-hooks` pre-commit runs `lint-staged --concurrent false`, which applies `oxfmt` to all files and eslint `--fix` to `packages/*/{src,types}/**/*.ts`, `packages/**/*.d.ts` and `playground/**/__tests__/**/*.ts`. — [package.json#L98-113](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/package.json#L98-L113)
- Safe edit boundary: anything under `playground/` is fixture + spec code, not shipped code — only `packages/vite`, `packages/create-vite` and `packages/plugin-legacy` are published, and CI skips tests entirely for `packages/create-vite/template**` changes. — [package.json#L37](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/package.json#L37) [.github/workflows/ci.yml#L48-57](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/.github/workflows/ci.yml#L48-L57) [pnpm-workspace.yaml](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/pnpm-workspace.yaml)
- Do not relax the filesystem guards casually: `isFileLoadingAllowed`, `looksLikeWindowsShortNamePath`, the NTFS-colon check and `rejectInvalidRequestMiddleware` each carry comments identifying a specific bypass class they exist to block. — [packages/vite/src/node/server/middlewares/static.ts#L289-345](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/middlewares/static.ts#L289-L345) [packages/vite/src/node/server/middlewares/rejectInvalidRequest.ts](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/middlewares/rejectInvalidRequest.ts)
- When adding a server-facing API, mirror the environment convention: put state on `DevEnvironment` (which owns `moduleGraph`, `pluginContainer`, `_pendingRequests`, `transformRequest`, `warmupRequest`, `waitForRequestsIdle`) and expose any flat `server.*` alias only through `warnFutureDeprecation`. — [packages/vite/src/node/server/environment.ts#L56-410](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/environment.ts#L56-L410) [packages/vite/src/node/server/index.ts#L620-756](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/index.ts#L620-L756) [packages/vite/src/node/deprecations.ts](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/deprecations.ts)
- Debug logging convention: use `createDebugger('vite:<area>')` (e.g. `vite:load`, `vite:transform`, `vite:cache`, `vite:env`, `vite:hmr`) and keep middleware functions *named*, since the names surface under `DEBUG=connect:dispatcher` — several files carry that exact comment. — [packages/vite/src/node/server/transformRequest.ts#L44-46](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/transformRequest.ts#L44-L46) [packages/vite/src/node/server/index.ts#L1033-1035](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/packages/vite/src/node/server/index.ts#L1033-L1035) [CONTRIBUTING.md#L115-118](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/CONTRIBUTING.md#L115-L118)
- Releases are automated and should not be hand-driven: `publish.yml` runs `scripts/detect-release.ts` against the latest commit subject on pushes to `main`/`v[0-9]*`, and `pnpm ci-publish` invokes `scripts/publishCI.ts`; `prepare-release.yml` and `scripts/prepare-release.ts` handle version bumps. — [.github/workflows/publish.yml#L1-45](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/.github/workflows/publish.yml#L1-L45) [scripts/publishCI.ts](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/scripts/publishCI.ts) [scripts/prepare-release.ts](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/scripts/prepare-release.ts) [package.json#L33](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/package.json#L33)
- The repo has an explicit AI Policy section in CONTRIBUTING.md alongside the Pull Request Guidelines and issue-triaging workflow — read it before opening a PR from an agent-assisted change. — [CONTRIBUTING.md#L224-263](https://github.com/vitejs/vite/blob/1544bb1b7ac92f775e9d0dea97954c99cc6cbd43/CONTRIBUTING.md#L224-L263)

Source repository: https://github.com/vitejs/vite
Full evidence corpus: https://github.com/Codevetter/codevetter/tree/main/benchmarks/repo-unpacks/vite.json

## Public product links

- [CodeVetter](https://codevetter.com/)
- [Download](https://codevetter.com/download)
- [Documentation](https://codevetter.com/docs/)
- [Source](https://github.com/Codevetter/codevetter)
