perf(browser): prewarm the browser while the Vite server starts - #10727
Conversation
Browser providers gain an optional prewarm({ config, vitest }) hook,
invoked in createClusterServer right before the browser Vite server is
created. The playwright provider uses it to start import('playwright')
and the browser launch concurrently with server startup; the launch
options are resolved by the same code as the real launch and compared at
adoption time, so on any mismatch the speculative browser is discarded
and the normal path runs. Skipped for connectOptions, persistentContext
and inspector flows. Every browser resolved from browser.name and
browser.instances is warmed.
✅ Deploy Preview for vitest-dev ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
The prewarm hook now receives the browser.instances entries that survive the --project filter (shared with the expansion via filterProjectBrowserInstances) instead of deriving browser names from the unfiltered config. The playwright provider also resolves launch options with the instance-level headless override and closes never-adopted warm browsers when Vitest closes, so a project that never initializes its provider cannot leak a browser in watch mode.
| if (provider?.prewarm) { | ||
| // instances filtered out by `--project` never become projects, so no | ||
| // provider would ever adopt (or close) a browser prepared for them | ||
| const instances = filterProjectBrowserInstances(vitest.config.project, config) |
There was a problem hiding this comment.
Probably nit. Instead of extracting and reusing filterProjectBrowserInstances, would it also possible to stash already available filtered result somewhere and grab it back here? I think config resolution (and project filtering) is already done till reaching here.
There was a problem hiding this comment.
Agree, this should be simplified
| // `headless` is the only launch-relevant option an instance can override; | ||
| // this mirrors how the instance's project config is cloned from the parent | ||
| const browserConfig = instance.headless == null | ||
| ? project.config.browser | ||
| : { ...project.config.browser, headless: instance.headless } | ||
| const launchOptions = resolveLaunchOptions(browserConfig, project.vitest.config.inspector, options, browserName) |
There was a problem hiding this comment.
This looks similar point and I assume ground truth config resolution is available at the point of createClusterServer doing prewarm instead of duplicating instance inheritance behavior.
Slop alert 🤖 but if I nudge agent, it said something like this:
gpt says
A lower-repetition design would carry the already cloned instance configs from expansion into server creation.
1. Add Handoff Metadata
At packages/vitest/src/node/types/config.ts:1270:
export interface ResolvedProjectEntry {
viteConfig: ResolvedViteConfig
projectConfig: ResolvedConfig
hidden?: boolean
/** Resolved browser-instance configs owned by this hidden parent. */
browserProjects?: ResolvedConfig[]
}2. Capture Resolved Configs
At packages/vitest/src/node/projects/resolveProjects.ts:458, filtering already determines the selected instances.
Before adding the hidden parent at line 468:
const browserProjects: ResolvedConfig[] = []
result.push({
...entry,
hidden: true,
browserProjects,
})Inside the instance loop, the authoritative config is created at lines 553-554:
const clonedConfig
= cloneProjectConfigForBrowserInstance(projectConfig, instance)
clonedConfig.name = name
browserProjects.push(clonedConfig)
result.push({
viteConfig,
projectConfig: clonedConfig,
})The same resolved config object is therefore used both for project creation and prewarming.
3. Pass Them Into Server Creation
At resolveProjects.ts:930:
const {
viteConfig,
projectConfig,
hidden,
browserProjects,
} = entryAt line 958:
const { server, parent } = await createClusterServer(
vitest,
viteConfig,
projectConfig,
browserProjects,
)4. Prewarm Effective Providers
At packages/vitest/src/node/plugins/browserLoader.ts:111:
export async function createClusterServer(
vitest: Vitest,
viteConfig: ResolvedViteConfig,
config: ResolvedConfig,
browserProjects: ResolvedConfig[] = [],
) {Each resolved project contains its effective provider at project.browser.provider. Grouping by provider also fixes the per-instance-provider issue:
const projectsByProvider = Map.groupBy(
browserProjects,
project => project.browser.provider,
)
for (const [provider, projects] of projectsByProvider) {
provider?.prewarm?.({ vitest, projects })
}5. Consume Resolved Browser Config
Change the prewarm context at packages/vitest/src/node/types/browser.ts:30:
prewarm?: (ctx: {
vitest: Vitest
projects: ResolvedConfig[]
}) => voidThen Playwright at packages/browser-playwright/src/playwright.ts:100 becomes:
prewarm(ctx) {
for (const project of ctx.projects) {
prewarmBrowser(ctx.vitest, options, project.browser)
}
}project.browser already contains:
- Effective browser name from
resolveProjects.ts:707 - Effective
headlessfrom line 705 - Effective provider from line 706
- Inherited trace, UI, and parent browser settings
This removes the second filtering call and the manual reconstruction of browserConfig inside Playwright.
Starts preparing the browser while the Vite server is still being created, so the launch latency overlaps server startup.
Browser providers gain an optional
prewarm({ config, vitest })hook, invoked increateClusterServerright before the browser Vite server is created (theTestProjectdoesn't exist yet at that point, so the hook receives the resolved config). The playwright provider uses it to startimport('playwright')and the browser launch concurrently with server startup.The speculative launch is always safe: the launch options are resolved by the same code as the real launch and compared at adoption time — on any mismatch the prewarmed browser is discarded and the normal launch path runs. A prewarmed browser that is never adopted is closed with the provider. Prewarming is skipped for
connectOptions,persistentContext, and inspector flows. Every browser name resolved frombrowser.nameandbrowser.instancesis warmed.