[READ-ONLY] Mirror of https://github.com/vitest-dev/vitest. Next generation testing framework powered by Vite. vitest.dev
test testing-tools vite
12

Configure Feed

Select the types of activity you want to include in your feed.

perf(browser): stop serving framework sourcemaps in headless runs (#10728)

authored by

Vladimir and committed by
GitHub
(Jul 14, 2026, 11:36 AM +0200) 6aefb3d9 62b8d3b3

+128 -5
+4
docs/.vitepress/config.ts
··· 655 655 link: '/config/browser/screenshotfailures', 656 656 }, 657 657 { 658 + text: 'browser.dependencySourcemaps', 659 + link: '/config/browser/dependencysourcemaps', 660 + }, 661 + { 658 662 text: 'browser.orchestratorScripts', 659 663 link: '/config/browser/orchestratorscripts', 660 664 },
+7
docs/guide/cli-generated.md
··· 381 381 382 382 If connection to the browser takes longer, the test suite will fail (default: `60_000`) 383 383 384 + ### browser.dependencySourcemaps 385 + 386 + - **CLI:** `--browser.dependencySourcemaps` 387 + - **Config:** [browser.dependencySourcemaps](/config/browser/dependencysourcemaps) 388 + 389 + Serve sourcemaps of dependencies to the browser in headless runs, used by devtools when debugging into `node_modules`. Reported test errors are source-mapped either way. Use `--browser.dependencySourcemaps=false` to speed up test runs if you don't step into dependency code (default: `true`) 390 + 384 391 ### browser.trackUnhandledErrors 385 392 386 393 - **CLI:** `--browser.trackUnhandledErrors`
+21
docs/config/browser/dependencysourcemaps.md
··· 1 + --- 2 + title: browser.dependencySourcemaps | Config 3 + outline: deep 4 + --- 5 + 6 + # browser.dependencySourcemaps 7 + 8 + - **Type:** `boolean` 9 + - **Default:** `true` 10 + 11 + Serve sourcemaps of your dependencies (files in `node_modules`) to the browser during headless test runs. 12 + 13 + These sourcemaps are used by browser devtools: with `dependencySourcemaps: false`, pausing inside dependency code shows the compiled code the browser actually runs instead of the dependency's original sources. If you don't debug into your dependencies this way, disabling them makes test runs faster: the server doesn't generate and inline the maps, and every browser tab downloads several times fewer bytes. 14 + 15 + Reported test errors are not affected: when an error is thrown inside a pre-bundled dependency, Vitest maps its stack frames using the sourcemaps stored on disk even when this option is disabled. Frames from dependencies that are served without pre-bundling (for example, [linked packages](https://vite.dev/guide/dep-pre-bundling#monorepos-and-linked-dependencies)) that don't ship their own sourcemaps fall back to the position in the served code, which usually matches the original file. 16 + 17 + Vitest never serves sourcemaps of its own pre-built modules in headless runs (unless [`--inspect`](/guide/cli#inspect) is used) — their frames are hidden from stack traces anyway. Sourcemaps of your own source files are always served. 18 + 19 + ::: tip 20 + If some of your workspace code resolves to a `node_modules` path (for example, with `resolve.preserveSymlinks`), set [`server.sourcemapIgnoreList`](https://vite.dev/config/server-options#server-sourcemapignorelist) to keep its sourcemaps even when this option is disabled. 21 + :::
+55 -2
packages/browser/src/node/index.ts
··· 5 5 import { MockerRegistry } from '@vitest/mocker' 6 6 import { interceptorPlugin } from '@vitest/mocker/node' 7 7 import { distClientRoot as uiClientRoot } from '@vitest/ui' 8 - import { toArray } from '@vitest/utils/helpers' 8 + import { cleanUrl, toArray } from '@vitest/utils/helpers' 9 9 import { join, resolve } from 'pathe' 10 10 import sirv from 'sirv' 11 11 import c from 'tinyrainbow' 12 - import { isFileServingAllowed, isValidApiRequest, rolldownVersion, distDir as vitestDist } from 'vitest/node' 12 + import { isCSSRequest, isFileServingAllowed, isValidApiRequest, rolldownVersion, distDir as vitestDist } from 'vitest/node' 13 13 import { version } from '../../package.json' 14 14 import { distRoot } from './constants' 15 15 import { createOrchestratorMiddleware } from './middlewares/orchestratorMiddleware' ··· 347 347 ...BrowserPlugin(contribution), 348 348 // this plugin's `configureServer` is ignored since it's added through `applyToEnvironment` 349 349 interceptorPlugin({ registry: mockerRegistry }), 350 + { 351 + name: 'vitest:browser:framework-sourcemaps', 352 + enforce: 'post', 353 + transform(code, id) { 354 + const parentServer = contribution.parent as ParentBrowserProject | undefined 355 + // In a headless run nothing can open devtools, so sourcemaps of 356 + // Vitest's own pre-built modules are never consumed: their stack 357 + // frames are filtered by stackIgnorePatterns. Generating and 358 + // inlining these maps costs server CPU and multiplies the bytes 359 + // the browser downloads by ~5 for every fresh browser context. 360 + // Sourcemaps of user files and (by default) their dependencies are 361 + // kept — they point error stacks and devtools at original sources. 362 + if ( 363 + !parentServer 364 + || !isHeadlessServer(parentServer) 365 + || parentServer.vitest.config.inspector.enabled 366 + ) { 367 + return null 368 + } 369 + if (isCSSRequest(id)) { 370 + return null 371 + } 372 + const path = cleanUrl(id) 373 + if (path.startsWith(distRoot) || path.startsWith(vitestDist)) { 374 + return { code, map: { mappings: '' } as any } 375 + } 376 + // users that never debug into node_modules can drop dependency 377 + // sourcemaps entirely; `server.sourcemapIgnoreList` (default: 378 + // node_modules) can opt paths back in even then, e.g. with 379 + // `preserveSymlinks` where workspace code keeps its node_modules 380 + // path and would be wrongly treated as a dependency 381 + if ( 382 + parentServer.config.browser.dependencySourcemaps === false 383 + && path.includes('/node_modules/') 384 + && (parentServer.vite.config.server.sourcemapIgnoreList(path, path) ?? true) 385 + ) { 386 + return { code, map: { mappings: '' } as any } 387 + } 388 + return null 389 + }, 390 + }, 350 391 ] 351 392 352 393 return contribution 394 + } 395 + 396 + function isHeadlessServer(parentServer: ParentBrowserProject): boolean { 397 + if (!parentServer.config.browser.headless) { 398 + return false 399 + } 400 + // sibling instances share this server (and its module graph cache, so a 401 + // late-spawned instance would receive already-cached transforms) and can 402 + // override `headless` — check the static instance options instead of the 403 + // lazily populated `children` to stay deterministic across runs 404 + const instances = parentServer.config.browser.instances ?? [] 405 + return instances.every(instance => instance.headless !== false) 353 406 } 354 407 355 408 function resolveBrowserOptimizeDeps(
+16 -3
packages/browser/src/node/projectParent.ts
··· 64 64 } 65 65 66 66 const result = this.vite.moduleGraph.getModuleById(id)?.transformResult 67 - // handle non-inline source map such as pre-bundled deps in node_modules/.vite 68 - if (result && !result.map) { 69 - const filePath = id.split('?')[0] 67 + const filePath = id.split('?')[0] 68 + // prefer the map stored on disk when the transform pipeline can't 69 + // provide a usable one: 70 + // - pre-bundled deps: the pipeline map resolves back into the 71 + // optimizer cache, while the map esbuild wrote next to the file 72 + // points at the real package sources 73 + // - an empty `mappings` means the map was intentionally not served 74 + // to the browser (`vitest:browser:framework-sourcemaps`), but disk 75 + // is still the source of truth for error stack traces 76 + if ( 77 + result && ( 78 + !result.map 79 + || result.map.mappings === '' 80 + || filePath.startsWith(this.vite.config.cacheDir) 81 + ) 82 + ) { 70 83 const extracted = extractSourcemapFromFile(result.code, filePath) 71 84 this.sourceMapCache.set(id, extracted?.map) 72 85 return extracted?.map
+3
packages/vitest/src/node/cli/cli-config.ts
··· 397 397 description: 'If connection to the browser takes longer, the test suite will fail (default: `60_000`)', 398 398 argument: '<timeout>', 399 399 }, 400 + dependencySourcemaps: { 401 + description: 'Serve sourcemaps of dependencies to the browser in headless runs, used by devtools when debugging into `node_modules`. Reported test errors are source-mapped either way. Use `--browser.dependencySourcemaps=false` to speed up test runs if you don\'t step into dependency code (default: `true`)', 402 + }, 400 403 trackUnhandledErrors: { 401 404 description: 'Control if Vitest catches uncaught exceptions so they can be reported (default: `true`)', 402 405 },
+22
packages/vitest/src/node/types/browser.ts
··· 271 271 screenshotFailures?: boolean 272 272 273 273 /** 274 + * Serve sourcemaps of your dependencies (files in `node_modules`) to the 275 + * browser during headless test runs. 276 + * 277 + * These sourcemaps are used by browser devtools: when disabled, pausing 278 + * inside dependency code shows the compiled code the browser actually 279 + * runs instead of the dependency's original sources. If you don't debug 280 + * into your dependencies this way, disabling them makes test runs faster: 281 + * the server doesn't generate and inline the maps, and every browser tab 282 + * downloads several times fewer bytes. 283 + * 284 + * Reported test errors are not affected: stack frames pointing into a 285 + * pre-bundled dependency are mapped using the sourcemaps stored on disk 286 + * even when this option is disabled. 287 + * 288 + * Vitest never serves sourcemaps of its own pre-built modules in headless 289 + * runs (unless `--inspect` is used) — their frames are hidden from stack 290 + * traces anyway. Sourcemaps of your own source files are always served. 291 + * @default true 292 + */ 293 + dependencySourcemaps?: boolean 294 + 295 + /** 274 296 * Path to the index.html file that will be used to run tests. 275 297 */ 276 298 testerHtmlPath?: string