Skip to content

Commit 7ef5cf4

Browse files
authored
fix: show a better error if there is a pending dynamic import (#9676)
1 parent b63653f commit 7ef5cf4

File tree

6 files changed

+42
-9
lines changed

6 files changed

+42
-9
lines changed

packages/vitest/src/integrations/env/loader.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { readFileSync } from 'node:fs'
66
import { isBuiltin } from 'node:module'
77
import { pathToFileURL } from 'node:url'
88
import { resolve } from 'pathe'
9-
import { ModuleRunner } from 'vite/module-runner'
9+
import { EvaluatedModules, ModuleRunner } from 'vite/module-runner'
1010
import { VitestTransport } from '../../runtime/moduleRunner/moduleTransport'
1111
import { environments } from './index'
1212

@@ -24,6 +24,7 @@ export function createEnvironmentLoader(root: string, rpc: WorkerRPC): ModuleRun
2424
if (!cachedLoader || cachedLoader.isClosed()) {
2525
_loaders.delete(root)
2626

27+
const evaluatedModules = new EvaluatedModules()
2728
const moduleRunner = new ModuleRunner({
2829
hmr: false,
2930
sourcemapInterceptor: 'prepareStackTrace',
@@ -46,7 +47,7 @@ export function createEnvironmentLoader(root: string, rpc: WorkerRPC): ModuleRun
4647
async resolveId(id, importer) {
4748
return rpc.resolve(id, importer, '__vitest__')
4849
},
49-
}),
50+
}, evaluatedModules, new WeakMap()),
5051
})
5152
_loaders.set(root, moduleRunner)
5253
}

packages/vitest/src/node/logger.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,7 @@ export class Logger {
301301
this.error(errorMessage)
302302
errors.forEach((err) => {
303303
this.printError(err, {
304-
fullStack: true,
304+
fullStack: (err as any).name !== 'EnvironmentTeardownError',
305305
type: (err as any).type || 'Unhandled Error',
306306
})
307307
})

packages/vitest/src/runtime/moduleRunner/moduleRunner.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,13 @@ export class VitestModuleRunner
4949
public mocker: VitestMocker
5050
public moduleExecutionInfo: ModuleExecutionInfo
5151
private _otel: Traces
52+
private _callstacks: WeakMap<EvaluatedModuleNode, string[]>
5253

5354
constructor(private vitestOptions: VitestModuleRunnerOptions) {
5455
const options = vitestOptions
55-
const transport = new VitestTransport(options.transport)
5656
const evaluatedModules = options.evaluatedModules
57+
const callstacks = new WeakMap<EvaluatedModuleNode, string[]>()
58+
const transport = new VitestTransport(options.transport, evaluatedModules, callstacks)
5759
super(
5860
{
5961
transport,
@@ -64,6 +66,7 @@ export class VitestModuleRunner
6466
},
6567
options.evaluator,
6668
)
69+
this._callstacks = callstacks
6770
this._otel = vitestOptions.traces || new Traces({ enabled: false })
6871
this.moduleExecutionInfo = options.getWorkerState().moduleExecutionInfo
6972
this.mocker = options.mocker || new VitestMocker(this, {
@@ -153,6 +156,9 @@ export class VitestModuleRunner
153156
metadata?: SSRImportMetadata,
154157
ignoreMock = false,
155158
): Promise<any> {
159+
// Track for a better error message if dynamic import is not resolved properly
160+
this._callstacks.set(mod, callstack)
161+
156162
if (ignoreMock) {
157163
return this._cachedRequest(url, mod, callstack, metadata)
158164
}

packages/vitest/src/runtime/moduleRunner/moduleTransport.ts

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
1-
import type { FetchFunction, ModuleRunnerTransport } from 'vite/module-runner'
1+
import type { EvaluatedModuleNode, EvaluatedModules, FetchFunction, ModuleRunnerTransport } from 'vite/module-runner'
22
import type { ResolveFunctionResult } from '../../types/general'
3+
import { EnvironmentTeardownError } from '../utils'
34

45
export interface VitestTransportOptions {
56
fetchModule: FetchFunction
67
resolveId: (id: string, importer?: string) => Promise<ResolveFunctionResult | null>
78
}
89

910
export class VitestTransport implements ModuleRunnerTransport {
10-
constructor(private options: VitestTransportOptions) {}
11+
constructor(
12+
private options: VitestTransportOptions,
13+
private evaluatedModules: EvaluatedModules,
14+
private callstacks: WeakMap<EvaluatedModuleNode, string[]>,
15+
) {}
1116

1217
async invoke(event: any): Promise<{ result: any } | { error: any }> {
1318
if (event.type !== 'custom') {
@@ -29,8 +34,24 @@ export class VitestTransport implements ModuleRunnerTransport {
2934
const result = await this.options.fetchModule(...data as Parameters<FetchFunction>)
3035
return { result }
3136
}
32-
catch (error) {
33-
return { error }
37+
catch (cause) {
38+
if (cause instanceof EnvironmentTeardownError) {
39+
const [id, importer] = data as Parameters<FetchFunction>
40+
let message = `Cannot load '${id}'${importer ? ` imported from ${importer}` : ''} after the environment was torn down. `
41+
+ `This is not a bug in Vitest.`
42+
43+
const moduleNode = importer ? this.evaluatedModules.getModuleById(importer) : undefined
44+
const callstack = moduleNode ? this.callstacks.get(moduleNode) : undefined
45+
if (callstack) {
46+
message += ` The last recorded callstack:\n- ${[...callstack, importer, id].reverse().join('\n- ')}`
47+
}
48+
const error = new EnvironmentTeardownError(message)
49+
if (cause.stack) {
50+
error.stack = cause.stack.replace(cause.message, error.message)
51+
}
52+
return { error }
53+
}
54+
return { error: cause }
3455
}
3556
}
3657
}

packages/vitest/src/runtime/utils.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ import { getSafeTimers } from '@vitest/utils/timers'
44

55
const NAME_WORKER_STATE = '__vitest_worker__'
66

7+
export class EnvironmentTeardownError extends Error {
8+
name = 'EnvironmentTeardownError'
9+
}
10+
711
export function getWorkerState(): WorkerGlobalState {
812
// @ts-expect-error untyped global
913
const workerState = globalThis[NAME_WORKER_STATE]

packages/vitest/src/runtime/worker.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { setupInspect } from './inspector'
66
import * as listeners from './listeners'
77
import { VitestEvaluatedModules } from './moduleRunner/evaluatedModules'
88
import { onCancel, rpcDone } from './rpc'
9+
import { EnvironmentTeardownError } from './utils'
910

1011
const resolvingModules = new Set<string>()
1112

@@ -21,7 +22,7 @@ async function execute(method: 'run' | 'collect', ctx: ContextRPC, worker: Vites
2122
// do not close the RPC channel so that we can get the error messages sent to the main thread
2223
cleanups.push(async () => {
2324
await Promise.all(rpc.$rejectPendingCalls(({ method, reject }) => {
24-
reject(new Error(`[vitest-worker]: Closing rpc while "${method}" was pending`))
25+
reject(new EnvironmentTeardownError(`[vitest-worker]: Closing rpc while "${method}" was pending`))
2526
}))
2627
})
2728

0 commit comments

Comments
 (0)