Reworking resolveConfig so the full config is resolved before any Vite server exists — and introducing a PluginHarness to give plugins access to the Vitest instance after resolution.
createVitest(options, viteOverrides, vitestOptions)Logger & packageInstaller internally. Config not resolved yet.vitest._setServer(options, server) coupledassert() guardedconfig / vite / state / snapshot / cache are getters that throw "not set yet" until the server step runs.Vitest instance once config is ready (#8572).
createVitest(options, viteOverrides, vitestOptions)logger, packageInstaller, version and a late-bound vitest ref. Threaded into every config-time plugin so they can call getVitest() after resolution.resolveConfig through pre-plugins, then resolves test config + all projects, returning a ResolvedViteConfig.CliOverride · VitestConfigresolveTestConfigBrowserLoaderPluginresolveProjectEntriesconfig, viteConfig, logger set immediately as real fields (no more lazy getters / assert)._setRootConfig → _attachRootServer → _attachProjectServers. On failure, close() cleans everything up.Vitest instance post-resolution.
Sharing one browser server across a project's instances (chromium/firefox/webkit) was already true before — that's not what changed. The new layer is at the project level: a browser project used to run two Vite servers — its own node vite server plus a separate browser server (_parentBrowser.vite, created lazily by _initBrowserServer). This PR collapses them into one server per project.
"app" (browser enabled)ViteDevServers coexist (the browser one spun up lazily by _initBrowserServer): teardown & cache logic had to walk both vite.environments and browser?.vite.environments.
"app" (browser enabled)createClusterServer_parentBrowser.spawn() (unchanged)
ResolvedViteConfig identity (Map<viteConfig, TestProject>): the primary owns the one server; root/benchmark siblings that resolve to the same config reuse it instead of creating their own.
applyBrowserOptimizeDeps also aggregates optimizeDeps for that single server before it's created.
| Aspect | Before | After |
|---|---|---|
| Ordering | Server created first, then config resolved against it | Config fully resolved first, server created from it |
Vitest constructor |
(cliOptions, options) + deprecated (mode, …) overloads |
(harness, viteConfig) — takes resolved config |
| State fields | Lazy getters guarded by assert() ("not set yet") |
Plain fields set during construction / _start |
| Plugin → Vitest access | No clean handle after config resolves | PluginHarness with late-bound getVitest() |
| Config resolution | Inline, coupled to _setServer |
Standalone resolveConfig() → ResolvedViteConfig |
| Browser project servers | Two servers: node project.vite + a separate _parentBrowser.vite |
One server reused for both (project.vite === project.browser.vite) |
| Server reuse keying | Ad-hoc: root by config-file path; instances/bench copy parent._vite |
Unified dedupe by resolved viteConfig identity (primary owns / siblings reuse) |
| Public API | — | Exports resolveConfig, resolveApiServerConfig, PluginHarness |
| Fixes | #6912 (resolve config without a server) · #8572 (plugin access to vitest) | |