vitest 4.0.14 → 4.0.16

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 (54) hide show
  1. package/dist/browser.d.ts +2 -2
  2. package/dist/browser.js +2 -2
  3. package/dist/chunks/{base.BEv8sRbK.js → base.Bin-9uYm.js} +7 -6
  4. package/dist/chunks/browser.d.Bz3lxTX-.d.ts +57 -0
  5. package/dist/chunks/{cac.DnEx6DOX.js → cac.BGonGPac.js} +10 -25
  6. package/dist/chunks/{cli-api.CbjxIXjQ.js → cli-api.BKg19Fvw.js} +469 -124
  7. package/dist/chunks/{config.d.g6OOauRt.d.ts → config.d.CzIjkicf.d.ts} +1 -0
  8. package/dist/chunks/{coverage.CtyeYmKM.js → coverage.BuJUwVtg.js} +2 -1
  9. package/dist/chunks/evaluatedModules.d.BxJ5omdx.d.ts +7 -0
  10. package/dist/chunks/{globals.C0izxiX3.js → globals.DOayXfHP.js} +3 -3
  11. package/dist/chunks/{index.D6PC4Dpu.js → index.456_DGfR.js} +128 -18
  12. package/dist/chunks/{index.CQwQ_SLL.js → index.6Qv1eEA6.js} +3 -3
  13. package/dist/chunks/index.Chj8NDwU.js +206 -0
  14. package/dist/chunks/{index.B88tjlE5.js → index.Drsj_6e7.js} +1 -1
  15. package/dist/chunks/{index.DBx1AtPJ.js → index.Z5E_ObnR.js} +1 -1
  16. package/dist/chunks/{init-forks.DmvIFK4U.js → init-forks.v9UONQS6.js} +11 -2
  17. package/dist/chunks/{init-threads.De6b3S3g.js → init-threads.DqYg3Trk.js} +1 -1
  18. package/dist/chunks/{init.a5SCIJ0x.js → init.KmQZdqFg.js} +2 -2
  19. package/dist/chunks/modules.BJuCwlRJ.js +36 -0
  20. package/dist/chunks/{plugin.d.B6hlg3fN.d.ts → plugin.d.v1sC_bv1.d.ts} +1 -1
  21. package/dist/chunks/{reporters.d.DeFcIuza.d.ts → reporters.d.Rsi0PyxX.d.ts} +27 -4
  22. package/dist/chunks/{rpc.BytlcPfC.js → rpc.BoxB0q7B.js} +1 -1
  23. package/dist/chunks/{setup-common.DGHc_BUK.js → setup-common.Cm-kSBVi.js} +1 -1
  24. package/dist/chunks/{startModuleRunner.W28wBIgJ.js → startModuleRunner.DpqpB8k3.js} +8 -9
  25. package/dist/chunks/{test.DqQZzsWf.js → test.B8ej_ZHS.js} +9 -4
  26. package/dist/chunks/{vi.BiaV1qII.js → vi.2VT5v0um.js} +40 -30
  27. package/dist/chunks/{vm.BbVD4fJ5.js → vm.qFl6P1nF.js} +2 -2
  28. package/dist/chunks/worker.d.5JNaocaN.d.ts +254 -0
  29. package/dist/cli.js +2 -2
  30. package/dist/config.d.ts +7 -7
  31. package/dist/coverage.d.ts +8 -8
  32. package/dist/coverage.js +1 -1
  33. package/dist/index.d.ts +22 -14
  34. package/dist/index.js +3 -3
  35. package/dist/module-evaluator.d.ts +5 -0
  36. package/dist/module-evaluator.js +23 -16
  37. package/dist/module-runner.js +2 -1
  38. package/dist/node.d.ts +17 -10
  39. package/dist/node.js +10 -9
  40. package/dist/reporters.d.ts +7 -7
  41. package/dist/reporters.js +2 -2
  42. package/dist/runners.d.ts +2 -1
  43. package/dist/runners.js +4 -4
  44. package/dist/worker.d.ts +5 -3
  45. package/dist/worker.js +11 -10
  46. package/dist/workers/forks.js +12 -11
  47. package/dist/workers/runVmTests.js +7 -7
  48. package/dist/workers/threads.js +12 -11
  49. package/dist/workers/vmForks.js +7 -6
  50. package/dist/workers/vmThreads.js +7 -6
  51. package/package.json +15 -15
  52. package/dist/chunks/browser.d.F6jMf15V.d.ts +0 -18
  53. package/dist/chunks/index.0kCJoeWi.js +0 -220
  54. package/dist/chunks/worker.d.DhEa3KzY.d.ts +0 -238
@@ -2,20 +2,20 @@ import fs, { promises, existsSync, mkdirSync, readFileSync, statSync, readdirSyn
2
2
  import { relative, resolve, dirname, join, extname, normalize, basename, isAbsolute } from 'pathe';
3
3
  import { C as CoverageProviderMap } from './coverage.D_JHT54q.js';
4
4
  import path, { resolve as resolve$1 } from 'node:path';
5
- import { noop, createDefer, slash, isExternalUrl, unwrapId, withTrailingSlash, cleanUrl, wrapId, toArray, deepMerge, nanoid, deepClone, isPrimitive, notNullish } from '@vitest/utils/helpers';
5
+ import { noop, createDefer, slash, withTrailingSlash, cleanUrl, wrapId, isExternalUrl, unwrapId, toArray, deepMerge, nanoid, deepClone, isPrimitive, notNullish } from '@vitest/utils/helpers';
6
6
  import { a as any, p as prompt } from './index.D4KonVSU.js';
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';
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.BuJUwVtg.js';
8
8
  import * as vite from 'vite';
9
- import { parseAst, searchForWorkspaceRoot, fetchModule, version, mergeConfig, createServer } from 'vite';
9
+ import { isFileServingAllowed as isFileServingAllowed$1, parseAst, searchForWorkspaceRoot, fetchModule, version, mergeConfig, createServer, isFileLoadingAllowed, normalizePath } 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
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.DnEx6DOX.js';
15
+ import { v as version$1 } from './cac.BGonGPac.js';
16
16
  import { performance as performance$1 } from 'node:perf_hooks';
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.D6PC4Dpu.js';
17
+ import { c as createBirpc } from './index.Chj8NDwU.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';
@@ -37,11 +37,12 @@ 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.BJuCwlRJ.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.B88tjlE5.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$1(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$1(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;
@@ -5450,7 +5472,7 @@ function createFileTask(testFilepath, code, requestMap, options) {
5450
5472
  file: null
5451
5473
  };
5452
5474
  file.file = file;
5453
- const indexMap = createIndexMap(code);
5475
+ const indexMap = createIndexLocationsMap(code);
5454
5476
  const map = requestMap && new TraceMap(requestMap);
5455
5477
  let lastSuite = file;
5456
5478
  const updateLatestSuite = (index) => {
@@ -5552,23 +5574,6 @@ async function transformSSR(project, filepath) {
5552
5574
  if (!request) return null;
5553
5575
  return await project.vite.ssrTransform(request.code, request.map, filepath);
5554
5576
  }
5555
- function createIndexMap(source) {
5556
- const map = /* @__PURE__ */ new Map();
5557
- let index = 0;
5558
- let line = 1;
5559
- let column = 1;
5560
- for (const char of source) {
5561
- map.set(index++, {
5562
- line,
5563
- column
5564
- });
5565
- if (char === "\n" || char === "\r\n") {
5566
- line++;
5567
- column = 0;
5568
- } else column++;
5569
- }
5570
- return map;
5571
- }
5572
5577
  function markDynamicTests(tasks) {
5573
5578
  for (const task of tasks) {
5574
5579
  if (task.dynamic) task.id += "-dynamic";
@@ -5766,7 +5771,7 @@ class FileSystemModuleCache {
5766
5771
  */
5767
5772
  rootCache;
5768
5773
  metadataFilePath;
5769
- version = "1.0.0-beta.2";
5774
+ version = "1.0.0-beta.3";
5770
5775
  fsCacheRoots = /* @__PURE__ */ new WeakMap();
5771
5776
  fsEnvironmentHashMap = /* @__PURE__ */ new WeakMap();
5772
5777
  fsCacheKeyGenerators = /* @__PURE__ */ new Set();
@@ -5811,7 +5816,7 @@ class FileSystemModuleCache {
5811
5816
  }
5812
5817
  async getCachedModule(cachedFilePath) {
5813
5818
  if (!existsSync(cachedFilePath)) {
5814
- 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`);
5815
5820
  return;
5816
5821
  }
5817
5822
  const fileResult = await this.readCachedFileConcurrently(cachedFilePath);
@@ -5824,16 +5829,18 @@ class FileSystemModuleCache {
5824
5829
  file: meta.file,
5825
5830
  code,
5826
5831
  importers: meta.importers,
5832
+ importedUrls: meta.importedUrls,
5827
5833
  mappings: meta.mappings
5828
5834
  };
5829
5835
  }
5830
- async saveCachedModule(cachedFilePath, fetchResult, importers = [], mappings = false) {
5836
+ async saveCachedModule(cachedFilePath, fetchResult, importers = [], importedUrls = [], mappings = false) {
5831
5837
  if ("code" in fetchResult) {
5832
5838
  const result = {
5833
5839
  file: fetchResult.file,
5834
5840
  id: fetchResult.id,
5835
5841
  url: fetchResult.url,
5836
5842
  importers,
5843
+ importedUrls,
5837
5844
  mappings
5838
5845
  };
5839
5846
  debugFs?.(`${c.yellow("[write]")} ${fetchResult.id} is cached in ${cachedFilePath}`);
@@ -5861,12 +5868,12 @@ class FileSystemModuleCache {
5861
5868
  else if (result === null) debugMemory?.(`${c.green("[read]")} ${id} was bailed out`);
5862
5869
  return result;
5863
5870
  }
5864
- generateCachePath(vitestConfig, environment, resolver, id, fileContent) {
5871
+ generateCachePath(vitestConfig, environment, id, fileContent) {
5865
5872
  // bail out if file has import.meta.glob because it depends on other files
5866
5873
  // TODO: figure out a way to still support it
5867
5874
  if (fileContent.includes("import.meta.glob(")) {
5868
5875
  this.saveMemoryCache(environment, id, null);
5869
- debugMemory?.(`${c.yellow("[write]")} ${id} was bailed out`);
5876
+ debugMemory?.(`${c.yellow("[write]")} ${id} was bailed out because it has "import.meta.glob"`);
5870
5877
  return null;
5871
5878
  }
5872
5879
  let hashString = "";
@@ -5895,7 +5902,7 @@ class FileSystemModuleCache {
5895
5902
  mode: config.mode,
5896
5903
  consumer: config.consumer,
5897
5904
  resolve: config.resolve,
5898
- plugins: config.plugins.map((p) => p.name),
5905
+ plugins: config.plugins.filter((p) => p.api?.vitest?.experimental?.ignoreFsModuleCache !== true).map((p) => p.name),
5899
5906
  configFileDependencies: config.configFileDependencies.map((file) => tryReadFileSync(file)),
5900
5907
  environment: environment.name,
5901
5908
  css: vitestConfig.css
@@ -5910,6 +5917,7 @@ class FileSystemModuleCache {
5910
5917
  let cacheRoot = this.fsCacheRoots.get(vitestConfig);
5911
5918
  if (cacheRoot == null) {
5912
5919
  cacheRoot = vitestConfig.experimental.fsModuleCachePath || this.rootCache;
5920
+ this.fsCacheRoots.set(vitestConfig, cacheRoot);
5913
5921
  if (!existsSync(cacheRoot)) mkdirSync(cacheRoot, { recursive: true });
5914
5922
  }
5915
5923
  const fsResultPath = join(cacheRoot, cacheKey);
@@ -6074,16 +6082,37 @@ function tryStatSync(file) {
6074
6082
  } catch {}
6075
6083
  }
6076
6084
 
6085
+ // this is copy pasted from vite
6086
+ function normalizeResolvedIdToUrl(environment, resolvedId) {
6087
+ const root = environment.config.root;
6088
+ const depsOptimizer = environment.depsOptimizer;
6089
+ let url;
6090
+ // normalize all imports into resolved URLs
6091
+ // e.g. `import 'foo'` -> `import '/@fs/.../node_modules/foo/index.js'`
6092
+ if (resolvedId.startsWith(withTrailingSlash(root)))
6093
+ // in root: infer short absolute path from root
6094
+ url = resolvedId.slice(root.length);
6095
+ else if (depsOptimizer?.isOptimizedDepFile(resolvedId) || resolvedId !== "/@react-refresh" && path.isAbsolute(resolvedId) && existsSync(cleanUrl(resolvedId)))
6096
+ // an optimized deps may not yet exists in the filesystem, or
6097
+ // a regular file exists but is out of root: rewrite to absolute /@fs/ paths
6098
+ url = path.posix.join("/@fs/", resolvedId);
6099
+ else url = resolvedId;
6100
+ // if the resolved id is not a valid browser import specifier,
6101
+ // prefix it to make it valid. We will strip this before feeding it
6102
+ // back into the transform pipeline
6103
+ if (url[0] !== "." && url[0] !== "/") url = wrapId(resolvedId);
6104
+ return url;
6105
+ }
6106
+
6077
6107
  const saveCachePromises = /* @__PURE__ */ new Map();
6078
6108
  const readFilePromises = /* @__PURE__ */ new Map();
6079
6109
  class ModuleFetcher {
6080
6110
  tmpDirectories = /* @__PURE__ */ new Set();
6081
6111
  fsCacheEnabled;
6082
- constructor(resolver, config, fsCache, traces, tmpProjectDir) {
6112
+ constructor(resolver, config, fsCache, tmpProjectDir) {
6083
6113
  this.resolver = resolver;
6084
6114
  this.config = config;
6085
6115
  this.fsCache = fsCache;
6086
- this.traces = traces;
6087
6116
  this.tmpProjectDir = tmpProjectDir;
6088
6117
  this.fsCacheEnabled = config.experimental?.fsModuleCache === true;
6089
6118
  }
@@ -6110,6 +6139,11 @@ class ModuleFetcher {
6110
6139
  type: "network"
6111
6140
  };
6112
6141
  }
6142
+ // handle unresolved id of dynamic import skipped by Vite import analysis
6143
+ if (url[0] !== "/") {
6144
+ const resolved = await environment.pluginContainer.resolveId(url, importer);
6145
+ if (resolved) url = normalizeResolvedIdToUrl(environment, resolved.id);
6146
+ }
6113
6147
  const moduleGraphModule = await environment.moduleGraph.ensureEntryFromUrl(unwrapId(url));
6114
6148
  const cached = !!moduleGraphModule.transformResult;
6115
6149
  if (moduleGraphModule.file) trace.setAttribute("code.file.path", moduleGraphModule.file);
@@ -6151,10 +6185,20 @@ class ModuleFetcher {
6151
6185
  }
6152
6186
  const result = await this.fetchAndProcess(environment, url, importer, moduleGraphModule, options);
6153
6187
  const importers = this.getSerializedDependencies(moduleGraphModule);
6188
+ const importedUrls = this.getSerializedImports(moduleGraphModule);
6154
6189
  const map = moduleGraphModule.transformResult?.map;
6155
6190
  const mappings = map && !("version" in map) && map.mappings === "";
6156
- return this.cacheResult(result, cachePath, importers, !!mappings);
6191
+ return this.cacheResult(result, cachePath, importers, importedUrls, !!mappings);
6157
6192
  }
6193
+ // we need this for UI to be able to show a module graph
6194
+ getSerializedImports(node) {
6195
+ const imports = [];
6196
+ node.importedModules.forEach((importer) => {
6197
+ imports.push(importer.url);
6198
+ });
6199
+ return imports;
6200
+ }
6201
+ // we need this for the watcher to be able to find the related test file
6158
6202
  getSerializedDependencies(node) {
6159
6203
  const dependencies = [];
6160
6204
  node.importers.forEach((importer) => {
@@ -6186,7 +6230,7 @@ class ModuleFetcher {
6186
6230
  // null means the file should not be cached
6187
6231
  if (memoryCacheKey !== void 0) return memoryCacheKey;
6188
6232
  const fileContent = await this.readFileContentToCache(environment, moduleGraphModule);
6189
- return this.fsCache.generateCachePath(this.config, environment, this.resolver, moduleGraphModule.id, fileContent);
6233
+ return this.fsCache.generateCachePath(this.config, environment, moduleGraphModule.id, fileContent);
6190
6234
  }
6191
6235
  async readFileContentToCache(environment, moduleGraphModule) {
6192
6236
  if (moduleGraphModule.file && !moduleGraphModule.file.startsWith("\0") && !moduleGraphModule.file.startsWith("virtual:")) {
@@ -6225,6 +6269,10 @@ class ModuleFetcher {
6225
6269
  const environmentNode = environment.moduleGraph.getModuleById(importer);
6226
6270
  if (environmentNode) moduleGraphModule.importers.add(environmentNode);
6227
6271
  });
6272
+ await Promise.all(cachedModule.importedUrls.map(async (url) => {
6273
+ const moduleNode = await environment.moduleGraph.ensureEntryFromUrl(url).catch(() => null);
6274
+ if (moduleNode) moduleGraphModule.importedModules.add(moduleNode);
6275
+ }));
6228
6276
  return {
6229
6277
  cached: true,
6230
6278
  file: cachedModule.file,
@@ -6240,13 +6288,13 @@ class ModuleFetcher {
6240
6288
  inlineSourceMap: false
6241
6289
  }).catch(handleRollupError));
6242
6290
  }
6243
- async cacheResult(result, cachePath, importers = [], mappings = false) {
6291
+ async cacheResult(result, cachePath, importers = [], importedUrls = [], mappings = false) {
6244
6292
  const returnResult = "code" in result ? getCachedResult(result, cachePath) : result;
6245
6293
  if (saveCachePromises.has(cachePath)) {
6246
6294
  await saveCachePromises.get(cachePath);
6247
6295
  return returnResult;
6248
6296
  }
6249
- const savePromise = this.fsCache.saveCachedModule(cachePath, result, importers, mappings).then(() => result).finally(() => {
6297
+ const savePromise = this.fsCache.saveCachedModule(cachePath, result, importers, importedUrls, mappings).then(() => result).finally(() => {
6250
6298
  saveCachePromises.delete(cachePath);
6251
6299
  });
6252
6300
  saveCachePromises.set(cachePath, savePromise);
@@ -6265,7 +6313,7 @@ class ModuleFetcher {
6265
6313
  }
6266
6314
  }
6267
6315
  function createFetchModuleFunction(resolver, config, fsCache, traces, tmpProjectDir) {
6268
- const fetcher = new ModuleFetcher(resolver, config, fsCache, traces, tmpProjectDir);
6316
+ const fetcher = new ModuleFetcher(resolver, config, fsCache, tmpProjectDir);
6269
6317
  return async (url, importer, environment, cacheFs, options, otelCarrier) => {
6270
6318
  await traces.waitInit();
6271
6319
  const context = otelCarrier ? traces.getContextFromCarrier(otelCarrier) : void 0;
@@ -6353,28 +6401,6 @@ function handleRollupError(e) {
6353
6401
  throw e;
6354
6402
  }
6355
6403
 
6356
- // this is copy pasted from vite
6357
- function normalizeResolvedIdToUrl(environment, resolvedId) {
6358
- const root = environment.config.root;
6359
- const depsOptimizer = environment.depsOptimizer;
6360
- let url;
6361
- // normalize all imports into resolved URLs
6362
- // e.g. `import 'foo'` -> `import '/@fs/.../node_modules/foo/index.js'`
6363
- if (resolvedId.startsWith(withTrailingSlash(root)))
6364
- // in root: infer short absolute path from root
6365
- url = resolvedId.slice(root.length);
6366
- else if (depsOptimizer?.isOptimizedDepFile(resolvedId) || resolvedId !== "/@react-refresh" && path.isAbsolute(resolvedId) && existsSync(cleanUrl(resolvedId)))
6367
- // an optimized deps may not yet exists in the filesystem, or
6368
- // a regular file exists but is out of root: rewrite to absolute /@fs/ paths
6369
- url = path.posix.join("/@fs/", resolvedId);
6370
- else url = resolvedId;
6371
- // if the resolved id is not a valid browser import specifier,
6372
- // prefix it to make it valid. We will strip this before feeding it
6373
- // back into the transform pipeline
6374
- if (url[0] !== "." && url[0] !== "/") url = wrapId(resolvedId);
6375
- return url;
6376
- }
6377
-
6378
6404
  class ServerModuleRunner extends ModuleRunner {
6379
6405
  constructor(environment, fetcher, config) {
6380
6406
  super({
@@ -6664,6 +6690,238 @@ This might cause false positive tests. Resolve unhandled errors to make sure you
6664
6690
  }
6665
6691
  }
6666
6692
 
6693
+ // this function recieves the module diagnostic with the location of imports
6694
+ // and populates it with collected import durations; the duration is injected
6695
+ // only if the current module is the one that imported the module
6696
+ // if testModule is not defined, then Vitest aggregates durations of ALL collected test modules
6697
+ function collectModuleDurationsDiagnostic(moduleId, state, moduleDiagnostic, testModule) {
6698
+ if (!moduleDiagnostic) return {
6699
+ modules: [],
6700
+ untrackedModules: []
6701
+ };
6702
+ const modules = [];
6703
+ const modulesById = {};
6704
+ const allModules = [...moduleDiagnostic.modules, ...moduleDiagnostic.untracked];
6705
+ const visitedByFiles = {};
6706
+ // this aggregates the times for _ALL_ tests if testModule is not passed
6707
+ // so if the module was imported in separate tests, the time will be accumulated
6708
+ for (const files of testModule ? [[testModule.task]] : state.filesMap.values()) for (const file of files) {
6709
+ const importDurations = file.importDurations;
6710
+ if (!importDurations) continue;
6711
+ const currentModule = state.getReportedEntity(file);
6712
+ if (!currentModule) continue;
6713
+ const visitedKey = currentModule.project.config.isolate === false ? "non-isolate" : file.id;
6714
+ if (!visitedByFiles[visitedKey]) visitedByFiles[visitedKey] = /* @__PURE__ */ new Set();
6715
+ const visited = visitedByFiles[visitedKey];
6716
+ allModules.forEach(({ resolvedId, resolvedUrl }) => {
6717
+ const durations = importDurations[resolvedId];
6718
+ // do not accumulate if module was already visited by suite (or suites in non-isolate mode)
6719
+ if (!durations || visited.has(resolvedId)) return;
6720
+ const importer = getModuleImporter(moduleId, durations, currentModule);
6721
+ modulesById[resolvedId] ??= {
6722
+ selfTime: 0,
6723
+ totalTime: 0,
6724
+ transformTime: 0,
6725
+ external: durations.external,
6726
+ importer
6727
+ };
6728
+ // only track if the current module imported this module,
6729
+ // otherwise it was imported instantly because it's cached
6730
+ if (importer === moduleId) {
6731
+ visited.add(resolvedId);
6732
+ modulesById[resolvedId].selfTime += durations.selfTime;
6733
+ modulesById[resolvedId].totalTime += durations.totalTime;
6734
+ // don't aggregate
6735
+ modulesById[resolvedId].transformTime = state.metadata[currentModule.project.name]?.duration[resolvedUrl]?.[0];
6736
+ }
6737
+ });
6738
+ }
6739
+ // if module was imported twice in the same file,
6740
+ // show only one time - the second should be shown as 0
6741
+ const visitedInFile = /* @__PURE__ */ new Set();
6742
+ moduleDiagnostic.modules.forEach((diagnostic) => {
6743
+ const durations = modulesById[diagnostic.resolvedId];
6744
+ if (!durations) return;
6745
+ if (visitedInFile.has(diagnostic.resolvedId)) modules.push({
6746
+ ...diagnostic,
6747
+ selfTime: 0,
6748
+ totalTime: 0,
6749
+ transformTime: 0,
6750
+ external: durations.external,
6751
+ importer: durations.importer
6752
+ });
6753
+ else {
6754
+ visitedInFile.add(diagnostic.resolvedId);
6755
+ modules.push({
6756
+ ...diagnostic,
6757
+ ...durations
6758
+ });
6759
+ }
6760
+ });
6761
+ const untracked = [];
6762
+ moduleDiagnostic.untracked.forEach((diagnostic) => {
6763
+ const durations = modulesById[diagnostic.resolvedId];
6764
+ if (!durations) return;
6765
+ if (visitedInFile.has(diagnostic.resolvedId)) untracked.push({
6766
+ selfTime: 0,
6767
+ totalTime: 0,
6768
+ transformTime: 0,
6769
+ external: durations.external,
6770
+ importer: durations.importer,
6771
+ resolvedId: diagnostic.resolvedId,
6772
+ resolvedUrl: diagnostic.resolvedUrl,
6773
+ url: diagnostic.rawUrl
6774
+ });
6775
+ else {
6776
+ visitedInFile.add(diagnostic.resolvedId);
6777
+ untracked.push({
6778
+ ...durations,
6779
+ resolvedId: diagnostic.resolvedId,
6780
+ resolvedUrl: diagnostic.resolvedUrl,
6781
+ url: diagnostic.rawUrl
6782
+ });
6783
+ }
6784
+ });
6785
+ return {
6786
+ modules,
6787
+ untrackedModules: untracked
6788
+ };
6789
+ }
6790
+ function getModuleImporter(moduleId, durations, testModule) {
6791
+ if (durations.importer === moduleId) return moduleId;
6792
+ if (!durations.importer) {
6793
+ if (moduleId === testModule.moduleId) return testModule.moduleId;
6794
+ return testModule.project.config.setupFiles.includes(moduleId) ? moduleId : durations.importer;
6795
+ }
6796
+ return durations.importer;
6797
+ }
6798
+ // the idea of this is very simple
6799
+ // it parses the source code to extract import/export statements
6800
+ // it parses SSR transformed file to extract __vite_ssr_import__ and __vite_ssr_dynamic_import__
6801
+ // it combines the two by looking at the original positions of SSR primitives
6802
+ // in the end, we are able to return a list of modules that were imported by this module
6803
+ // mapped to their IDs in Vite's module graph
6804
+ async function collectSourceModulesLocations(moduleId, moduleGraph) {
6805
+ const transformResult = moduleGraph.getModuleById(moduleId)?.transformResult;
6806
+ if (!transformResult || !transformResult.ssr) return;
6807
+ const map = transformResult.map;
6808
+ if (!map || !("version" in map) || !map.sources.length) return;
6809
+ const sourceImports = map.sources.reduce((acc, sourceId, index) => {
6810
+ const source = map.sourcesContent?.[index];
6811
+ if (source != null) acc[sourceId] = parseSourceImportsAndExports(source);
6812
+ return acc;
6813
+ }, {});
6814
+ const transformImports = await parseTransformResult(moduleGraph, transformResult);
6815
+ const traceMap = map && "version" in map && new TraceMap(map);
6816
+ const modules = {};
6817
+ const untracked = [];
6818
+ transformImports.forEach((row) => {
6819
+ const original = traceMap && originalPositionFor(traceMap, row.start);
6820
+ if (original && original.source != null) {
6821
+ // if there are several at the same position, this is a bug
6822
+ // probably caused by import.meta.glob imports returning incorrect positions
6823
+ // all the new import.meta.glob imports come first, so only the last module on this line is correct
6824
+ const sourceImport = sourceImports[original.source].get(`${original.line}:${original.column}`);
6825
+ if (sourceImport) {
6826
+ if (modules[sourceImport.rawUrl]) {
6827
+ // remove imports with a different resolvedId
6828
+ const differentImports = modules[sourceImport.rawUrl].filter((d) => d.resolvedId !== row.resolvedId);
6829
+ untracked.push(...differentImports);
6830
+ modules[sourceImport.rawUrl] = modules[sourceImport.rawUrl].filter((d) => d.resolvedId === row.resolvedId);
6831
+ }
6832
+ modules[sourceImport.rawUrl] ??= [];
6833
+ modules[sourceImport.rawUrl].push({
6834
+ start: sourceImport.start,
6835
+ end: sourceImport.end,
6836
+ startIndex: sourceImport.startIndex,
6837
+ endIndex: sourceImport.endIndex,
6838
+ rawUrl: sourceImport.rawUrl,
6839
+ resolvedId: row.resolvedId,
6840
+ resolvedUrl: row.resolvedUrl
6841
+ });
6842
+ }
6843
+ }
6844
+ });
6845
+ return {
6846
+ modules: Object.values(modules).flat(),
6847
+ untracked
6848
+ };
6849
+ }
6850
+ function fillSourcesMap(syntax, sourcesMap, source, indexMap) {
6851
+ const splitSeparator = `${syntax} `;
6852
+ const splitSources = source.split(splitSeparator);
6853
+ const chunks = [];
6854
+ let index = 0;
6855
+ for (const chunk of splitSources) {
6856
+ chunks.push({
6857
+ chunk,
6858
+ startIndex: index
6859
+ });
6860
+ index += chunk.length + splitSeparator.length;
6861
+ }
6862
+ chunks.forEach(({ chunk, startIndex }) => {
6863
+ const normalized = chunk.replace(/'/g, "\"");
6864
+ const startQuoteIdx = normalized.indexOf("\"");
6865
+ if (startQuoteIdx === -1) return;
6866
+ const endQuoteIdx = normalized.indexOf("\"", startQuoteIdx + 1);
6867
+ if (endQuoteIdx === -1) return;
6868
+ const staticSyntax = {
6869
+ startIndex: startIndex + startQuoteIdx,
6870
+ endIndex: startIndex + endQuoteIdx + 1,
6871
+ start: indexMap.get(startIndex + startQuoteIdx),
6872
+ end: indexMap.get(startIndex + endQuoteIdx + 1),
6873
+ rawUrl: normalized.slice(startQuoteIdx + 1, endQuoteIdx)
6874
+ };
6875
+ // -7 to include "import "
6876
+ for (let i = startIndex - 7; i < staticSyntax.endIndex; i++) {
6877
+ const location = indexMap.get(i);
6878
+ if (location) sourcesMap.set(`${location.line}:${location.column}`, staticSyntax);
6879
+ }
6880
+ });
6881
+ }
6882
+ // this function tries to parse ESM static import and export statements from
6883
+ // the source. if the source is not JS/TS, but supports static ESM syntax,
6884
+ // then this will also find them because it' only checks the strings, it doesn't parse the AST
6885
+ function parseSourceImportsAndExports(source) {
6886
+ if (!source.includes("import ") && !source.includes("export ")) return /* @__PURE__ */ new Map();
6887
+ const sourcesMap = /* @__PURE__ */ new Map();
6888
+ const indexMap = createIndexLocationsMap(source);
6889
+ fillSourcesMap("import", sourcesMap, source, indexMap);
6890
+ fillSourcesMap("export", sourcesMap, source, indexMap);
6891
+ return sourcesMap;
6892
+ }
6893
+ async function parseTransformResult(moduleGraph, transformResult) {
6894
+ const code = transformResult.code;
6895
+ const regexp = /(?:__vite_ssr_import__|__vite_ssr_dynamic_import__)\("([^"]+)"/g;
6896
+ const lineColumnMap = createIndexLocationsMap(code);
6897
+ const importPositions = [];
6898
+ let match;
6899
+ // eslint-disable-next-line no-cond-assign
6900
+ while (match = regexp.exec(code)) {
6901
+ const startIndex = match.index;
6902
+ const endIndex = match.index + match[0].length - 1;
6903
+ importPositions.push({
6904
+ raw: match[1],
6905
+ startIndex,
6906
+ endIndex
6907
+ });
6908
+ }
6909
+ return (await Promise.all(importPositions.map(async ({ startIndex, endIndex, raw }) => {
6910
+ const position = lineColumnMap.get(startIndex);
6911
+ const endPosition = lineColumnMap.get(endIndex);
6912
+ const moduleNode = await moduleGraph.getModuleByUrl(raw);
6913
+ if (!position || !endPosition || !moduleNode || !moduleNode.id) return;
6914
+ return {
6915
+ resolvedId: moduleNode.id,
6916
+ resolvedUrl: moduleNode.url,
6917
+ start: position,
6918
+ end: endPosition,
6919
+ startIndex,
6920
+ endIndex
6921
+ };
6922
+ }))).filter((n) => n != null);
6923
+ }
6924
+
6667
6925
  const __dirname$1 = url.fileURLToPath(new URL(".", import.meta.url));
6668
6926
  class VitestPackageInstaller {
6669
6927
  isPackageExists(name, options) {
@@ -7042,7 +7300,7 @@ function createMethodsRPC(project, methodsOptions = {}) {
7042
7300
  const file = cleanUrl(resolved.id);
7043
7301
  if (resolved.external) return {
7044
7302
  file,
7045
- url: !resolved.id.startsWith("node:") && isBuiltin(resolved.id) ? `node:${resolved.id}` : resolved.id,
7303
+ url: isBuiltin(resolved.id) ? toBuiltin(resolved.id) : resolved.id,
7046
7304
  id: resolved.id
7047
7305
  };
7048
7306
  return {
@@ -7246,7 +7504,7 @@ class PoolRunner {
7246
7504
  this._operationLock = null;
7247
7505
  }
7248
7506
  }
7249
- async stop() {
7507
+ async stop(options) {
7250
7508
  // Wait for any ongoing operation to complete
7251
7509
  if (this._operationLock) await this._operationLock;
7252
7510
  if (this._state === RunnerState.STOPPED || this._state === RunnerState.STOPPING) return;
@@ -7274,6 +7532,11 @@ class PoolRunner {
7274
7532
  this.off("message", onStop);
7275
7533
  }
7276
7534
  };
7535
+ // Don't wait for graceful exit's response when force exiting
7536
+ if (options?.force) return onStop({
7537
+ type: "stopped",
7538
+ __vitest_worker_response__: true
7539
+ });
7277
7540
  this.on("message", onStop);
7278
7541
  this.postMessage({
7279
7542
  type: "stop",
@@ -7733,7 +7996,8 @@ class Pool {
7733
7996
  };
7734
7997
  this.activeTasks.push(activeTask);
7735
7998
  // active tasks receive cancel signal and shut down gracefully
7736
- async function cancelTask() {
7999
+ async function cancelTask(options) {
8000
+ if (options?.force) await runner.stop({ force: true });
7737
8001
  await runner.waitForTerminated();
7738
8002
  resolver.reject(/* @__PURE__ */ new Error("Cancelled"));
7739
8003
  }
@@ -7783,6 +8047,9 @@ catch (error) {
7783
8047
  return this.schedule();
7784
8048
  }
7785
8049
  async cancel() {
8050
+ // Force exit if previous cancel is still on-going
8051
+ // for example when user does 'CTRL+c' twice in row
8052
+ const force = this._isCancelling;
7786
8053
  // Set flag to prevent new tasks from being queued
7787
8054
  this._isCancelling = true;
7788
8055
  const pendingTasks = this.queue.splice(0);
@@ -7790,11 +8057,12 @@ catch (error) {
7790
8057
  const error = /* @__PURE__ */ new Error("Cancelled");
7791
8058
  pendingTasks.forEach((task) => task.resolver.reject(error));
7792
8059
  }
7793
- const activeTasks = this.activeTasks.splice(0);
7794
- await Promise.all(activeTasks.map((task) => task.cancelTask()));
7795
- const sharedRunners = this.sharedRunners.splice(0);
7796
- await Promise.all(sharedRunners.map((runner) => runner.stop()));
7797
- await Promise.all(this.exitPromises.splice(0));
8060
+ await Promise.all(this.activeTasks.map((task) => task.cancelTask({ force })));
8061
+ this.activeTasks = [];
8062
+ await Promise.all(this.sharedRunners.map((runner) => runner.stop()));
8063
+ this.sharedRunners = [];
8064
+ await Promise.all(this.exitPromises);
8065
+ this.exitPromises = [];
7798
8066
  this.workerIds.forEach((_, id) => this.freeWorkerId(id));
7799
8067
  // Reset flag after cancellation completes
7800
8068
  this._isCancelling = false;
@@ -8198,7 +8466,10 @@ function serializeConfig(project) {
8198
8466
  printConsoleTrace: config.printConsoleTrace ?? globalConfig.printConsoleTrace,
8199
8467
  benchmark: config.benchmark && { includeSamples: config.benchmark.includeSamples },
8200
8468
  serializedDefines: config.browser.enabled ? "" : project._serializedDefines || "",
8201
- experimental: { fsModuleCache: config.experimental.fsModuleCache ?? false }
8469
+ experimental: {
8470
+ fsModuleCache: config.experimental.fsModuleCache ?? false,
8471
+ printImportBreakdown: config.experimental.printImportBreakdown
8472
+ }
8202
8473
  };
8203
8474
  }
8204
8475
 
@@ -9118,7 +9389,10 @@ function ModuleRunnerTransform() {
9118
9389
  environment.resolve.noExternal = true;
9119
9390
  // Workaround `noExternal` merging bug on Vite 6
9120
9391
  // https://github.com/vitejs/vite/pull/20502
9121
- if (name === "ssr") delete config.ssr?.noExternal;
9392
+ if (name === "ssr") {
9393
+ delete config.ssr?.noExternal;
9394
+ delete config.ssr?.external;
9395
+ }
9122
9396
  if (name === "__vitest_vm__" || name === "__vitest__") continue;
9123
9397
  const currentOptimizeDeps = environment.optimizeDeps || (name === "client" ? config.optimizeDeps : name === "ssr" ? config.ssr?.optimizeDeps : void 0);
9124
9398
  const optimizeDeps = resolveOptimizerConfig(testConfig.deps?.optimizer?.[name], currentOptimizeDeps);
@@ -9348,6 +9622,7 @@ function WorkspaceVitestPlugin(project, options) {
9348
9622
 
9349
9623
  class VitestResolver {
9350
9624
  options;
9625
+ externalizeConcurrentCache = /* @__PURE__ */ new Map();
9351
9626
  externalizeCache = /* @__PURE__ */ new Map();
9352
9627
  constructor(cacheDir, config) {
9353
9628
  // sorting to make cache consistent
@@ -9367,8 +9642,20 @@ class VitestResolver {
9367
9642
  external
9368
9643
  };
9369
9644
  }
9370
- shouldExternalize(file) {
9371
- return shouldExternalize(normalizeId(file), this.options, this.externalizeCache);
9645
+ wasExternalized(file) {
9646
+ const normalizedFile = normalizeId(file);
9647
+ if (!this.externalizeCache.has(normalizedFile)) return false;
9648
+ return this.externalizeCache.get(normalizedFile) ?? false;
9649
+ }
9650
+ async shouldExternalize(file) {
9651
+ const normalizedFile = normalizeId(file);
9652
+ if (this.externalizeCache.has(normalizedFile)) return this.externalizeCache.get(normalizedFile);
9653
+ return shouldExternalize(normalizeId(file), this.options, this.externalizeConcurrentCache).then((result) => {
9654
+ this.externalizeCache.set(normalizedFile, result);
9655
+ return result;
9656
+ }).finally(() => {
9657
+ this.externalizeConcurrentCache.delete(normalizedFile);
9658
+ });
9372
9659
  }
9373
9660
  }
9374
9661
  function normalizeId(id) {
@@ -9413,6 +9700,9 @@ function guessCJSversion(id) {
9413
9700
  }
9414
9701
  // The code from https://github.com/unjs/mlly/blob/c5bcca0cda175921344fd6de1bc0c499e73e5dac/src/syntax.ts#L51-L98
9415
9702
  async function isValidNodeImport(id) {
9703
+ // clean url to strip off `?v=...` query etc.
9704
+ // node can natively import files with query params, so externalizing them is safe.
9705
+ id = cleanUrl(id);
9416
9706
  const extension = extname(id);
9417
9707
  if (BUILTIN_EXTENSIONS.has(extension)) return true;
9418
9708
  if (extension !== ".js") return false;
@@ -9433,7 +9723,7 @@ async function shouldExternalize(id, options, cache) {
9433
9723
  return cache.get(id);
9434
9724
  }
9435
9725
  async function _shouldExternalize(id, options) {
9436
- if (isBuiltin(id)) return id;
9726
+ if (isBuiltin$1(id)) return id;
9437
9727
  // data: should be processed by native import,
9438
9728
  // since it is a feature of ESM.
9439
9729
  // also externalize network imports since nodejs allows it when --experimental-network-imports
@@ -9450,7 +9740,6 @@ async function _shouldExternalize(id, options) {
9450
9740
  if (matchPattern(id, moduleDirectories, defaultInline)) return false;
9451
9741
  if (matchPattern(id, moduleDirectories, depsExternal)) return id;
9452
9742
  if (isLibraryModule && await isValidNodeImport(id)) return id;
9453
- return false;
9454
9743
  }
9455
9744
  function matchPattern(id, moduleDirectories, patterns) {
9456
9745
  if (patterns == null) return false;
@@ -9527,6 +9816,21 @@ async function createViteServer(inlineConfig) {
9527
9816
  console.error = error;
9528
9817
  return server;
9529
9818
  }
9819
+ function isFileServingAllowed(configOrUrl, urlOrServer) {
9820
+ const config = typeof urlOrServer === "string" ? configOrUrl : urlOrServer.config;
9821
+ const url = typeof urlOrServer === "string" ? urlOrServer : configOrUrl;
9822
+ if (!config.server.fs.strict) return true;
9823
+ return isFileLoadingAllowed(config, fsPathFromUrl(url));
9824
+ }
9825
+ const FS_PREFIX = "/@fs/";
9826
+ const VOLUME_RE = /^[A-Z]:/i;
9827
+ function fsPathFromId(id) {
9828
+ const fsPath = normalizePath(id.startsWith(FS_PREFIX) ? id.slice(5) : id);
9829
+ return fsPath[0] === "/" || VOLUME_RE.test(fsPath) ? fsPath : `/${fsPath}`;
9830
+ }
9831
+ function fsPathFromUrl(url) {
9832
+ return fsPathFromId(cleanUrl(url));
9833
+ }
9530
9834
 
9531
9835
  class TestProject {
9532
9836
  /**
@@ -10681,6 +10985,12 @@ class TestSuite extends SuiteImplementation {
10681
10985
  class TestModule extends SuiteImplementation {
10682
10986
  type = "module";
10683
10987
  /**
10988
+ * The Vite environment that processes files on the server.
10989
+ *
10990
+ * Can be empty if test module did not run yet.
10991
+ */
10992
+ viteEnvironment;
10993
+ /**
10684
10994
  * This is usually an absolute UNIX file path.
10685
10995
  * It can be a virtual ID if the file is not on the disk.
10686
10996
  * This value corresponds to the ID in the Vite's module graph.
@@ -10695,6 +11005,8 @@ class TestModule extends SuiteImplementation {
10695
11005
  super(task, project);
10696
11006
  this.moduleId = task.filepath;
10697
11007
  this.relativeModuleId = task.name;
11008
+ if (task.viteEnvironment === "__browser__") this.viteEnvironment = project.browser?.vite.environments.client;
11009
+ else if (typeof task.viteEnvironment === "string") this.viteEnvironment = project.vite.environments[task.viteEnvironment];
10698
11010
  }
10699
11011
  /**
10700
11012
  * Checks the running state of the test file.
@@ -12247,6 +12559,39 @@ class Vitest {
12247
12559
  return await this.runningPromise;
12248
12560
  });
12249
12561
  }
12562
+ /**
12563
+ * Returns module's diagnostic. If `testModule` is not provided, `selfTime` and `totalTime` will be aggregated across all tests.
12564
+ *
12565
+ * If the module was not transformed or executed, the diagnostic will be empty.
12566
+ * @experimental
12567
+ * @see {@link https://vitest.dev/api/advanced/vitest#getsourcemodulediagnostic}
12568
+ */
12569
+ async experimental_getSourceModuleDiagnostic(moduleId, testModule) {
12570
+ if (testModule) {
12571
+ const viteEnvironment = testModule.viteEnvironment;
12572
+ // if there is no viteEnvironment, it means the file did not run yet
12573
+ if (!viteEnvironment) return {
12574
+ modules: [],
12575
+ untrackedModules: []
12576
+ };
12577
+ const moduleLocations = await collectSourceModulesLocations(moduleId, viteEnvironment.moduleGraph);
12578
+ return collectModuleDurationsDiagnostic(moduleId, this.state, moduleLocations, testModule);
12579
+ }
12580
+ const environments = this.projects.flatMap((p) => {
12581
+ return Object.values(p.vite.environments);
12582
+ });
12583
+ const aggregatedLocationsResult = await Promise.all(environments.map((environment) => collectSourceModulesLocations(moduleId, environment.moduleGraph)));
12584
+ return collectModuleDurationsDiagnostic(moduleId, this.state, aggregatedLocationsResult.reduce((acc, locations) => {
12585
+ if (locations) {
12586
+ acc.modules.push(...locations.modules);
12587
+ acc.untracked.push(...locations.untracked);
12588
+ }
12589
+ return acc;
12590
+ }, {
12591
+ modules: [],
12592
+ untracked: []
12593
+ }));
12594
+ }
12250
12595
  async experimental_parseSpecifications(specifications, options) {
12251
12596
  if (this.mode !== "test") throw new Error(`The \`experimental_parseSpecifications\` does not support "${this.mode}" mode.`);
12252
12597
  const limit = limitConcurrency(options?.concurrency ?? (typeof nodeos__default.availableParallelism === "function" ? nodeos__default.availableParallelism() : nodeos__default.cpus().length));
@@ -13242,4 +13587,4 @@ var cliApi = /*#__PURE__*/Object.freeze({
13242
13587
  startVitest: startVitest
13243
13588
  });
13244
13589
 
13245
- export { FilesNotFoundError as F, GitNotFoundError as G, ThreadsPoolWorker as T, Vitest as V, VitestPlugin as a, VitestPackageInstaller as b, createVitest as c, createMethodsRPC as d, escapeTestName as e, ForksPoolWorker as f, getFilePoolName as g, TypecheckPoolWorker as h, isValidApiRequest as i, VmForksPoolWorker as j, VmThreadsPoolWorker as k, experimental_getRunnerTask as l, registerConsoleShortcuts as m, createViteLogger as n, createDebugger as o, cliApi as p, resolveFsAllow as r, startVitest as s };
13590
+ export { FilesNotFoundError as F, GitNotFoundError as G, ThreadsPoolWorker as T, Vitest as V, VitestPlugin as a, VitestPackageInstaller as b, createVitest as c, createMethodsRPC as d, escapeTestName as e, ForksPoolWorker as f, getFilePoolName as g, TypecheckPoolWorker as h, isValidApiRequest as i, VmForksPoolWorker as j, VmThreadsPoolWorker as k, experimental_getRunnerTask as l, registerConsoleShortcuts as m, isFileServingAllowed as n, createViteLogger as o, createDebugger as p, cliApi as q, resolveFsAllow as r, startVitest as s };