Skip to content

Commit f9eacbc

Browse files
authored
fix(vite-node): add ERR_MODULE_NOT_FOUND code error if module cannot be loaded (#7776)
1 parent 53c9973 commit f9eacbc

File tree

8 files changed

+78
-15
lines changed

8 files changed

+78
-15
lines changed

packages/vite-node/src/client.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { extractSourceMap } from './source-map'
99
import {
1010
cleanUrl,
1111
createImportMetaEnvProxy,
12+
isBareImport,
1213
isInternalRequest,
1314
isNodeBuiltin,
1415
isPrimitive,
@@ -325,6 +326,28 @@ export class ViteNodeRunner {
325326
return await this.cachedRequest(id, fsPath, callstack)
326327
}
327328

329+
private async _fetchModule(id: string, importer?: string) {
330+
try {
331+
return await this.options.fetchModule(id)
332+
}
333+
catch (cause: any) {
334+
// rethrow vite error if it cannot load the module because it's not resolved
335+
if (
336+
(typeof cause === 'object' && cause.code === 'ERR_LOAD_URL')
337+
|| (typeof cause?.message === 'string' && cause.message.includes('Failed to load url'))
338+
) {
339+
const error = new Error(
340+
`Cannot find ${isBareImport(id) ? 'package' : 'module'} '${id}'${importer ? ` imported from '${importer}'` : ''}`,
341+
{ cause },
342+
) as Error & { code: string }
343+
error.code = 'ERR_MODULE_NOT_FOUND'
344+
throw error
345+
}
346+
347+
throw cause
348+
}
349+
}
350+
328351
/** @internal */
329352
async directRequest(id: string, fsPath: string, _callstack: string[]) {
330353
const moduleId = normalizeModuleId(fsPath)
@@ -345,7 +368,10 @@ export class ViteNodeRunner {
345368
if (id in requestStubs) {
346369
return requestStubs[id]
347370
}
348-
let { code: transformed, externalize } = await this.options.fetchModule(id)
371+
let { code: transformed, externalize } = await this._fetchModule(
372+
id,
373+
callstack[callstack.length - 2],
374+
)
349375

350376
if (externalize) {
351377
debugNative(externalize)

packages/vite-node/src/utils.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ export function slash(str: string): string {
2121
return str.replace(/\\/g, '/')
2222
}
2323

24+
const bareImportRE = /^(?![a-z]:)[\w@](?!.*:\/\/)/i
25+
26+
export function isBareImport(id: string): boolean {
27+
return bareImportRE.test(id)
28+
}
29+
2430
export const VALID_ID_PREFIX = '/@id/'
2531

2632
export function normalizeRequestId(id: string, base?: string): string {

packages/vitest/src/runtime/external-executor.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import fs from 'node:fs'
66
import { dirname } from 'node:path'
77
import { fileURLToPath, pathToFileURL } from 'node:url'
88
import { extname, join, normalize } from 'pathe'
9-
import { getCachedData, isNodeBuiltin, setCacheData } from 'vite-node/utils'
9+
import { getCachedData, isBareImport, isNodeBuiltin, setCacheData } from 'vite-node/utils'
1010
import { CommonjsExecutor } from './vm/commonjs-executor'
1111
import { EsmExecutor } from './vm/esm-executor'
1212
import { ViteExecutor } from './vm/vite-executor'
@@ -209,7 +209,7 @@ export class ExternalModulesExecutor {
209209
(type === 'module' || type === 'commonjs' || type === 'wasm')
210210
&& !existsSync(path)
211211
) {
212-
const error = new Error(`Cannot find module '${path}'`);
212+
const error = new Error(`Cannot find ${isBareImport(path) ? 'package' : 'module'} '${path}'`);
213213
(error as any).code = 'ERR_MODULE_NOT_FOUND'
214214
throw error
215215
}

packages/vitest/src/runtime/vm/vite-executor.ts

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,30 @@ export class ViteExecutor {
6363
return cached
6464
}
6565
return this.esm.createEsModule(fileUrl, async () => {
66-
const result = await this.options.transform(fileUrl, 'web')
67-
if (!result.code) {
68-
throw new Error(
69-
`[vitest] Failed to transform ${fileUrl}. Does the file exist?`,
70-
)
66+
try {
67+
const result = await this.options.transform(fileUrl, 'web')
68+
if (result.code) {
69+
return result.code
70+
}
7171
}
72-
return result.code
72+
catch (cause: any) {
73+
// rethrow vite error if it cannot load the module because it's not resolved
74+
if (
75+
(typeof cause === 'object' && cause.code === 'ERR_LOAD_URL')
76+
|| (typeof cause?.message === 'string' && cause.message.includes('Failed to load url'))
77+
) {
78+
const error = new Error(
79+
`Cannot find module '${fileUrl}'`,
80+
{ cause },
81+
) as Error & { code: string }
82+
error.code = 'ERR_MODULE_NOT_FOUND'
83+
throw error
84+
}
85+
}
86+
87+
throw new Error(
88+
`[vitest] Failed to transform ${fileUrl}. Does the file exist?`,
89+
)
7390
})
7491
}
7592

test/cli/fixtures/vm-threads/not-found.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ it('package', async () => {
2222
it('builtin', async () => {
2323
await expect(() => notFound.importBuiltin()).rejects.toMatchObject({
2424
code: 'ERR_MODULE_NOT_FOUND',
25-
message: 'Cannot find module \'node:non-existing-builtin\'',
25+
message: 'Cannot find package \'node:non-existing-builtin\'',
2626
})
2727
})
2828

@@ -31,6 +31,6 @@ it('builtin', async () => {
3131
it('namespace', async () => {
3232
await expect(() => notFound.importNamespace()).rejects.toMatchObject({
3333
code: 'ERR_MODULE_NOT_FOUND',
34-
message: 'Cannot find module \'non-existing-namespace:xyz\'',
34+
message: 'Cannot find package \'non-existing-namespace:xyz\'',
3535
})
3636
})

test/core/test/dynamic-import.test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { expect, test } from 'vitest'
2+
3+
test('dynamic import', async () => {
4+
try {
5+
await import('non-existing-module' as any)
6+
expect.unreachable()
7+
}
8+
catch (err: any) {
9+
expect(err.message).toBe(
10+
`Cannot find package 'non-existing-module' imported from '${import.meta.filename}'`,
11+
)
12+
expect(err.code).toBe('ERR_MODULE_NOT_FOUND')
13+
}
14+
})

test/core/test/imports.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,9 @@ test('dynamic import has null prototype', async () => {
8282
test('dynamic import throws an error', async () => {
8383
const path = './some-unknown-path'
8484
const imported = import(path)
85-
await expect(imported).rejects.toThrowError(/Failed to load url \.\/some-unknown-path/)
85+
await expect(imported).rejects.toThrowError(/Cannot find module '\.\/some-unknown-path' imported/)
8686
// @ts-expect-error path does not exist
87-
await expect(() => import('./some-unknown-path')).rejects.toThrowError(/Failed to load/)
87+
await expect(() => import('./some-unknown-path')).rejects.toThrowError(/Cannot find module/)
8888
})
8989

9090
test('can import @vite/client', async () => {

test/core/test/web-worker-jsdom.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ it('worker with invalid url throws an error', async () => {
1818
if (!import.meta.env.VITEST_VM_POOL) {
1919
expect(event.error).toBeInstanceOf(Error)
2020
}
21-
expect(event.error.message).toContain('Failed to load')
21+
expect(event.error.message).toContain('Cannot find module')
2222
})
2323

2424
it('throws an error on invalid path', async () => {
@@ -34,7 +34,7 @@ it('throws an error on invalid path', async () => {
3434
if (!import.meta.env.VITEST_VM_POOL) {
3535
expect(event.error).toBeInstanceOf(Error)
3636
}
37-
expect(event.error.message).toContain('Failed to load')
37+
expect(event.error.message).toContain('Cannot find module')
3838
})
3939

4040
it('returns globals on self correctly', async () => {

0 commit comments

Comments
 (0)