vitest 4.0.13 → 4.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/dist/browser.d.ts +1 -1
  2. package/dist/browser.js +1 -1
  3. package/dist/chunks/{base.Dqf2QAxh.js → base.CTp-EStD.js} +6 -6
  4. package/dist/chunks/browser.d.DBzUq_Na.d.ts +57 -0
  5. package/dist/chunks/{cac.L-UbQ_Ix.js → cac.BNNpZQl7.js} +10 -25
  6. package/dist/chunks/{cli-api.CdZ6wo9-.js → cli-api.C7sYjHmQ.js} +442 -110
  7. package/dist/chunks/{config.d.g6OOauRt.d.ts → config.d.CzIjkicf.d.ts} +1 -0
  8. package/dist/chunks/evaluatedModules.d.BxJ5omdx.d.ts +7 -0
  9. package/dist/chunks/{globals.C0izxiX3.js → globals.DOayXfHP.js} +3 -3
  10. package/dist/chunks/{index.DWDW6mLz.js → index.456_DGfR.js} +137 -22
  11. package/dist/chunks/{index.QWbK7rHY.js → index.BspFP3mn.js} +12 -7
  12. package/dist/chunks/{index.CMvpbrsJ.js → index.Drsj_6e7.js} +1 -1
  13. package/dist/chunks/{index.DBx1AtPJ.js → index.Z5E_ObnR.js} +1 -1
  14. package/dist/chunks/{index.CQwQ_SLL.js → index.bFLgAE-Z.js} +2 -2
  15. package/dist/chunks/{init-forks.CglOH45c.js → init-forks.CKEYp90N.js} +11 -2
  16. package/dist/chunks/{init-threads.BuMdIy1r.js → init-threads.D8Ok07M7.js} +1 -1
  17. package/dist/chunks/{init.MkYs5nmh.js → init.B04saIIg.js} +4 -4
  18. package/dist/chunks/modules.DJPjQW6m.js +35 -0
  19. package/dist/chunks/{plugin.d.B4l3vYM_.d.ts → plugin.d.CY7CUjf-.d.ts} +1 -1
  20. package/dist/chunks/{reporters.d.J2RlBlp9.d.ts → reporters.d.OXEK7y4s.d.ts} +29 -6
  21. package/dist/chunks/{setup-common.DGHc_BUK.js → setup-common.Cm-kSBVi.js} +1 -1
  22. package/dist/chunks/{startModuleRunner.W28wBIgJ.js → startModuleRunner.Iz2V0ESw.js} +8 -9
  23. package/dist/chunks/{test.DqQZzsWf.js → test.BT8LKgU9.js} +8 -3
  24. package/dist/chunks/{vi.BiaV1qII.js → vi.2VT5v0um.js} +40 -30
  25. package/dist/chunks/{vm.Y19jrZy2.js → vm.BwmD1Rql.js} +2 -2
  26. package/dist/chunks/{worker.d.DCy61tzi.d.ts → worker.d.B4A26qg6.d.ts} +2 -2
  27. package/dist/cli.js +2 -2
  28. package/dist/config.d.ts +7 -7
  29. package/dist/coverage.d.ts +8 -8
  30. package/dist/environments.js +1 -1
  31. package/dist/index.d.ts +22 -14
  32. package/dist/index.js +3 -3
  33. package/dist/module-evaluator.d.ts +5 -0
  34. package/dist/module-evaluator.js +23 -5
  35. package/dist/module-runner.js +2 -1
  36. package/dist/node.d.ts +8 -8
  37. package/dist/node.js +7 -6
  38. package/dist/reporters.d.ts +7 -7
  39. package/dist/reporters.js +2 -2
  40. package/dist/runners.d.ts +2 -1
  41. package/dist/runners.js +2 -2
  42. package/dist/worker.d.ts +2 -2
  43. package/dist/worker.js +10 -9
  44. package/dist/workers/forks.js +11 -10
  45. package/dist/workers/runVmTests.js +5 -5
  46. package/dist/workers/threads.js +11 -10
  47. package/dist/workers/vmForks.js +6 -5
  48. package/dist/workers/vmThreads.js +6 -5
  49. package/package.json +15 -20
  50. package/dist/chunks/browser.d.CDvMh6F9.d.ts +0 -18
@@ -6,16 +6,16 @@ import { noop, createDefer, slash, isExternalUrl, unwrapId, withTrailingSlash, c
6
6
  import { a as any, p as prompt } from './index.D4KonVSU.js';
7
7
  import { h as hash, R as RandomSequencer, i as isPackageExists, c as isBrowserEnabled, r as resolveConfig, g as getCoverageProvider, a as resolveApiServerConfig, d as resolveModule } from './coverage.CtyeYmKM.js';
8
8
  import * as vite from 'vite';
9
- import { parseAst, searchForWorkspaceRoot, fetchModule, version, mergeConfig, createServer } from 'vite';
9
+ import { isFileServingAllowed, parseAst, searchForWorkspaceRoot, fetchModule, version, mergeConfig, createServer } from 'vite';
10
10
  import { A as API_PATH, c as configFiles, d as defaultBrowserPort, a as defaultPort } from './constants.D_Q9UYh-.js';
11
11
  import * as nodeos from 'node:os';
12
12
  import nodeos__default, { tmpdir } from 'node:os';
13
- import { generateHash as generateHash$1, calculateSuiteHash, someTasksAreOnly, interpretTaskModes, hasFailed, generateFileHash, limitConcurrency, createFileTask as createFileTask$1, getTasks, isTestCase } from '@vitest/runner/utils';
13
+ import { generateHash as generateHash$1, createTaskName, calculateSuiteHash, someTasksAreOnly, interpretTaskModes, hasFailed, generateFileHash, limitConcurrency, createFileTask as createFileTask$1, getTasks, isTestCase } from '@vitest/runner/utils';
14
14
  import { SnapshotManager } from '@vitest/snapshot/manager';
15
- import { v as version$1 } from './cac.L-UbQ_Ix.js';
15
+ import { v as version$1 } from './cac.BNNpZQl7.js';
16
16
  import { performance as performance$1 } from 'node:perf_hooks';
17
17
  import { c as createBirpc } from './index.0kCJoeWi.js';
18
- import { p as parse, d as stringify, e as TraceMap, o as originalPositionFor, h as ancestor, i as printError, f as formatProjectName, w as withLabel, j as errorBanner, k as divider, l as Typechecker, m as generateCodeFrame, n as createDefinesScript, R as ReportersMap, B as BlobReporter, r as readBlobs, q as convertTasksToEvents, H as HangingProcessReporter } from './index.DWDW6mLz.js';
18
+ import { p as parse, d as stringify, e as createIndexLocationsMap, h as TraceMap, o as originalPositionFor, i as ancestor, j as printError, f as formatProjectName, w as withLabel, k as errorBanner, l as divider, m as Typechecker, n as generateCodeFrame, q as escapeRegExp, r as createDefinesScript, R as ReportersMap, u as groupBy, B as BlobReporter, v as readBlobs, x as convertTasksToEvents, H as HangingProcessReporter, y as wildcardPatternToRegExp, z as stdout } from './index.456_DGfR.js';
19
19
  import require$$0$3 from 'events';
20
20
  import require$$1$1 from 'https';
21
21
  import require$$2 from 'http';
@@ -30,18 +30,19 @@ import { g as getDefaultExportFromCjs } from './_commonjsHelpers.D26ty3Ew.js';
30
30
  import crypto, { createHash } from 'node:crypto';
31
31
  import { rootDir, distDir } from '../path.js';
32
32
  import { T as Traces } from './traces.U4xDYhzZ.js';
33
- import createDebug from 'debug';
33
+ import { createDebug } from 'obug';
34
34
  import { rm, readFile, writeFile, rename, stat, unlink, mkdir, copyFile } from 'node:fs/promises';
35
35
  import c from 'tinyrainbow';
36
36
  import { VitestModuleEvaluator } from '#module-evaluator';
37
37
  import { ModuleRunner } from 'vite/module-runner';
38
38
  import { Console } from 'node:console';
39
39
  import { highlight } from '@vitest/utils/highlight';
40
- import { createRequire, isBuiltin, builtinModules } from 'node:module';
40
+ import { createRequire, builtinModules, isBuiltin as isBuiltin$1 } from 'node:module';
41
41
  import url, { fileURLToPath, pathToFileURL } from 'node:url';
42
42
  import { i as isTTY, a as isWindows } from './env.D4Lgay0q.js';
43
43
  import { isatty } from 'node:tty';
44
44
  import EventEmitter$1, { EventEmitter } from 'node:events';
45
+ import { t as toBuiltin, i as isBuiltin } from './modules.DJPjQW6m.js';
45
46
  import { fork } from 'node:child_process';
46
47
  import { Worker } from 'node:worker_threads';
47
48
  import pm from 'picomatch';
@@ -52,7 +53,7 @@ import { c as configDefaults } from './defaults.BOqNVLsY.js';
52
53
  import { KNOWN_ASSET_RE } from '@vitest/utils/constants';
53
54
  import { findNearestPackageData } from '@vitest/utils/resolver';
54
55
  import * as esModuleLexer from 'es-module-lexer';
55
- import { a as BenchmarkReportsMap } from './index.CMvpbrsJ.js';
56
+ import { a as BenchmarkReportsMap } from './index.Drsj_6e7.js';
56
57
  import assert$1 from 'node:assert';
57
58
  import { serializeValue } from '@vitest/utils/serialize';
58
59
  import { parseErrorStacktrace } from '@vitest/utils/source-map';
@@ -5025,30 +5026,55 @@ function requireWebsocketServer () {
5025
5026
  var websocketServerExports = requireWebsocketServer();
5026
5027
  var WebSocketServer = /*@__PURE__*/getDefaultExportFromCjs(websocketServerExports);
5027
5028
 
5028
- async function getModuleGraph(ctx, projectName, id, browser = false) {
5029
+ function getTestFileEnvironment(project, testFile, browser = false) {
5030
+ let environment;
5031
+ if (browser) environment = project.browser?.vite.environments.client;
5032
+ else for (const name in project.vite.environments) {
5033
+ const env = project.vite.environments[name];
5034
+ if (env.moduleGraph.getModuleById(testFile)) {
5035
+ environment = env;
5036
+ break;
5037
+ }
5038
+ }
5039
+ return environment;
5040
+ }
5041
+
5042
+ async function getModuleGraph(ctx, projectName, testFilePath, browser = false) {
5029
5043
  const graph = {};
5030
5044
  const externalized = /* @__PURE__ */ new Set();
5031
5045
  const inlined = /* @__PURE__ */ new Set();
5032
5046
  const project = ctx.getProjectByName(projectName);
5033
- async function get(mod, seen = /* @__PURE__ */ new Map()) {
5047
+ const environment = getTestFileEnvironment(project, testFilePath, browser);
5048
+ if (!environment) throw new Error(`Cannot find environment for ${testFilePath}`);
5049
+ const seen = /* @__PURE__ */ new Map();
5050
+ function get(mod) {
5034
5051
  if (!mod || !mod.id) return;
5035
- if (mod.id === "\0vitest/browser") return;
5052
+ if (mod.id === "\0vitest/browser" || mod.id.includes("plugin-vue:export-helper")) return;
5036
5053
  if (seen.has(mod)) return seen.get(mod);
5037
- let id = clearId(mod.id);
5054
+ const id = clearId(mod.id);
5038
5055
  seen.set(mod, id);
5039
- // TODO: how to know if it was rewritten(?) - what is rewritten?
5040
- const rewrote = browser ? mod.file?.includes(project.browser.vite.config.cacheDir) ? mod.id : false : false;
5041
- if (rewrote) {
5042
- id = rewrote;
5043
- externalized.add(id);
5044
- seen.set(mod, id);
5045
- } else inlined.add(id);
5046
- const mods = Array.from(mod.importedModules).filter((i) => i.id && !i.id.includes("/vitest/dist/"));
5047
- graph[id] = (await Promise.all(mods.map((m) => get(m, seen)))).filter(Boolean);
5056
+ if (id.startsWith("__vite-browser-external:")) {
5057
+ const external = id.slice(24);
5058
+ externalized.add(external);
5059
+ return external;
5060
+ }
5061
+ const external = project._resolver.wasExternalized(id);
5062
+ if (typeof external === "string") {
5063
+ externalized.add(external);
5064
+ return external;
5065
+ }
5066
+ if (browser && mod.file?.includes(project.browser.vite.config.cacheDir)) {
5067
+ externalized.add(mod.id);
5068
+ return id;
5069
+ }
5070
+ inlined.add(id);
5071
+ graph[id] = Array.from(mod.importedModules).filter((i) => i.id && !i.id.includes("/vitest/dist/")).map((m) => get(m)).filter(Boolean);
5048
5072
  return id;
5049
5073
  }
5050
- if (browser && project.browser) await get(project.browser.vite.moduleGraph.getModuleById(id));
5051
- else await get(project.vite.moduleGraph.getModuleById(id));
5074
+ get(environment.moduleGraph.getModuleById(testFilePath));
5075
+ project.config.setupFiles.forEach((setupFile) => {
5076
+ get(environment.moduleGraph.getModuleById(setupFile));
5077
+ });
5052
5078
  return {
5053
5079
  graph,
5054
5080
  externalized: Array.from(externalized),
@@ -5146,15 +5172,36 @@ function setup(ctx, _server) {
5146
5172
  color: p.color
5147
5173
  }));
5148
5174
  },
5149
- async getTransformResult(projectName, id, browser = false) {
5175
+ async getExternalResult(moduleId, testFileTaskId) {
5176
+ const testModule = ctx.state.getReportedEntityById(testFileTaskId);
5177
+ if (!testModule) return;
5178
+ if (!isFileServingAllowed(testModule.project.vite.config, moduleId)) return;
5179
+ const result = {};
5180
+ try {
5181
+ result.source = await promises.readFile(moduleId, "utf-8");
5182
+ } catch {}
5183
+ return result;
5184
+ },
5185
+ async getTransformResult(projectName, moduleId, testFileTaskId, browser = false) {
5150
5186
  const project = ctx.getProjectByName(projectName);
5151
- const result = browser ? await project.browser.vite.transformRequest(id) : await project.vite.transformRequest(id);
5152
- if (result) {
5153
- try {
5154
- result.source = result.source || await promises.readFile(id, "utf-8");
5155
- } catch {}
5156
- return result;
5157
- }
5187
+ const testModule = ctx.state.getReportedEntityById(testFileTaskId);
5188
+ if (!testModule || !isFileServingAllowed(project.vite.config, moduleId)) return;
5189
+ const environment = getTestFileEnvironment(project, testModule.moduleId, browser);
5190
+ const moduleNode = environment?.moduleGraph.getModuleById(moduleId);
5191
+ if (!environment || !moduleNode?.transformResult) return;
5192
+ const result = moduleNode.transformResult;
5193
+ try {
5194
+ result.source = result.source || (moduleNode.file ? await promises.readFile(moduleNode.file, "utf-8") : void 0);
5195
+ } catch {}
5196
+ // TODO: store this in HTML reporter separetly
5197
+ const transformDuration = ctx.state.metadata[projectName]?.duration[moduleNode.url]?.[0];
5198
+ if (transformDuration != null) result.transformTime = transformDuration;
5199
+ try {
5200
+ const diagnostic = await ctx.experimental_getSourceModuleDiagnostic(moduleId, testModule);
5201
+ result.modules = diagnostic.modules;
5202
+ result.untrackedModules = diagnostic.untrackedModules;
5203
+ } catch {}
5204
+ return result;
5158
5205
  },
5159
5206
  async getModuleGraph(project, id, browser) {
5160
5207
  return getModuleGraph(ctx, project, id, browser);
@@ -5273,31 +5320,6 @@ var setup$1 = /*#__PURE__*/Object.freeze({
5273
5320
  setup: setup
5274
5321
  });
5275
5322
 
5276
- function groupBy(collection, iteratee) {
5277
- return collection.reduce((acc, item) => {
5278
- const key = iteratee(item);
5279
- acc[key] ||= [];
5280
- acc[key].push(item);
5281
- return acc;
5282
- }, {});
5283
- }
5284
- function stdout() {
5285
- // @ts-expect-error Node.js maps process.stdout to console._stdout
5286
- // eslint-disable-next-line no-console
5287
- return console._stdout || process.stdout;
5288
- }
5289
- function escapeRegExp(s) {
5290
- // From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping
5291
- return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
5292
- }
5293
- function wildcardPatternToRegExp(pattern) {
5294
- const negated = pattern[0] === "!";
5295
- if (negated) pattern = pattern.slice(1);
5296
- let regexp = `${pattern.split("*").map(escapeRegExp).join(".*")}$`;
5297
- if (negated) regexp = `(?!${regexp})`;
5298
- return new RegExp(`^${regexp}`, "i");
5299
- }
5300
-
5301
5323
  function createDebugger(namespace) {
5302
5324
  const debug = createDebug(namespace);
5303
5325
  if (debug.enabled) return debug;
@@ -5401,6 +5423,7 @@ function createFailedFileTask(project, filepath, error) {
5401
5423
  type: "suite",
5402
5424
  id: /* @__PURE__ */ generateHash$1(`${testFilepath}${project.config.name || ""}`),
5403
5425
  name: testFilepath,
5426
+ fullName: testFilepath,
5404
5427
  mode: "run",
5405
5428
  tasks: [],
5406
5429
  start: 0,
@@ -5438,6 +5461,7 @@ function createFileTask(testFilepath, code, requestMap, options) {
5438
5461
  type: "suite",
5439
5462
  id: /* @__PURE__ */ generateHash$1(`${testFilepath}${options.name || ""}`),
5440
5463
  name: testFilepath,
5464
+ fullName: testFilepath,
5441
5465
  mode: "run",
5442
5466
  tasks: [],
5443
5467
  start: ast.start,
@@ -5448,7 +5472,7 @@ function createFileTask(testFilepath, code, requestMap, options) {
5448
5472
  file: null
5449
5473
  };
5450
5474
  file.file = file;
5451
- const indexMap = createIndexMap(code);
5475
+ const indexMap = createIndexLocationsMap(code);
5452
5476
  const map = requestMap && new TraceMap(requestMap);
5453
5477
  let lastSuite = file;
5454
5478
  const updateLatestSuite = (index) => {
@@ -5482,6 +5506,8 @@ function createFileTask(testFilepath, code, requestMap, options) {
5482
5506
  tasks: [],
5483
5507
  mode,
5484
5508
  name: definition.name,
5509
+ fullName: createTaskName([latestSuite.fullName, definition.name]),
5510
+ fullTestName: createTaskName([latestSuite.fullTestName, definition.name]),
5485
5511
  end: definition.end,
5486
5512
  start: definition.start,
5487
5513
  location,
@@ -5501,6 +5527,8 @@ function createFileTask(testFilepath, code, requestMap, options) {
5501
5527
  mode,
5502
5528
  context: {},
5503
5529
  name: definition.name,
5530
+ fullName: createTaskName([latestSuite.fullName, definition.name]),
5531
+ fullTestName: createTaskName([latestSuite.fullTestName, definition.name]),
5504
5532
  end: definition.end,
5505
5533
  start: definition.start,
5506
5534
  location,
@@ -5546,23 +5574,6 @@ async function transformSSR(project, filepath) {
5546
5574
  if (!request) return null;
5547
5575
  return await project.vite.ssrTransform(request.code, request.map, filepath);
5548
5576
  }
5549
- function createIndexMap(source) {
5550
- const map = /* @__PURE__ */ new Map();
5551
- let index = 0;
5552
- let line = 1;
5553
- let column = 1;
5554
- for (const char of source) {
5555
- map.set(index++, {
5556
- line,
5557
- column
5558
- });
5559
- if (char === "\n" || char === "\r\n") {
5560
- line++;
5561
- column = 0;
5562
- } else column++;
5563
- }
5564
- return map;
5565
- }
5566
5577
  function markDynamicTests(tasks) {
5567
5578
  for (const task of tasks) {
5568
5579
  if (task.dynamic) task.id += "-dynamic";
@@ -5760,7 +5771,7 @@ class FileSystemModuleCache {
5760
5771
  */
5761
5772
  rootCache;
5762
5773
  metadataFilePath;
5763
- version = "1.0.0-beta.1";
5774
+ version = "1.0.0-beta.3";
5764
5775
  fsCacheRoots = /* @__PURE__ */ new WeakMap();
5765
5776
  fsEnvironmentHashMap = /* @__PURE__ */ new WeakMap();
5766
5777
  fsCacheKeyGenerators = /* @__PURE__ */ new Set();
@@ -5805,7 +5816,7 @@ class FileSystemModuleCache {
5805
5816
  }
5806
5817
  async getCachedModule(cachedFilePath) {
5807
5818
  if (!existsSync(cachedFilePath)) {
5808
- debugFs?.(`${c.red("[empty]")} ${cachedFilePath} doesn't exist, transforming by vite instead`);
5819
+ debugFs?.(`${c.red("[empty]")} ${cachedFilePath} doesn't exist, transforming by vite first`);
5809
5820
  return;
5810
5821
  }
5811
5822
  const fileResult = await this.readCachedFileConcurrently(cachedFilePath);
@@ -5818,16 +5829,18 @@ class FileSystemModuleCache {
5818
5829
  file: meta.file,
5819
5830
  code,
5820
5831
  importers: meta.importers,
5832
+ importedUrls: meta.importedUrls,
5821
5833
  mappings: meta.mappings
5822
5834
  };
5823
5835
  }
5824
- async saveCachedModule(cachedFilePath, fetchResult, importers = [], mappings = false) {
5836
+ async saveCachedModule(cachedFilePath, fetchResult, importers = [], importedUrls = [], mappings = false) {
5825
5837
  if ("code" in fetchResult) {
5826
5838
  const result = {
5827
5839
  file: fetchResult.file,
5828
5840
  id: fetchResult.id,
5829
5841
  url: fetchResult.url,
5830
5842
  importers,
5843
+ importedUrls,
5831
5844
  mappings
5832
5845
  };
5833
5846
  debugFs?.(`${c.yellow("[write]")} ${fetchResult.id} is cached in ${cachedFilePath}`);
@@ -5855,12 +5868,12 @@ class FileSystemModuleCache {
5855
5868
  else if (result === null) debugMemory?.(`${c.green("[read]")} ${id} was bailed out`);
5856
5869
  return result;
5857
5870
  }
5858
- generateCachePath(vitestConfig, environment, resolver, id, fileContent) {
5871
+ generateCachePath(vitestConfig, environment, id, fileContent) {
5859
5872
  // bail out if file has import.meta.glob because it depends on other files
5860
5873
  // TODO: figure out a way to still support it
5861
5874
  if (fileContent.includes("import.meta.glob(")) {
5862
5875
  this.saveMemoryCache(environment, id, null);
5863
- debugMemory?.(`${c.yellow("[write]")} ${id} was bailed out`);
5876
+ debugMemory?.(`${c.yellow("[write]")} ${id} was bailed out because it has "import.meta.glob"`);
5864
5877
  return null;
5865
5878
  }
5866
5879
  let hashString = "";
@@ -5889,7 +5902,7 @@ class FileSystemModuleCache {
5889
5902
  mode: config.mode,
5890
5903
  consumer: config.consumer,
5891
5904
  resolve: config.resolve,
5892
- plugins: config.plugins.map((p) => p.name),
5905
+ plugins: config.plugins.filter((p) => p.api?.vitest?.experimental?.ignoreFsModuleCache !== true).map((p) => p.name),
5893
5906
  configFileDependencies: config.configFileDependencies.map((file) => tryReadFileSync(file)),
5894
5907
  environment: environment.name,
5895
5908
  css: vitestConfig.css
@@ -5904,6 +5917,7 @@ class FileSystemModuleCache {
5904
5917
  let cacheRoot = this.fsCacheRoots.get(vitestConfig);
5905
5918
  if (cacheRoot == null) {
5906
5919
  cacheRoot = vitestConfig.experimental.fsModuleCachePath || this.rootCache;
5920
+ this.fsCacheRoots.set(vitestConfig, cacheRoot);
5907
5921
  if (!existsSync(cacheRoot)) mkdirSync(cacheRoot, { recursive: true });
5908
5922
  }
5909
5923
  const fsResultPath = join(cacheRoot, cacheKey);
@@ -6073,11 +6087,10 @@ const readFilePromises = /* @__PURE__ */ new Map();
6073
6087
  class ModuleFetcher {
6074
6088
  tmpDirectories = /* @__PURE__ */ new Set();
6075
6089
  fsCacheEnabled;
6076
- constructor(resolver, config, fsCache, traces, tmpProjectDir) {
6090
+ constructor(resolver, config, fsCache, tmpProjectDir) {
6077
6091
  this.resolver = resolver;
6078
6092
  this.config = config;
6079
6093
  this.fsCache = fsCache;
6080
- this.traces = traces;
6081
6094
  this.tmpProjectDir = tmpProjectDir;
6082
6095
  this.fsCacheEnabled = config.experimental?.fsModuleCache === true;
6083
6096
  }
@@ -6108,6 +6121,11 @@ class ModuleFetcher {
6108
6121
  const cached = !!moduleGraphModule.transformResult;
6109
6122
  if (moduleGraphModule.file) trace.setAttribute("code.file.path", moduleGraphModule.file);
6110
6123
  if (options?.cached && cached) return { cache: true };
6124
+ const externalize = await this.resolver.shouldExternalize(moduleGraphModule.id);
6125
+ if (externalize) return {
6126
+ externalize,
6127
+ type: "module"
6128
+ };
6111
6129
  const cachePath = await this.getCachePath(environment, moduleGraphModule);
6112
6130
  // full fs caching is disabled, but we still want to keep tmp files if makeTmpCopies is enabled
6113
6131
  // this is primarily used by the forks pool to avoid using process.send(bigBuffer)
@@ -6140,10 +6158,20 @@ class ModuleFetcher {
6140
6158
  }
6141
6159
  const result = await this.fetchAndProcess(environment, url, importer, moduleGraphModule, options);
6142
6160
  const importers = this.getSerializedDependencies(moduleGraphModule);
6161
+ const importedUrls = this.getSerializedImports(moduleGraphModule);
6143
6162
  const map = moduleGraphModule.transformResult?.map;
6144
6163
  const mappings = map && !("version" in map) && map.mappings === "";
6145
- return this.cacheResult(result, cachePath, importers, !!mappings);
6164
+ return this.cacheResult(result, cachePath, importers, importedUrls, !!mappings);
6146
6165
  }
6166
+ // we need this for UI to be able to show a module graph
6167
+ getSerializedImports(node) {
6168
+ const imports = [];
6169
+ node.importedModules.forEach((importer) => {
6170
+ imports.push(importer.url);
6171
+ });
6172
+ return imports;
6173
+ }
6174
+ // we need this for the watcher to be able to find the related test file
6147
6175
  getSerializedDependencies(node) {
6148
6176
  const dependencies = [];
6149
6177
  node.importers.forEach((importer) => {
@@ -6175,7 +6203,7 @@ class ModuleFetcher {
6175
6203
  // null means the file should not be cached
6176
6204
  if (memoryCacheKey !== void 0) return memoryCacheKey;
6177
6205
  const fileContent = await this.readFileContentToCache(environment, moduleGraphModule);
6178
- return this.fsCache.generateCachePath(this.config, environment, this.resolver, moduleGraphModule.id, fileContent);
6206
+ return this.fsCache.generateCachePath(this.config, environment, moduleGraphModule.id, fileContent);
6179
6207
  }
6180
6208
  async readFileContentToCache(environment, moduleGraphModule) {
6181
6209
  if (moduleGraphModule.file && !moduleGraphModule.file.startsWith("\0") && !moduleGraphModule.file.startsWith("virtual:")) {
@@ -6214,6 +6242,10 @@ class ModuleFetcher {
6214
6242
  const environmentNode = environment.moduleGraph.getModuleById(importer);
6215
6243
  if (environmentNode) moduleGraphModule.importers.add(environmentNode);
6216
6244
  });
6245
+ await Promise.all(cachedModule.importedUrls.map(async (url) => {
6246
+ const moduleNode = await environment.moduleGraph.ensureEntryFromUrl(url).catch(() => null);
6247
+ if (moduleNode) moduleGraphModule.importedModules.add(moduleNode);
6248
+ }));
6217
6249
  return {
6218
6250
  cached: true,
6219
6251
  file: cachedModule.file,
@@ -6224,23 +6256,18 @@ class ModuleFetcher {
6224
6256
  };
6225
6257
  }
6226
6258
  async fetchAndProcess(environment, url, importer, moduleGraphModule, options) {
6227
- const externalize = await this.resolver.shouldExternalize(moduleGraphModule.id);
6228
- if (externalize) return {
6229
- externalize,
6230
- type: "module"
6231
- };
6232
6259
  return processResultSource(environment, await fetchModule(environment, url, importer, {
6233
6260
  ...options,
6234
6261
  inlineSourceMap: false
6235
6262
  }).catch(handleRollupError));
6236
6263
  }
6237
- async cacheResult(result, cachePath, importers = [], mappings = false) {
6264
+ async cacheResult(result, cachePath, importers = [], importedUrls = [], mappings = false) {
6238
6265
  const returnResult = "code" in result ? getCachedResult(result, cachePath) : result;
6239
6266
  if (saveCachePromises.has(cachePath)) {
6240
6267
  await saveCachePromises.get(cachePath);
6241
6268
  return returnResult;
6242
6269
  }
6243
- const savePromise = this.fsCache.saveCachedModule(cachePath, result, importers, mappings).then(() => result).finally(() => {
6270
+ const savePromise = this.fsCache.saveCachedModule(cachePath, result, importers, importedUrls, mappings).then(() => result).finally(() => {
6244
6271
  saveCachePromises.delete(cachePath);
6245
6272
  });
6246
6273
  saveCachePromises.set(cachePath, savePromise);
@@ -6259,7 +6286,7 @@ class ModuleFetcher {
6259
6286
  }
6260
6287
  }
6261
6288
  function createFetchModuleFunction(resolver, config, fsCache, traces, tmpProjectDir) {
6262
- const fetcher = new ModuleFetcher(resolver, config, fsCache, traces, tmpProjectDir);
6289
+ const fetcher = new ModuleFetcher(resolver, config, fsCache, tmpProjectDir);
6263
6290
  return async (url, importer, environment, cacheFs, options, otelCarrier) => {
6264
6291
  await traces.waitInit();
6265
6292
  const context = otelCarrier ? traces.getContextFromCarrier(otelCarrier) : void 0;
@@ -6658,6 +6685,238 @@ This might cause false positive tests. Resolve unhandled errors to make sure you
6658
6685
  }
6659
6686
  }
6660
6687
 
6688
+ // this function recieves the module diagnostic with the location of imports
6689
+ // and populates it with collected import durations; the duration is injected
6690
+ // only if the current module is the one that imported the module
6691
+ // if testModule is not defined, then Vitest aggregates durations of ALL collected test modules
6692
+ function collectModuleDurationsDiagnostic(moduleId, state, moduleDiagnostic, testModule) {
6693
+ if (!moduleDiagnostic) return {
6694
+ modules: [],
6695
+ untrackedModules: []
6696
+ };
6697
+ const modules = [];
6698
+ const modulesById = {};
6699
+ const allModules = [...moduleDiagnostic.modules, ...moduleDiagnostic.untracked];
6700
+ const visitedByFiles = {};
6701
+ // this aggregates the times for _ALL_ tests if testModule is not passed
6702
+ // so if the module was imported in separate tests, the time will be accumulated
6703
+ for (const files of testModule ? [[testModule.task]] : state.filesMap.values()) for (const file of files) {
6704
+ const importDurations = file.importDurations;
6705
+ if (!importDurations) continue;
6706
+ const currentModule = state.getReportedEntity(file);
6707
+ if (!currentModule) continue;
6708
+ const visitedKey = currentModule.project.config.isolate === false ? "non-isolate" : file.id;
6709
+ if (!visitedByFiles[visitedKey]) visitedByFiles[visitedKey] = /* @__PURE__ */ new Set();
6710
+ const visited = visitedByFiles[visitedKey];
6711
+ allModules.forEach(({ resolvedId, resolvedUrl }) => {
6712
+ const durations = importDurations[resolvedId];
6713
+ // do not accumulate if module was already visited by suite (or suites in non-isolate mode)
6714
+ if (!durations || visited.has(resolvedId)) return;
6715
+ const importer = getModuleImporter(moduleId, durations, currentModule);
6716
+ modulesById[resolvedId] ??= {
6717
+ selfTime: 0,
6718
+ totalTime: 0,
6719
+ transformTime: 0,
6720
+ external: durations.external,
6721
+ importer
6722
+ };
6723
+ // only track if the current module imported this module,
6724
+ // otherwise it was imported instantly because it's cached
6725
+ if (importer === moduleId) {
6726
+ visited.add(resolvedId);
6727
+ modulesById[resolvedId].selfTime += durations.selfTime;
6728
+ modulesById[resolvedId].totalTime += durations.totalTime;
6729
+ // don't aggregate
6730
+ modulesById[resolvedId].transformTime = state.metadata[currentModule.project.name]?.duration[resolvedUrl]?.[0];
6731
+ }
6732
+ });
6733
+ }
6734
+ // if module was imported twice in the same file,
6735
+ // show only one time - the second should be shown as 0
6736
+ const visitedInFile = /* @__PURE__ */ new Set();
6737
+ moduleDiagnostic.modules.forEach((diagnostic) => {
6738
+ const durations = modulesById[diagnostic.resolvedId];
6739
+ if (!durations) return;
6740
+ if (visitedInFile.has(diagnostic.resolvedId)) modules.push({
6741
+ ...diagnostic,
6742
+ selfTime: 0,
6743
+ totalTime: 0,
6744
+ transformTime: 0,
6745
+ external: durations.external,
6746
+ importer: durations.importer
6747
+ });
6748
+ else {
6749
+ visitedInFile.add(diagnostic.resolvedId);
6750
+ modules.push({
6751
+ ...diagnostic,
6752
+ ...durations
6753
+ });
6754
+ }
6755
+ });
6756
+ const untracked = [];
6757
+ moduleDiagnostic.untracked.forEach((diagnostic) => {
6758
+ const durations = modulesById[diagnostic.resolvedId];
6759
+ if (!durations) return;
6760
+ if (visitedInFile.has(diagnostic.resolvedId)) untracked.push({
6761
+ selfTime: 0,
6762
+ totalTime: 0,
6763
+ transformTime: 0,
6764
+ external: durations.external,
6765
+ importer: durations.importer,
6766
+ resolvedId: diagnostic.resolvedId,
6767
+ resolvedUrl: diagnostic.resolvedUrl,
6768
+ url: diagnostic.rawUrl
6769
+ });
6770
+ else {
6771
+ visitedInFile.add(diagnostic.resolvedId);
6772
+ untracked.push({
6773
+ ...durations,
6774
+ resolvedId: diagnostic.resolvedId,
6775
+ resolvedUrl: diagnostic.resolvedUrl,
6776
+ url: diagnostic.rawUrl
6777
+ });
6778
+ }
6779
+ });
6780
+ return {
6781
+ modules,
6782
+ untrackedModules: untracked
6783
+ };
6784
+ }
6785
+ function getModuleImporter(moduleId, durations, testModule) {
6786
+ if (durations.importer === moduleId) return moduleId;
6787
+ if (!durations.importer) {
6788
+ if (moduleId === testModule.moduleId) return testModule.moduleId;
6789
+ return testModule.project.config.setupFiles.includes(moduleId) ? moduleId : durations.importer;
6790
+ }
6791
+ return durations.importer;
6792
+ }
6793
+ // the idea of this is very simple
6794
+ // it parses the source code to extract import/export statements
6795
+ // it parses SSR transformed file to extract __vite_ssr_import__ and __vite_ssr_dynamic_import__
6796
+ // it combines the two by looking at the original positions of SSR primitives
6797
+ // in the end, we are able to return a list of modules that were imported by this module
6798
+ // mapped to their IDs in Vite's module graph
6799
+ async function collectSourceModulesLocations(moduleId, moduleGraph) {
6800
+ const transformResult = moduleGraph.getModuleById(moduleId)?.transformResult;
6801
+ if (!transformResult || !transformResult.ssr) return;
6802
+ const map = transformResult.map;
6803
+ if (!map || !("version" in map) || !map.sources.length) return;
6804
+ const sourceImports = map.sources.reduce((acc, sourceId, index) => {
6805
+ const source = map.sourcesContent?.[index];
6806
+ if (source != null) acc[sourceId] = parseSourceImportsAndExports(source);
6807
+ return acc;
6808
+ }, {});
6809
+ const transformImports = await parseTransformResult(moduleGraph, transformResult);
6810
+ const traceMap = map && "version" in map && new TraceMap(map);
6811
+ const modules = {};
6812
+ const untracked = [];
6813
+ transformImports.forEach((row) => {
6814
+ const original = traceMap && originalPositionFor(traceMap, row.start);
6815
+ if (original && original.source != null) {
6816
+ // if there are several at the same position, this is a bug
6817
+ // probably caused by import.meta.glob imports returning incorrect positions
6818
+ // all the new import.meta.glob imports come first, so only the last module on this line is correct
6819
+ const sourceImport = sourceImports[original.source].get(`${original.line}:${original.column}`);
6820
+ if (sourceImport) {
6821
+ if (modules[sourceImport.rawUrl]) {
6822
+ // remove imports with a different resolvedId
6823
+ const differentImports = modules[sourceImport.rawUrl].filter((d) => d.resolvedId !== row.resolvedId);
6824
+ untracked.push(...differentImports);
6825
+ modules[sourceImport.rawUrl] = modules[sourceImport.rawUrl].filter((d) => d.resolvedId === row.resolvedId);
6826
+ }
6827
+ modules[sourceImport.rawUrl] ??= [];
6828
+ modules[sourceImport.rawUrl].push({
6829
+ start: sourceImport.start,
6830
+ end: sourceImport.end,
6831
+ startIndex: sourceImport.startIndex,
6832
+ endIndex: sourceImport.endIndex,
6833
+ rawUrl: sourceImport.rawUrl,
6834
+ resolvedId: row.resolvedId,
6835
+ resolvedUrl: row.resolvedUrl
6836
+ });
6837
+ }
6838
+ }
6839
+ });
6840
+ return {
6841
+ modules: Object.values(modules).flat(),
6842
+ untracked
6843
+ };
6844
+ }
6845
+ function fillSourcesMap(syntax, sourcesMap, source, indexMap) {
6846
+ const splitSeparator = `${syntax} `;
6847
+ const splitSources = source.split(splitSeparator);
6848
+ const chunks = [];
6849
+ let index = 0;
6850
+ for (const chunk of splitSources) {
6851
+ chunks.push({
6852
+ chunk,
6853
+ startIndex: index
6854
+ });
6855
+ index += chunk.length + splitSeparator.length;
6856
+ }
6857
+ chunks.forEach(({ chunk, startIndex }) => {
6858
+ const normalized = chunk.replace(/'/g, "\"");
6859
+ const startQuoteIdx = normalized.indexOf("\"");
6860
+ if (startQuoteIdx === -1) return;
6861
+ const endQuoteIdx = normalized.indexOf("\"", startQuoteIdx + 1);
6862
+ if (endQuoteIdx === -1) return;
6863
+ const staticSyntax = {
6864
+ startIndex: startIndex + startQuoteIdx,
6865
+ endIndex: startIndex + endQuoteIdx + 1,
6866
+ start: indexMap.get(startIndex + startQuoteIdx),
6867
+ end: indexMap.get(startIndex + endQuoteIdx + 1),
6868
+ rawUrl: normalized.slice(startQuoteIdx + 1, endQuoteIdx)
6869
+ };
6870
+ // -7 to include "import "
6871
+ for (let i = startIndex - 7; i < staticSyntax.endIndex; i++) {
6872
+ const location = indexMap.get(i);
6873
+ if (location) sourcesMap.set(`${location.line}:${location.column}`, staticSyntax);
6874
+ }
6875
+ });
6876
+ }
6877
+ // this function tries to parse ESM static import and export statements from
6878
+ // the source. if the source is not JS/TS, but supports static ESM syntax,
6879
+ // then this will also find them because it' only checks the strings, it doesn't parse the AST
6880
+ function parseSourceImportsAndExports(source) {
6881
+ if (!source.includes("import ") && !source.includes("export ")) return /* @__PURE__ */ new Map();
6882
+ const sourcesMap = /* @__PURE__ */ new Map();
6883
+ const indexMap = createIndexLocationsMap(source);
6884
+ fillSourcesMap("import", sourcesMap, source, indexMap);
6885
+ fillSourcesMap("export", sourcesMap, source, indexMap);
6886
+ return sourcesMap;
6887
+ }
6888
+ async function parseTransformResult(moduleGraph, transformResult) {
6889
+ const code = transformResult.code;
6890
+ const regexp = /(?:__vite_ssr_import__|__vite_ssr_dynamic_import__)\("([^"]+)"/g;
6891
+ const lineColumnMap = createIndexLocationsMap(code);
6892
+ const importPositions = [];
6893
+ let match;
6894
+ // eslint-disable-next-line no-cond-assign
6895
+ while (match = regexp.exec(code)) {
6896
+ const startIndex = match.index;
6897
+ const endIndex = match.index + match[0].length - 1;
6898
+ importPositions.push({
6899
+ raw: match[1],
6900
+ startIndex,
6901
+ endIndex
6902
+ });
6903
+ }
6904
+ return (await Promise.all(importPositions.map(async ({ startIndex, endIndex, raw }) => {
6905
+ const position = lineColumnMap.get(startIndex);
6906
+ const endPosition = lineColumnMap.get(endIndex);
6907
+ const moduleNode = await moduleGraph.getModuleByUrl(raw);
6908
+ if (!position || !endPosition || !moduleNode || !moduleNode.id) return;
6909
+ return {
6910
+ resolvedId: moduleNode.id,
6911
+ resolvedUrl: moduleNode.url,
6912
+ start: position,
6913
+ end: endPosition,
6914
+ startIndex,
6915
+ endIndex
6916
+ };
6917
+ }))).filter((n) => n != null);
6918
+ }
6919
+
6661
6920
  const __dirname$1 = url.fileURLToPath(new URL(".", import.meta.url));
6662
6921
  class VitestPackageInstaller {
6663
6922
  isPackageExists(name, options) {
@@ -7036,7 +7295,7 @@ function createMethodsRPC(project, methodsOptions = {}) {
7036
7295
  const file = cleanUrl(resolved.id);
7037
7296
  if (resolved.external) return {
7038
7297
  file,
7039
- url: !resolved.id.startsWith("node:") && isBuiltin(resolved.id) ? `node:${resolved.id}` : resolved.id,
7298
+ url: isBuiltin(resolved.id) ? toBuiltin(resolved.id) : resolved.id,
7040
7299
  id: resolved.id
7041
7300
  };
7042
7301
  return {
@@ -7179,7 +7438,6 @@ class PoolRunner {
7179
7438
  __vitest_worker_request__: true,
7180
7439
  type: method,
7181
7440
  context,
7182
- poolId: this.poolId,
7183
7441
  otelCarrier: this.getOTELCarrier()
7184
7442
  });
7185
7443
  }
@@ -7187,7 +7445,7 @@ class PoolRunner {
7187
7445
  const activeContext = this._otel?.currentContext || this._otel?.workerContext;
7188
7446
  return activeContext ? this._traces.getContextCarrier(activeContext) : void 0;
7189
7447
  }
7190
- async start() {
7448
+ async start(options) {
7191
7449
  // Wait for any ongoing operation to complete
7192
7450
  if (this._operationLock) await this._operationLock;
7193
7451
  if (this._state === RunnerState.STARTED || this._state === RunnerState.STARTING) return;
@@ -7211,6 +7469,8 @@ class PoolRunner {
7211
7469
  const tracesSdk = projectConfig?.sdkPath ?? globalConfig?.sdkPath;
7212
7470
  this.postMessage({
7213
7471
  type: "start",
7472
+ poolId: this.poolId,
7473
+ workerId: options.workerId,
7214
7474
  __vitest_worker_request__: true,
7215
7475
  options: { reportMemory: this.worker.reportMemory ?? false },
7216
7476
  context: {
@@ -7239,7 +7499,7 @@ class PoolRunner {
7239
7499
  this._operationLock = null;
7240
7500
  }
7241
7501
  }
7242
- async stop() {
7502
+ async stop(options) {
7243
7503
  // Wait for any ongoing operation to complete
7244
7504
  if (this._operationLock) await this._operationLock;
7245
7505
  if (this._state === RunnerState.STOPPED || this._state === RunnerState.STOPPING) return;
@@ -7267,6 +7527,11 @@ class PoolRunner {
7267
7527
  this.off("message", onStop);
7268
7528
  }
7269
7529
  };
7530
+ // Don't wait for graceful exit's response when force exiting
7531
+ if (options?.force) return onStop({
7532
+ type: "stopped",
7533
+ __vitest_worker_response__: true
7534
+ });
7270
7535
  this.on("message", onStop);
7271
7536
  this.postMessage({
7272
7537
  type: "stop",
@@ -7716,6 +7981,8 @@ class Pool {
7716
7981
  try {
7717
7982
  let isMemoryLimitReached = false;
7718
7983
  const runner = this.getPoolRunner(task, method);
7984
+ const poolId = runner.poolId ?? this.getWorkerId();
7985
+ runner.poolId = poolId;
7719
7986
  const activeTask = {
7720
7987
  task,
7721
7988
  resolver,
@@ -7724,7 +7991,8 @@ class Pool {
7724
7991
  };
7725
7992
  this.activeTasks.push(activeTask);
7726
7993
  // active tasks receive cancel signal and shut down gracefully
7727
- async function cancelTask() {
7994
+ async function cancelTask(options) {
7995
+ if (options?.force) await runner.stop({ force: true });
7728
7996
  await runner.waitForTerminated();
7729
7997
  resolver.reject(/* @__PURE__ */ new Error("Cancelled"));
7730
7998
  }
@@ -7742,10 +8010,8 @@ class Pool {
7742
8010
  resolver.reject(new Error(`[vitest-pool]: Worker ${task.worker} emitted error.`, { cause: error }));
7743
8011
  });
7744
8012
  const id = setTimeout(() => resolver.reject(/* @__PURE__ */ new Error(`[vitest-pool]: Timeout starting ${task.worker} runner.`)), WORKER_START_TIMEOUT);
7745
- await runner.start().finally(() => clearTimeout(id));
8013
+ await runner.start({ workerId: task.context.workerId }).finally(() => clearTimeout(id));
7746
8014
  }
7747
- const poolId = runner.poolId ?? this.getWorkerId();
7748
- runner.poolId = poolId;
7749
8015
  const span = runner.startTracesSpan(`vitest.worker.${method}`);
7750
8016
  // Start running the test in the worker
7751
8017
  runner.request(method, task.context);
@@ -7776,6 +8042,9 @@ catch (error) {
7776
8042
  return this.schedule();
7777
8043
  }
7778
8044
  async cancel() {
8045
+ // Force exit if previous cancel is still on-going
8046
+ // for example when user does 'CTRL+c' twice in row
8047
+ const force = this._isCancelling;
7779
8048
  // Set flag to prevent new tasks from being queued
7780
8049
  this._isCancelling = true;
7781
8050
  const pendingTasks = this.queue.splice(0);
@@ -7783,11 +8052,12 @@ catch (error) {
7783
8052
  const error = /* @__PURE__ */ new Error("Cancelled");
7784
8053
  pendingTasks.forEach((task) => task.resolver.reject(error));
7785
8054
  }
7786
- const activeTasks = this.activeTasks.splice(0);
7787
- await Promise.all(activeTasks.map((task) => task.cancelTask()));
7788
- const sharedRunners = this.sharedRunners.splice(0);
7789
- await Promise.all(sharedRunners.map((runner) => runner.stop()));
7790
- await Promise.all(this.exitPromises.splice(0));
8055
+ await Promise.all(this.activeTasks.map((task) => task.cancelTask({ force })));
8056
+ this.activeTasks = [];
8057
+ await Promise.all(this.sharedRunners.map((runner) => runner.stop()));
8058
+ this.sharedRunners = [];
8059
+ await Promise.all(this.exitPromises);
8060
+ this.exitPromises = [];
7791
8061
  this.workerIds.forEach((_, id) => this.freeWorkerId(id));
7792
8062
  // Reset flag after cancellation completes
7793
8063
  this._isCancelling = false;
@@ -8191,7 +8461,10 @@ function serializeConfig(project) {
8191
8461
  printConsoleTrace: config.printConsoleTrace ?? globalConfig.printConsoleTrace,
8192
8462
  benchmark: config.benchmark && { includeSamples: config.benchmark.includeSamples },
8193
8463
  serializedDefines: config.browser.enabled ? "" : project._serializedDefines || "",
8194
- experimental: { fsModuleCache: config.experimental.fsModuleCache ?? false }
8464
+ experimental: {
8465
+ fsModuleCache: config.experimental.fsModuleCache ?? false,
8466
+ printImportBreakdown: config.experimental.printImportBreakdown
8467
+ }
8195
8468
  };
8196
8469
  }
8197
8470
 
@@ -9111,7 +9384,10 @@ function ModuleRunnerTransform() {
9111
9384
  environment.resolve.noExternal = true;
9112
9385
  // Workaround `noExternal` merging bug on Vite 6
9113
9386
  // https://github.com/vitejs/vite/pull/20502
9114
- if (name === "ssr") delete config.ssr?.noExternal;
9387
+ if (name === "ssr") {
9388
+ delete config.ssr?.noExternal;
9389
+ delete config.ssr?.external;
9390
+ }
9115
9391
  if (name === "__vitest_vm__" || name === "__vitest__") continue;
9116
9392
  const currentOptimizeDeps = environment.optimizeDeps || (name === "client" ? config.optimizeDeps : name === "ssr" ? config.ssr?.optimizeDeps : void 0);
9117
9393
  const optimizeDeps = resolveOptimizerConfig(testConfig.deps?.optimizer?.[name], currentOptimizeDeps);
@@ -9341,6 +9617,7 @@ function WorkspaceVitestPlugin(project, options) {
9341
9617
 
9342
9618
  class VitestResolver {
9343
9619
  options;
9620
+ externalizeConcurrentCache = /* @__PURE__ */ new Map();
9344
9621
  externalizeCache = /* @__PURE__ */ new Map();
9345
9622
  constructor(cacheDir, config) {
9346
9623
  // sorting to make cache consistent
@@ -9360,8 +9637,20 @@ class VitestResolver {
9360
9637
  external
9361
9638
  };
9362
9639
  }
9363
- shouldExternalize(file) {
9364
- return shouldExternalize(normalizeId(file), this.options, this.externalizeCache);
9640
+ wasExternalized(file) {
9641
+ const normalizedFile = normalizeId(file);
9642
+ if (!this.externalizeCache.has(normalizedFile)) return false;
9643
+ return this.externalizeCache.get(normalizedFile) ?? false;
9644
+ }
9645
+ async shouldExternalize(file) {
9646
+ const normalizedFile = normalizeId(file);
9647
+ if (this.externalizeCache.has(normalizedFile)) return this.externalizeCache.get(normalizedFile);
9648
+ return shouldExternalize(normalizeId(file), this.options, this.externalizeConcurrentCache).then((result) => {
9649
+ this.externalizeCache.set(normalizedFile, result);
9650
+ return result;
9651
+ }).finally(() => {
9652
+ this.externalizeConcurrentCache.delete(normalizedFile);
9653
+ });
9365
9654
  }
9366
9655
  }
9367
9656
  function normalizeId(id) {
@@ -9406,6 +9695,9 @@ function guessCJSversion(id) {
9406
9695
  }
9407
9696
  // The code from https://github.com/unjs/mlly/blob/c5bcca0cda175921344fd6de1bc0c499e73e5dac/src/syntax.ts#L51-L98
9408
9697
  async function isValidNodeImport(id) {
9698
+ // clean url to strip off `?v=...` query etc.
9699
+ // node can natively import files with query params, so externalizing them is safe.
9700
+ id = cleanUrl(id);
9409
9701
  const extension = extname(id);
9410
9702
  if (BUILTIN_EXTENSIONS.has(extension)) return true;
9411
9703
  if (extension !== ".js") return false;
@@ -9426,7 +9718,7 @@ async function shouldExternalize(id, options, cache) {
9426
9718
  return cache.get(id);
9427
9719
  }
9428
9720
  async function _shouldExternalize(id, options) {
9429
- if (isBuiltin(id)) return id;
9721
+ if (isBuiltin$1(id)) return id;
9430
9722
  // data: should be processed by native import,
9431
9723
  // since it is a feature of ESM.
9432
9724
  // also externalize network imports since nodejs allows it when --experimental-network-imports
@@ -9443,7 +9735,6 @@ async function _shouldExternalize(id, options) {
9443
9735
  if (matchPattern(id, moduleDirectories, defaultInline)) return false;
9444
9736
  if (matchPattern(id, moduleDirectories, depsExternal)) return id;
9445
9737
  if (isLibraryModule && await isValidNodeImport(id)) return id;
9446
- return false;
9447
9738
  }
9448
9739
  function matchPattern(id, moduleDirectories, patterns) {
9449
9740
  if (patterns == null) return false;
@@ -10674,6 +10965,12 @@ class TestSuite extends SuiteImplementation {
10674
10965
  class TestModule extends SuiteImplementation {
10675
10966
  type = "module";
10676
10967
  /**
10968
+ * The Vite environment that processes files on the server.
10969
+ *
10970
+ * Can be empty if test module did not run yet.
10971
+ */
10972
+ viteEnvironment;
10973
+ /**
10677
10974
  * This is usually an absolute UNIX file path.
10678
10975
  * It can be a virtual ID if the file is not on the disk.
10679
10976
  * This value corresponds to the ID in the Vite's module graph.
@@ -10688,6 +10985,8 @@ class TestModule extends SuiteImplementation {
10688
10985
  super(task, project);
10689
10986
  this.moduleId = task.filepath;
10690
10987
  this.relativeModuleId = task.name;
10988
+ if (task.viteEnvironment === "__browser__") this.viteEnvironment = project.browser?.vite.environments.client;
10989
+ else if (typeof task.viteEnvironment === "string") this.viteEnvironment = project.vite.environments[task.viteEnvironment];
10691
10990
  }
10692
10991
  /**
10693
10992
  * Checks the running state of the test file.
@@ -12240,6 +12539,39 @@ class Vitest {
12240
12539
  return await this.runningPromise;
12241
12540
  });
12242
12541
  }
12542
+ /**
12543
+ * Returns module's diagnostic. If `testModule` is not provided, `selfTime` and `totalTime` will be aggregated across all tests.
12544
+ *
12545
+ * If the module was not transformed or executed, the diagnostic will be empty.
12546
+ * @experimental
12547
+ * @see {@link https://vitest.dev/api/advanced/vitest#getsourcemodulediagnostic}
12548
+ */
12549
+ async experimental_getSourceModuleDiagnostic(moduleId, testModule) {
12550
+ if (testModule) {
12551
+ const viteEnvironment = testModule.viteEnvironment;
12552
+ // if there is no viteEnvironment, it means the file did not run yet
12553
+ if (!viteEnvironment) return {
12554
+ modules: [],
12555
+ untrackedModules: []
12556
+ };
12557
+ const moduleLocations = await collectSourceModulesLocations(moduleId, viteEnvironment.moduleGraph);
12558
+ return collectModuleDurationsDiagnostic(moduleId, this.state, moduleLocations, testModule);
12559
+ }
12560
+ const environments = this.projects.flatMap((p) => {
12561
+ return Object.values(p.vite.environments);
12562
+ });
12563
+ const aggregatedLocationsResult = await Promise.all(environments.map((environment) => collectSourceModulesLocations(moduleId, environment.moduleGraph)));
12564
+ return collectModuleDurationsDiagnostic(moduleId, this.state, aggregatedLocationsResult.reduce((acc, locations) => {
12565
+ if (locations) {
12566
+ acc.modules.push(...locations.modules);
12567
+ acc.untracked.push(...locations.untracked);
12568
+ }
12569
+ return acc;
12570
+ }, {
12571
+ modules: [],
12572
+ untracked: []
12573
+ }));
12574
+ }
12243
12575
  async experimental_parseSpecifications(specifications, options) {
12244
12576
  if (this.mode !== "test") throw new Error(`The \`experimental_parseSpecifications\` does not support "${this.mode}" mode.`);
12245
12577
  const limit = limitConcurrency(options?.concurrency ?? (typeof nodeos__default.availableParallelism === "function" ? nodeos__default.availableParallelism() : nodeos__default.cpus().length));