vitest 4.0.0-beta.4 → 4.0.0-beta.5

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 (71) hide show
  1. package/dist/browser.d.ts +8 -9
  2. package/dist/browser.js +3 -2
  3. package/dist/chunks/base.DMfOuRWD.js +48 -0
  4. package/dist/chunks/{benchmark.CYdenmiT.js → benchmark.CtuRzf-i.js} +4 -1
  5. package/dist/chunks/{browser.d.BRP8scJf.d.ts → browser.d.Cawq_X_N.d.ts} +1 -1
  6. package/dist/chunks/{cac.CY0IAxC4.js → cac.CKnbxhn2.js} +8 -9
  7. package/dist/chunks/{cli-api.B8xRY9Zt.js → cli-api.COn58yrl.js} +540 -342
  8. package/dist/chunks/{config.d.DZo8c7fw.d.ts → config.d.CKNVOKm0.d.ts} +3 -8
  9. package/dist/chunks/{console.DoJHFxmj.js → console.Duv2dVIC.js} +1 -1
  10. package/dist/chunks/{constants.CXzqaLmq.js → constants.D_Q9UYh-.js} +1 -6
  11. package/dist/chunks/{coverage.C84l9G-M.js → coverage.B6cReEn1.js} +232 -133
  12. package/dist/chunks/{coverage.DVF1vEu8.js → coverage.D_JHT54q.js} +2 -2
  13. package/dist/chunks/{coverage.d.CNYjU4GF.d.ts → coverage.d.BZtK59WP.d.ts} +7 -5
  14. package/dist/chunks/{creator.yfA2ExGt.js → creator.DUVZ6rfm.js} +1 -1
  15. package/dist/chunks/{environment.d.Bhm9oc0v.d.ts → environment.d.2fYMoz3o.d.ts} +26 -4
  16. package/dist/chunks/{global.d.DAhT2emn.d.ts → global.d.K6uBQHzY.d.ts} +1 -1
  17. package/dist/chunks/{globals.Dgo-vS5G.js → globals.CJQ63oO0.js} +6 -5
  18. package/dist/chunks/{index.CmSc2RE5.js → index.BRtIe7r8.js} +4 -4
  19. package/dist/chunks/{index.Bz6b0Ib7.js → index.DQhAfQQU.js} +12 -4
  20. package/dist/chunks/{index.D3SKT3tv.js → index.DgN0Zk9a.js} +1 -1
  21. package/dist/chunks/{index.D1_MsKEt.js → index.QZr3S3vQ.js} +4 -2
  22. package/dist/chunks/{index.CtUvr1c8.js → index.oWRWx-nj.js} +18 -18
  23. package/dist/chunks/moduleRunner.d.mmOmOGrW.d.ts +202 -0
  24. package/dist/chunks/moduleTransport.I-bgQy0S.js +19 -0
  25. package/dist/chunks/{node.fjCdwEIl.js → node.4JV5OXkt.js} +1 -1
  26. package/dist/chunks/{plugin.d.CLhMcYdD.d.ts → plugin.d.CvOlgjxK.d.ts} +1 -1
  27. package/dist/chunks/{reporters.d.DWg40D2B.d.ts → reporters.d.CYE9sT5z.d.ts} +37 -69
  28. package/dist/chunks/resolver.D5bG4zy5.js +162 -0
  29. package/dist/chunks/{rpc.jnQO9F8a.js → rpc.DGoW_Vl-.js} +1 -1
  30. package/dist/chunks/{runBaseTests.DBVVLMSb.js → runBaseTests.B3KcKqlF.js} +22 -23
  31. package/dist/chunks/{setup-common.Ebx5x0eP.js → setup-common.lgPs-bYv.js} +11 -10
  32. package/dist/chunks/{execute.Dt-pCVcL.js → startModuleRunner.C8FtT_BY.js} +381 -312
  33. package/dist/chunks/{typechecker.CMNPqJOo.js → typechecker.BgoW4nTA.js} +1 -1
  34. package/dist/chunks/{utils.XdZDrNZV.js → utils.B9FY3b73.js} +7 -8
  35. package/dist/chunks/{vi.CA0EPI9Y.js → vi.DGAfBY4R.js} +12 -8
  36. package/dist/chunks/{vm.BUnLJt_P.js → vm.BKfKvaKl.js} +36 -56
  37. package/dist/chunks/{worker.d.zjyR34Pb.d.ts → worker.d.D9QWnzAe.d.ts} +16 -13
  38. package/dist/chunks/{worker.d.C-1AbnVe.d.ts → worker.d.Db-UVmXc.d.ts} +1 -1
  39. package/dist/cli.js +4 -4
  40. package/dist/config.cjs +3 -9
  41. package/dist/config.d.ts +10 -12
  42. package/dist/config.js +1 -1
  43. package/dist/coverage.d.ts +10 -11
  44. package/dist/coverage.js +5 -6
  45. package/dist/environments.d.ts +2 -2
  46. package/dist/environments.js +1 -1
  47. package/dist/index.d.ts +10 -9
  48. package/dist/index.js +5 -4
  49. package/dist/module-evaluator.d.ts +12 -0
  50. package/dist/module-evaluator.js +327 -0
  51. package/dist/module-runner.js +15 -0
  52. package/dist/node.d.ts +12 -13
  53. package/dist/node.js +15 -14
  54. package/dist/reporters.d.ts +7 -8
  55. package/dist/reporters.js +3 -3
  56. package/dist/runners.d.ts +3 -3
  57. package/dist/runners.js +16 -10
  58. package/dist/snapshot.js +2 -2
  59. package/dist/suite.js +2 -2
  60. package/dist/worker.js +84 -31
  61. package/dist/workers/forks.js +10 -9
  62. package/dist/workers/runVmTests.js +16 -17
  63. package/dist/workers/threads.js +10 -9
  64. package/dist/workers/vmForks.js +9 -8
  65. package/dist/workers/vmThreads.js +9 -8
  66. package/dist/workers.d.ts +5 -4
  67. package/dist/workers.js +14 -13
  68. package/package.json +21 -16
  69. package/dist/chunks/base.BaCDDRPG.js +0 -38
  70. package/dist/execute.d.ts +0 -148
  71. package/dist/execute.js +0 -13
@@ -1,15 +1,86 @@
1
1
  import fs from 'node:fs';
2
+ import { builtinModules, isBuiltin } from 'node:module';
3
+ import { highlight, isBareImport } from '@vitest/utils';
2
4
  import { pathToFileURL } from 'node:url';
3
- import vm from 'node:vm';
4
- import { processError } from '@vitest/utils/error';
5
- import { normalize as normalize$1 } from 'pathe';
6
- import { ViteNodeRunner, DEFAULT_REQUEST_STUBS } from 'vite-node/client';
7
- import { isInternalRequest, isNodeBuiltin as isNodeBuiltin$1, isPrimitive, toFilePath } from 'vite-node/utils';
5
+ import { normalize as normalize$1, join as join$1 } from 'pathe';
8
6
  import { distDir } from '../path.js';
7
+ import { processError } from '@vitest/utils/error';
8
+ import { VitestModuleEvaluator, unwrapId } from '../module-evaluator.js';
9
9
  import { resolve as resolve$1, isAbsolute as isAbsolute$1 } from 'node:path';
10
+ import vm from 'node:vm';
10
11
  import { MockerRegistry, mockObject, RedirectedModule, AutomockedModule } from '@vitest/mocker';
11
- import { builtinModules } from 'node:module';
12
- import { highlight } from '@vitest/utils';
12
+ import { ModuleRunner } from 'vite/module-runner';
13
+ import { V as VitestTransport } from './moduleTransport.I-bgQy0S.js';
14
+
15
+ const bareVitestRegexp = /^@?vitest(?:\/|$)/;
16
+ const normalizedDistDir = normalize$1(distDir);
17
+ const relativeIds = {};
18
+ const externalizeMap = /* @__PURE__ */ new Map();
19
+ // all Vitest imports always need to be externalized
20
+ function getCachedVitestImport(id, state) {
21
+ if (id.startsWith("/@fs/") || id.startsWith("\\@fs\\")) id = id.slice(process.platform === "win32" ? 5 : 4);
22
+ if (externalizeMap.has(id)) return {
23
+ externalize: externalizeMap.get(id),
24
+ type: "module"
25
+ };
26
+ // always externalize Vitest because we import from there before running tests
27
+ // so we already have it cached by Node.js
28
+ const root = state().config.root;
29
+ const relativeRoot = relativeIds[root] ?? (relativeIds[root] = normalizedDistDir.slice(root.length));
30
+ if (id.includes(distDir) || id.includes(normalizedDistDir)) {
31
+ const externalize = id.startsWith("file://") ? id : pathToFileURL(id).toString();
32
+ externalizeMap.set(id, externalize);
33
+ return {
34
+ externalize,
35
+ type: "module"
36
+ };
37
+ }
38
+ if (relativeRoot && relativeRoot !== "/" && id.startsWith(relativeRoot)) {
39
+ const path = join$1(root, id);
40
+ const externalize = pathToFileURL(path).toString();
41
+ externalizeMap.set(id, externalize);
42
+ return {
43
+ externalize,
44
+ type: "module"
45
+ };
46
+ }
47
+ if (bareVitestRegexp.test(id)) {
48
+ externalizeMap.set(id, id);
49
+ return {
50
+ externalize: id,
51
+ type: "module"
52
+ };
53
+ }
54
+ return null;
55
+ }
56
+
57
+ const dispose = [];
58
+ function listenForErrors(state) {
59
+ dispose.forEach((fn) => fn());
60
+ dispose.length = 0;
61
+ function catchError(err, type, event) {
62
+ const worker = state();
63
+ const listeners = process.listeners(event);
64
+ // if there is another listener, assume that it's handled by user code
65
+ // one is Vitest's own listener
66
+ if (listeners.length > 1) return;
67
+ const error = processError(err);
68
+ if (typeof error === "object" && error != null) {
69
+ error.VITEST_TEST_NAME = worker.current?.type === "test" ? worker.current.name : void 0;
70
+ if (worker.filepath) error.VITEST_TEST_PATH = worker.filepath;
71
+ error.VITEST_AFTER_ENV_TEARDOWN = worker.environmentTeardownRun;
72
+ }
73
+ state().rpc.onUnhandledError(error, type);
74
+ }
75
+ const uncaughtException = (e) => catchError(e, "Uncaught Exception", "uncaughtException");
76
+ const unhandledRejection = (e) => catchError(e, "Unhandled Rejection", "unhandledRejection");
77
+ process.on("uncaughtException", uncaughtException);
78
+ process.on("unhandledRejection", unhandledRejection);
79
+ dispose.push(() => {
80
+ process.off("uncaughtException", uncaughtException);
81
+ process.off("unhandledRejection", unhandledRejection);
82
+ });
83
+ }
13
84
 
14
85
  const _DRIVE_LETTER_START_RE = /^[A-Za-z]:\//;
15
86
  function normalizeWindowsPath(input = "") {
@@ -53,7 +124,7 @@ const join = function(...segments) {
53
124
  }
54
125
  return normalize(path);
55
126
  };
56
- function cwd() {
127
+ function cwd$1() {
57
128
  if (typeof process !== "undefined" && typeof process.cwd === "function") return process.cwd().replace(/\\/g, "/");
58
129
  return "/";
59
130
  }
@@ -62,7 +133,7 @@ const resolve = function(...arguments_) {
62
133
  let resolvedPath = "";
63
134
  let resolvedAbsolute = false;
64
135
  for (let index = arguments_.length - 1; index >= -1 && !resolvedAbsolute; index--) {
65
- const path = index >= 0 ? arguments_[index] : cwd();
136
+ const path = index >= 0 ? arguments_[index] : cwd$1();
66
137
  if (!path || path.length === 0) continue;
67
138
  resolvedPath = `${path}/${resolvedPath}`;
68
139
  resolvedAbsolute = isAbsolute(path);
@@ -198,7 +269,7 @@ const builtins = new Set([
198
269
  "wasi"
199
270
  ]);
200
271
  // https://nodejs.org/api/modules.html#built-in-modules-with-mandatory-node-prefix
201
- const prefixedBuiltins = new Set([
272
+ const prefixedBuiltins$1 = new Set([
202
273
  "node:sea",
203
274
  "node:sqlite",
204
275
  "node:test",
@@ -206,7 +277,7 @@ const prefixedBuiltins = new Set([
206
277
  ]);
207
278
  const NODE_BUILTIN_NAMESPACE = "node:";
208
279
  function isNodeBuiltin(id) {
209
- if (prefixedBuiltins.has(id)) return true;
280
+ if (prefixedBuiltins$1.has(id)) return true;
210
281
  return builtins.has(id.startsWith(NODE_BUILTIN_NAMESPACE) ? id.slice(5) : id);
211
282
  }
212
283
 
@@ -218,9 +289,10 @@ class VitestMocker {
218
289
  filterPublicKeys;
219
290
  registries = /* @__PURE__ */ new Map();
220
291
  mockContext = { callstack: null };
221
- constructor(executor) {
222
- this.executor = executor;
223
- const context = this.executor.options.context;
292
+ constructor(moduleRunner, options) {
293
+ this.moduleRunner = moduleRunner;
294
+ this.options = options;
295
+ const context = this.options.context;
224
296
  if (context) this.primitives = vm.runInContext("({ Object, Error, Function, RegExp, Symbol, Array, Map })", context);
225
297
  else this.primitives = {
226
298
  Object,
@@ -250,16 +322,17 @@ class VitestMocker {
250
322
  ];
251
323
  }
252
324
  get root() {
253
- return this.executor.options.root;
325
+ return this.options.root;
254
326
  }
255
- get moduleCache() {
256
- return this.executor.moduleCache;
327
+ get evaluatedModules() {
328
+ return this.moduleRunner.evaluatedModules;
257
329
  }
258
330
  get moduleDirectories() {
259
- return this.executor.options.moduleDirectories || [];
331
+ return this.options.moduleDirectories || [];
260
332
  }
261
333
  async initializeSpyModule() {
262
- this.spyModule = await this.executor.executeId(spyModulePath);
334
+ if (this.spyModule) return;
335
+ this.spyModule = await this.moduleRunner.import(spyModulePath);
263
336
  }
264
337
  getMockerRegistry() {
265
338
  const suite = this.getSuiteFilepath();
@@ -269,15 +342,19 @@ class VitestMocker {
269
342
  reset() {
270
343
  this.registries.clear();
271
344
  }
272
- deleteCachedItem(id) {
345
+ invalidateModuleById(id) {
273
346
  const mockId = this.getMockPath(id);
274
- if (this.moduleCache.has(mockId)) this.moduleCache.delete(mockId);
347
+ const node = this.evaluatedModules.getModuleById(mockId);
348
+ if (node) {
349
+ this.evaluatedModules.invalidateModule(node);
350
+ node.mockedExports = void 0;
351
+ }
275
352
  }
276
353
  isModuleDirectory(path) {
277
354
  return this.moduleDirectories.some((dir) => path.includes(dir));
278
355
  }
279
356
  getSuiteFilepath() {
280
- return this.executor.state.filepath || "global";
357
+ return this.options.getCurrentTestFilepath() || "global";
281
358
  }
282
359
  createError(message, codeFrame) {
283
360
  const Error = this.primitives.Error;
@@ -285,40 +362,49 @@ class VitestMocker {
285
362
  Object.assign(error, { codeFrame });
286
363
  return error;
287
364
  }
288
- async resolvePath(rawId, importer) {
289
- let id;
290
- let fsPath;
291
- try {
292
- [id, fsPath] = await this.executor.originalResolveUrl(rawId, importer);
293
- } catch (error) {
294
- // it's allowed to mock unresolved modules
295
- if (error.code === "ERR_MODULE_NOT_FOUND") {
296
- const { id: unresolvedId } = error[Symbol.for("vitest.error.not_found.data")];
297
- id = unresolvedId;
298
- fsPath = unresolvedId;
299
- } else throw error;
365
+ async resolveId(rawId, importer) {
366
+ const result = await this.options.resolveId(rawId, importer);
367
+ if (!result) {
368
+ const id = normalizeModuleId(rawId);
369
+ return {
370
+ id,
371
+ url: rawId,
372
+ external: id
373
+ };
300
374
  }
301
375
  // external is node_module or unresolved module
302
376
  // for example, some people mock "vscode" and don't have it installed
303
- const external = !isAbsolute$1(fsPath) || this.isModuleDirectory(fsPath) ? rawId : null;
377
+ const external = !isAbsolute$1(result.file) || this.isModuleDirectory(result.file) ? normalizeModuleId(rawId) : null;
304
378
  return {
305
- id,
306
- fsPath,
307
- external: external ? this.normalizePath(external) : external
379
+ ...result,
380
+ id: normalizeModuleId(result.id),
381
+ external
308
382
  };
309
383
  }
310
384
  async resolveMocks() {
311
385
  if (!VitestMocker.pendingIds.length) return;
312
386
  await Promise.all(VitestMocker.pendingIds.map(async (mock) => {
313
- const { fsPath, external } = await this.resolvePath(mock.id, mock.importer);
314
- if (mock.action === "unmock") this.unmockPath(fsPath);
315
- if (mock.action === "mock") this.mockPath(mock.id, fsPath, external, mock.type, mock.factory);
387
+ const { id, url, external } = await this.resolveId(mock.id, mock.importer);
388
+ if (mock.action === "unmock") this.unmockPath(id);
389
+ if (mock.action === "mock") this.mockPath(mock.id, id, url, external, mock.type, mock.factory);
316
390
  }));
317
391
  VitestMocker.pendingIds = [];
318
392
  }
319
- async callFunctionMock(dep, mock) {
320
- const cached = this.moduleCache.get(dep)?.exports;
321
- if (cached) return cached;
393
+ ensureModule(id, url) {
394
+ const node = this.evaluatedModules.ensureModule(id, url);
395
+ // TODO
396
+ node.meta = {
397
+ id,
398
+ url,
399
+ code: "",
400
+ file: null,
401
+ invalidate: false
402
+ };
403
+ return node;
404
+ }
405
+ async callFunctionMock(id, url, mock) {
406
+ const node = this.ensureModule(id, url);
407
+ if (node.exports) return node.exports;
322
408
  const exports = await mock.resolve();
323
409
  const moduleExports = new Proxy(exports, { get: (target, prop) => {
324
410
  const val = target[prop];
@@ -339,7 +425,7 @@ If you need to partially mock a module, you can use "importOriginal" helper insi
339
425
  }
340
426
  return val;
341
427
  } });
342
- this.moduleCache.set(dep, { exports: moduleExports });
428
+ node.exports = moduleExports;
343
429
  return moduleExports;
344
430
  }
345
431
  // public method to avoid circular dependency
@@ -352,12 +438,9 @@ If you need to partially mock a module, you can use "importOriginal" helper insi
352
438
  }
353
439
  getDependencyMock(id) {
354
440
  const registry = this.getMockerRegistry();
355
- return registry.get(id);
356
- }
357
- normalizePath(path) {
358
- return this.moduleCache.normalizePath(path);
441
+ return registry.getById(fixLeadingSlashes(id));
359
442
  }
360
- resolveMockPath(mockPath, external) {
443
+ findMockRedirect(mockPath, external) {
361
444
  return findMockRedirect(this.root, mockPath, external);
362
445
  }
363
446
  mockObject(object, mockExports = {}, behavior = "automock") {
@@ -369,75 +452,88 @@ If you need to partially mock a module, you can use "importOriginal" helper insi
369
452
  type: behavior
370
453
  }, object, mockExports);
371
454
  }
372
- unmockPath(path) {
455
+ unmockPath(id) {
373
456
  const registry = this.getMockerRegistry();
374
- const id = this.normalizePath(path);
375
- registry.delete(id);
376
- this.deleteCachedItem(id);
457
+ registry.deleteById(id);
458
+ this.invalidateModuleById(id);
377
459
  }
378
- mockPath(originalId, path, external, mockType, factory) {
460
+ mockPath(originalId, id, url, external, mockType, factory) {
379
461
  const registry = this.getMockerRegistry();
380
- const id = this.normalizePath(path);
381
- if (mockType === "manual") registry.register("manual", originalId, id, id, factory);
382
- else if (mockType === "autospy") registry.register("autospy", originalId, id, id);
462
+ if (mockType === "manual") registry.register("manual", originalId, id, url, factory);
463
+ else if (mockType === "autospy") registry.register("autospy", originalId, id, url);
383
464
  else {
384
- const redirect = this.resolveMockPath(id, external);
385
- if (redirect) registry.register("redirect", originalId, id, id, redirect);
386
- else registry.register("automock", originalId, id, id);
465
+ const redirect = this.findMockRedirect(id, external);
466
+ if (redirect) registry.register("redirect", originalId, id, url, redirect);
467
+ else registry.register("automock", originalId, id, url);
387
468
  }
388
469
  // every time the mock is registered, we remove the previous one from the cache
389
- this.deleteCachedItem(id);
470
+ this.invalidateModuleById(id);
390
471
  }
391
472
  async importActual(rawId, importer, callstack) {
392
- const { id, fsPath } = await this.resolvePath(rawId, importer);
393
- const result = await this.executor.cachedRequest(id, fsPath, callstack || [importer]);
473
+ const { url } = await this.resolveId(rawId, importer);
474
+ const node = await this.moduleRunner.fetchModule(url, importer);
475
+ const result = await this.moduleRunner.cachedRequest(node.url, node, callstack || [importer], void 0, true);
394
476
  return result;
395
477
  }
396
- async importMock(rawId, importee) {
397
- const { id, fsPath, external } = await this.resolvePath(rawId, importee);
398
- const normalizedId = this.normalizePath(fsPath);
399
- let mock = this.getDependencyMock(normalizedId);
478
+ async importMock(rawId, importer) {
479
+ const { id, url, external } = await this.resolveId(rawId, importer);
480
+ let mock = this.getDependencyMock(id);
400
481
  if (!mock) {
401
- const redirect = this.resolveMockPath(normalizedId, external);
402
- if (redirect) mock = new RedirectedModule(rawId, normalizedId, normalizedId, redirect);
403
- else mock = new AutomockedModule(rawId, normalizedId, normalizedId);
482
+ const redirect = this.findMockRedirect(id, external);
483
+ if (redirect) mock = new RedirectedModule(rawId, id, rawId, redirect);
484
+ else mock = new AutomockedModule(rawId, id, rawId);
404
485
  }
405
486
  if (mock.type === "automock" || mock.type === "autospy") {
406
- const mod = await this.executor.cachedRequest(id, fsPath, [importee]);
407
- return this.mockObject(mod, {}, mock.type);
487
+ const node = await this.moduleRunner.fetchModule(url, importer);
488
+ const mod = await this.moduleRunner.cachedRequest(url, node, [importer], void 0, true);
489
+ const Object = this.primitives.Object;
490
+ return this.mockObject(mod, Object.create(Object.prototype), mock.type);
408
491
  }
409
- if (mock.type === "manual") return this.callFunctionMock(fsPath, mock);
410
- return this.executor.dependencyRequest(mock.redirect, mock.redirect, [importee]);
492
+ if (mock.type === "manual") return this.callFunctionMock(id, url, mock);
493
+ const node = await this.moduleRunner.fetchModule(mock.redirect);
494
+ return this.moduleRunner.cachedRequest(mock.redirect, node, [importer], void 0, true);
411
495
  }
412
- async requestWithMock(url, callstack) {
413
- const id = this.normalizePath(url);
414
- const mock = this.getDependencyMock(id);
415
- if (!mock) return;
416
- const mockPath = this.getMockPath(id);
496
+ async requestWithMockedModule(url, evaluatedNode, callstack, mock) {
497
+ const mockId = this.getMockPath(evaluatedNode.id);
417
498
  if (mock.type === "automock" || mock.type === "autospy") {
418
- const cache = this.moduleCache.get(mockPath);
419
- if (cache.exports) return cache.exports;
420
- const exports = {};
421
- // Assign the empty exports object early to allow for cycles to work. The object will be filled by mockObject()
422
- this.moduleCache.set(mockPath, { exports });
423
- const mod = await this.executor.directRequest(url, url, callstack);
499
+ const cache = this.evaluatedModules.getModuleById(mockId);
500
+ if (cache && cache.mockedExports) return cache.mockedExports;
501
+ const Object = this.primitives.Object;
502
+ // we have to define a separate object that will copy all properties into itself
503
+ // and can't just use the same `exports` define automatically by Vite before the evaluator
504
+ const exports = Object.create(null);
505
+ Object.defineProperty(exports, Symbol.toStringTag, {
506
+ value: "Module",
507
+ configurable: true,
508
+ writable: true
509
+ });
510
+ const node = this.ensureModule(mockId, this.getMockPath(evaluatedNode.url));
511
+ node.meta = evaluatedNode.meta;
512
+ node.file = evaluatedNode.file;
513
+ node.mockedExports = exports;
514
+ const mod = await this.moduleRunner.cachedRequest(url, node, callstack, void 0, true);
424
515
  this.mockObject(mod, exports, mock.type);
425
516
  return exports;
426
517
  }
427
- if (mock.type === "manual" && !callstack.includes(mockPath) && !callstack.includes(url)) try {
428
- callstack.push(mockPath);
518
+ if (mock.type === "manual" && !callstack.includes(mockId) && !callstack.includes(url)) try {
519
+ callstack.push(mockId);
429
520
  // this will not work if user does Promise.all(import(), import())
430
521
  // we can also use AsyncLocalStorage to store callstack, but this won't work in the browser
431
522
  // maybe we should improve mock API in the future?
432
523
  this.mockContext.callstack = callstack;
433
- return await this.callFunctionMock(mockPath, mock);
524
+ return await this.callFunctionMock(mockId, this.getMockPath(url), mock);
434
525
  } finally {
435
526
  this.mockContext.callstack = null;
436
- const indexMock = callstack.indexOf(mockPath);
527
+ const indexMock = callstack.indexOf(mockId);
437
528
  callstack.splice(indexMock, 1);
438
529
  }
439
530
  else if (mock.type === "redirect" && !callstack.includes(mock.redirect)) return mock.redirect;
440
531
  }
532
+ async mockedRequest(url, evaluatedNode, callstack) {
533
+ const mock = this.getDependencyMock(evaluatedNode.id);
534
+ if (!mock) return;
535
+ return this.requestWithMockedModule(url, evaluatedNode, callstack, mock);
536
+ }
441
537
  queueMock(id, importer, factoryOrOptions) {
442
538
  const mockType = getMockType(factoryOrOptions);
443
539
  VitestMocker.pendingIds.push({
@@ -461,248 +557,221 @@ function getMockType(factoryOrOptions) {
461
557
  if (typeof factoryOrOptions === "function") return "manual";
462
558
  return factoryOrOptions.spy ? "autospy" : "automock";
463
559
  }
464
-
465
- const normalizedDistDir = normalize$1(distDir);
466
- const { readFileSync } = fs;
467
- async function createVitestExecutor(options) {
468
- const runner = new VitestExecutor(options);
469
- await runner.executeId("/@vite/env");
470
- await runner.mocker.initializeSpyModule();
471
- return runner;
560
+ // unique id that is not available as "$bare_import" like "test"
561
+ // https://nodejs.org/api/modules.html#built-in-modules-with-mandatory-node-prefix
562
+ const prefixedBuiltins = new Set([
563
+ "node:sea",
564
+ "node:sqlite",
565
+ "node:test",
566
+ "node:test/reporters"
567
+ ]);
568
+ const isWindows$1 = process.platform === "win32";
569
+ // transform file url to id
570
+ // virtual:custom -> virtual:custom
571
+ // \0custom -> \0custom
572
+ // /root/id -> /id
573
+ // /root/id.js -> /id.js
574
+ // C:/root/id.js -> /id.js
575
+ // C:\root\id.js -> /id.js
576
+ // TODO: expose this in vite/module-runner
577
+ function normalizeModuleId(file) {
578
+ if (prefixedBuiltins.has(file)) return file;
579
+ // unix style, but Windows path still starts with the drive letter to check the root
580
+ const unixFile = slash(file).replace(/^\/@fs\//, isWindows$1 ? "" : "/").replace(/^node:/, "").replace(/^\/+/, "/");
581
+ // if it's not in the root, keep it as a path, not a URL
582
+ return unixFile.replace(/^file:\//, "/");
472
583
  }
473
- const externalizeMap = /* @__PURE__ */ new Map();
474
- const bareVitestRegexp = /^@?vitest(?:\/|$)/;
475
- const dispose = [];
476
- function listenForErrors(state) {
477
- dispose.forEach((fn) => fn());
478
- dispose.length = 0;
479
- function catchError(err, type, event) {
480
- const worker = state();
481
- const listeners = process.listeners(event);
482
- // if there is another listener, assume that it's handled by user code
483
- // one is Vitest's own listener
484
- if (listeners.length > 1) return;
485
- const error = processError(err);
486
- if (!isPrimitive(error)) {
487
- error.VITEST_TEST_NAME = worker.current?.type === "test" ? worker.current.name : void 0;
488
- if (worker.filepath) error.VITEST_TEST_PATH = worker.filepath;
489
- error.VITEST_AFTER_ENV_TEARDOWN = worker.environmentTeardownRun;
490
- }
491
- state().rpc.onUnhandledError(error, type);
492
- }
493
- const uncaughtException = (e) => catchError(e, "Uncaught Exception", "uncaughtException");
494
- const unhandledRejection = (e) => catchError(e, "Unhandled Rejection", "unhandledRejection");
495
- process.on("uncaughtException", uncaughtException);
496
- process.on("unhandledRejection", unhandledRejection);
497
- dispose.push(() => {
498
- process.off("uncaughtException", uncaughtException);
499
- process.off("unhandledRejection", unhandledRejection);
500
- });
584
+ const windowsSlashRE = /\\/g;
585
+ function slash(p) {
586
+ return p.replace(windowsSlashRE, "/");
501
587
  }
502
- const relativeIds = {};
503
- function getVitestImport(id, state) {
504
- if (externalizeMap.has(id)) return { externalize: externalizeMap.get(id) };
505
- // always externalize Vitest because we import from there before running tests
506
- // so we already have it cached by Node.js
507
- const root = state().config.root;
508
- const relativeRoot = relativeIds[root] ?? (relativeIds[root] = normalizedDistDir.slice(root.length));
509
- if (id.includes(distDir) || id.includes(normalizedDistDir) || relativeRoot && relativeRoot !== "/" && id.startsWith(relativeRoot)) {
510
- const { path } = toFilePath(id, root);
511
- const externalize = pathToFileURL(path).toString();
512
- externalizeMap.set(id, externalize);
513
- return { externalize };
588
+ const multipleSlashRe = /^\/+/;
589
+ // module-runner incorrectly replaces file:///path with `///path`
590
+ function fixLeadingSlashes(id) {
591
+ if (id.startsWith("//")) return id.replace(multipleSlashRe, "/");
592
+ return id;
593
+ }
594
+
595
+ // @ts-expect-error overriding private method
596
+ class VitestModuleRunner extends ModuleRunner {
597
+ mocker;
598
+ moduleExecutionInfo;
599
+ constructor(options) {
600
+ const transport = new VitestTransport(options.transport);
601
+ const evaluatedModules = options.evaluatedModules;
602
+ super({
603
+ transport,
604
+ hmr: false,
605
+ evaluatedModules,
606
+ sourcemapInterceptor: "prepareStackTrace"
607
+ }, options.evaluator);
608
+ this.options = options;
609
+ this.moduleExecutionInfo = options.getWorkerState().moduleExecutionInfo;
610
+ this.mocker = options.mocker || new VitestMocker(this, {
611
+ context: options.vm?.context,
612
+ resolveId: options.transport.resolveId,
613
+ get root() {
614
+ return options.getWorkerState().config.root;
615
+ },
616
+ get moduleDirectories() {
617
+ return options.getWorkerState().config.deps.moduleDirectories || [];
618
+ },
619
+ getCurrentTestFilepath() {
620
+ return options.getWorkerState().filepath;
621
+ }
622
+ });
623
+ if (options.vm) options.vm.context.__vitest_mocker__ = this.mocker;
624
+ else Object.defineProperty(globalThis, "__vitest_mocker__", {
625
+ configurable: true,
626
+ writable: true,
627
+ value: this.mocker
628
+ });
514
629
  }
515
- if (bareVitestRegexp.test(id)) {
516
- externalizeMap.set(id, id);
517
- return { externalize: id };
630
+ async import(rawId) {
631
+ const resolved = await this.options.transport.resolveId(rawId);
632
+ if (!resolved) return super.import(rawId);
633
+ return super.import(resolved.url);
634
+ }
635
+ async fetchModule(url, importer) {
636
+ const module = await this.cachedModule(url, importer);
637
+ return module;
638
+ }
639
+ _cachedRequest(url, module, callstack = [], metadata) {
640
+ // @ts-expect-error "cachedRequest" is private
641
+ return super.cachedRequest(url, module, callstack, metadata);
642
+ }
643
+ /**
644
+ * @internal
645
+ */
646
+ async cachedRequest(url, mod, callstack = [], metadata, ignoreMock = false) {
647
+ if (ignoreMock) return this._cachedRequest(url, mod, callstack, metadata);
648
+ let mocked;
649
+ if (mod.meta && "mockedModule" in mod.meta) mocked = await this.mocker.requestWithMockedModule(url, mod, callstack, mod.meta.mockedModule);
650
+ else mocked = await this.mocker.mockedRequest(url, mod, callstack);
651
+ if (typeof mocked === "string") {
652
+ const node = await this.fetchModule(mocked);
653
+ return this._cachedRequest(mocked, node, callstack, metadata);
654
+ }
655
+ if (mocked != null && typeof mocked === "object") return mocked;
656
+ return this._cachedRequest(url, mod, callstack, metadata);
657
+ }
658
+ /** @internal */
659
+ _invalidateSubTreeById(ids, invalidated = /* @__PURE__ */ new Set()) {
660
+ for (const id of ids) {
661
+ if (invalidated.has(id)) continue;
662
+ const node = this.evaluatedModules.getModuleById(id);
663
+ if (!node) continue;
664
+ invalidated.add(id);
665
+ const subIds = Array.from(this.evaluatedModules.idToModuleMap).filter(([, mod]) => mod.importers.has(id)).map(([key]) => key);
666
+ if (subIds.length) this._invalidateSubTreeById(subIds, invalidated);
667
+ this.evaluatedModules.invalidateModule(node);
668
+ }
518
669
  }
519
- return null;
520
670
  }
521
- async function startVitestExecutor(options) {
671
+
672
+ const { readFileSync } = fs;
673
+ const browserExternalId = "__vite-browser-external";
674
+ const browserExternalLength = 24;
675
+ const VITEST_VM_CONTEXT_SYMBOL = "__vitest_vm_context__";
676
+ const cwd = process.cwd();
677
+ const isWindows = process.platform === "win32";
678
+ async function startVitestModuleRunner(options) {
522
679
  const state = () => globalThis.__vitest_worker__ || options.state;
523
680
  const rpc = () => state().rpc;
524
681
  process.exit = (code = process.exitCode || 0) => {
525
682
  throw new Error(`process.exit unexpectedly called with "${code}"`);
526
683
  };
527
684
  listenForErrors(state);
528
- const getTransformMode = () => {
529
- return state().environment.transformMode ?? "ssr";
685
+ const environment = () => {
686
+ const environment = state().environment;
687
+ return environment.viteEnvironment || environment.name;
530
688
  };
531
- return await createVitestExecutor({
532
- async fetchModule(id) {
533
- const vitest = getVitestImport(id, state);
534
- if (vitest) return vitest;
535
- const result = await rpc().fetch(id, getTransformMode());
536
- if (result.id && !result.externalize) {
537
- const code = readFileSync(result.id, "utf-8");
538
- return { code };
539
- }
540
- return result;
541
- },
542
- resolveId(id, importer) {
543
- return rpc().resolveId(id, importer, getTransformMode());
544
- },
545
- get moduleCache() {
546
- return state().moduleCache;
547
- },
689
+ const vm = options.context && options.externalModulesExecutor ? {
690
+ context: options.context,
691
+ externalModulesExecutor: options.externalModulesExecutor
692
+ } : void 0;
693
+ const evaluator = options.evaluator || new VitestModuleEvaluator(vm, {
548
694
  get moduleExecutionInfo() {
549
695
  return state().moduleExecutionInfo;
550
696
  },
551
697
  get interopDefault() {
552
698
  return state().config.deps.interopDefault;
553
699
  },
554
- get moduleDirectories() {
555
- return state().config.deps.moduleDirectories;
556
- },
557
- get root() {
558
- return state().config.root;
559
- },
560
- get base() {
561
- return state().config.base;
700
+ getCurrentTestFilepath: () => state().filepath
701
+ });
702
+ const moduleRunner = new VitestModuleRunner({
703
+ evaluatedModules: options.evaluatedModules,
704
+ evaluator,
705
+ mocker: options.mocker,
706
+ transport: {
707
+ async fetchModule(id, importer, options) {
708
+ const resolvingModules = state().resolvingModules;
709
+ if (isWindows) {
710
+ if (id[1] === ":") {
711
+ // The drive letter is different for whatever reason, we need to normalize it to CWD
712
+ if (id[0] !== cwd[0] && id[0].toUpperCase() === cwd[0].toUpperCase()) {
713
+ const isUpperCase = cwd[0].toUpperCase() === cwd[0];
714
+ id = (isUpperCase ? id[0].toUpperCase() : id[0].toLowerCase()) + id.slice(1);
715
+ }
716
+ // always mark absolute windows paths, otherwise Vite will externalize it
717
+ id = `/@id/${id}`;
718
+ }
719
+ }
720
+ const vitest = getCachedVitestImport(id, state);
721
+ if (vitest) return vitest;
722
+ const rawId = unwrapId(id);
723
+ resolvingModules.add(rawId);
724
+ try {
725
+ if (VitestMocker.pendingIds.length) await moduleRunner.mocker.resolveMocks();
726
+ const resolvedMock = moduleRunner.mocker.getDependencyMock(rawId);
727
+ if (resolvedMock?.type === "manual" || resolvedMock?.type === "redirect") return {
728
+ code: "",
729
+ file: null,
730
+ id,
731
+ url: id,
732
+ invalidate: false,
733
+ mockedModule: resolvedMock
734
+ };
735
+ if (isBuiltin(rawId) || rawId.startsWith(browserExternalId)) return {
736
+ externalize: toBuiltin(rawId),
737
+ type: "builtin"
738
+ };
739
+ const result = await rpc().fetch(id, importer, environment(), options);
740
+ if ("cached" in result) {
741
+ const code = readFileSync(result.tmp, "utf-8");
742
+ return {
743
+ code,
744
+ ...result
745
+ };
746
+ }
747
+ return result;
748
+ } catch (cause) {
749
+ // rethrow vite error if it cannot load the module because it's not resolved
750
+ if (typeof cause === "object" && cause != null && cause.code === "ERR_LOAD_URL" || typeof cause?.message === "string" && cause.message.includes("Failed to load url") || typeof cause?.message === "string" && cause.message.startsWith("Cannot find module '")) {
751
+ const error = new Error(`Cannot find ${isBareImport(id) ? "package" : "module"} '${id}'${importer ? ` imported from '${importer}'` : ""}`, { cause });
752
+ error.code = "ERR_MODULE_NOT_FOUND";
753
+ throw error;
754
+ }
755
+ throw cause;
756
+ } finally {
757
+ resolvingModules.delete(rawId);
758
+ }
759
+ },
760
+ resolveId(id, importer) {
761
+ return rpc().resolve(id, importer, environment());
762
+ }
562
763
  },
563
- ...options
764
+ getWorkerState: state,
765
+ vm
564
766
  });
767
+ await moduleRunner.import("/@vite/env");
768
+ await moduleRunner.mocker.initializeSpyModule();
769
+ return moduleRunner;
565
770
  }
566
- function updateStyle(id, css) {
567
- if (typeof document === "undefined") return;
568
- const element = document.querySelector(`[data-vite-dev-id="${id}"]`);
569
- if (element) {
570
- element.textContent = css;
571
- return;
572
- }
573
- const head = document.querySelector("head");
574
- const style = document.createElement("style");
575
- style.setAttribute("type", "text/css");
576
- style.setAttribute("data-vite-dev-id", id);
577
- style.textContent = css;
578
- head?.appendChild(style);
579
- }
580
- function removeStyle(id) {
581
- if (typeof document === "undefined") return;
582
- const sheet = document.querySelector(`[data-vite-dev-id="${id}"]`);
583
- if (sheet) document.head.removeChild(sheet);
584
- }
585
- function getDefaultRequestStubs(context) {
586
- if (!context) {
587
- const clientStub = {
588
- ...DEFAULT_REQUEST_STUBS["@vite/client"],
589
- updateStyle,
590
- removeStyle
591
- };
592
- return {
593
- "/@vite/client": clientStub,
594
- "@vite/client": clientStub
595
- };
596
- }
597
- const clientStub = vm.runInContext(`(defaultClient) => ({ ...defaultClient, updateStyle: ${updateStyle.toString()}, removeStyle: ${removeStyle.toString()} })`, context)(DEFAULT_REQUEST_STUBS["@vite/client"]);
598
- return {
599
- "/@vite/client": clientStub,
600
- "@vite/client": clientStub
601
- };
602
- }
603
- class VitestExecutor extends ViteNodeRunner {
604
- mocker;
605
- externalModules;
606
- primitives;
607
- constructor(options) {
608
- super({
609
- ...options,
610
- interopDefault: options.context ? false : options.interopDefault
611
- });
612
- this.options = options;
613
- this.mocker = new VitestMocker(this);
614
- if (!options.context) {
615
- Object.defineProperty(globalThis, "__vitest_mocker__", {
616
- value: this.mocker,
617
- writable: true,
618
- configurable: true
619
- });
620
- this.primitives = {
621
- Object,
622
- Reflect,
623
- Symbol
624
- };
625
- } else if (options.externalModulesExecutor) {
626
- this.primitives = vm.runInContext("({ Object, Reflect, Symbol })", options.context);
627
- this.externalModules = options.externalModulesExecutor;
628
- } else throw new Error("When context is provided, externalModulesExecutor must be provided as well.");
629
- }
630
- getContextPrimitives() {
631
- return this.primitives;
632
- }
633
- get state() {
634
- // @ts-expect-error injected untyped global
635
- return globalThis.__vitest_worker__ || this.options.state;
636
- }
637
- get moduleExecutionInfo() {
638
- return this.options.moduleExecutionInfo;
639
- }
640
- shouldResolveId(id, _importee) {
641
- if (isInternalRequest(id) || id.startsWith("data:")) return false;
642
- const transformMode = this.state.environment?.transformMode ?? "ssr";
643
- // do not try and resolve node builtins in Node
644
- // import('url') returns Node internal even if 'url' package is installed
645
- return transformMode === "ssr" ? !isNodeBuiltin$1(id) : !id.startsWith("node:");
646
- }
647
- async originalResolveUrl(id, importer) {
648
- return super.resolveUrl(id, importer);
649
- }
650
- async resolveUrl(id, importer) {
651
- if (VitestMocker.pendingIds.length) await this.mocker.resolveMocks();
652
- if (importer && importer.startsWith("mock:")) importer = importer.slice(5);
653
- try {
654
- return await super.resolveUrl(id, importer);
655
- } catch (error) {
656
- if (error.code === "ERR_MODULE_NOT_FOUND") {
657
- const { id } = error[Symbol.for("vitest.error.not_found.data")];
658
- const path = this.mocker.normalizePath(id);
659
- const mock = this.mocker.getDependencyMock(path);
660
- if (mock !== void 0) return [id, id];
661
- }
662
- throw error;
663
- }
664
- }
665
- async runModule(context, transformed) {
666
- const vmContext = this.options.context;
667
- if (!vmContext || !this.externalModules) return super.runModule(context, transformed);
668
- // add 'use strict' since ESM enables it by default
669
- const codeDefinition = `'use strict';async (${Object.keys(context).join(",")})=>{{`;
670
- const code = `${codeDefinition}${transformed}\n}}`;
671
- const options = {
672
- filename: context.__filename,
673
- lineOffset: 0,
674
- columnOffset: -codeDefinition.length
675
- };
676
- const finishModuleExecutionInfo = this.startCalculateModuleExecutionInfo(options.filename, codeDefinition.length);
677
- try {
678
- const fn = vm.runInContext(code, vmContext, {
679
- ...options,
680
- importModuleDynamically: this.externalModules.importModuleDynamically
681
- });
682
- await fn(...Object.values(context));
683
- } finally {
684
- this.options.moduleExecutionInfo?.set(options.filename, finishModuleExecutionInfo());
685
- }
686
- }
687
- async importExternalModule(path) {
688
- if (this.externalModules) return this.externalModules.import(path);
689
- return super.importExternalModule(path);
690
- }
691
- async dependencyRequest(id, fsPath, callstack) {
692
- const mocked = await this.mocker.requestWithMock(fsPath, callstack);
693
- if (typeof mocked === "string") return super.dependencyRequest(mocked, mocked, callstack);
694
- if (mocked && typeof mocked === "object") return mocked;
695
- return super.dependencyRequest(id, fsPath, callstack);
696
- }
697
- prepareContext(context) {
698
- // support `import.meta.vitest` for test entry
699
- if (this.state.filepath && normalize$1(this.state.filepath) === normalize$1(context.__filename)) {
700
- const globalNamespace = this.options.context || globalThis;
701
- Object.defineProperty(context.__vite_ssr_import_meta__, "vitest", { get: () => globalNamespace.__vitest_index__ });
702
- }
703
- if (this.options.context && this.externalModules) context.require = this.externalModules.createRequire(context.__filename);
704
- return context;
705
- }
771
+ function toBuiltin(id) {
772
+ if (id.startsWith(browserExternalId)) id = id.slice(browserExternalLength);
773
+ if (!id.startsWith("node:")) id = `node:${id}`;
774
+ return id;
706
775
  }
707
776
 
708
- export { VitestExecutor as V, getDefaultRequestStubs as g, startVitestExecutor as s };
777
+ export { VitestModuleRunner as V, VITEST_VM_CONTEXT_SYMBOL as a, startVitestModuleRunner as s };