diff --git a/packages/vitest/src/runtime/external-executor.ts b/packages/vitest/src/runtime/external-executor.ts index 5093ab6c8a1f..05a976583cb5 100644 --- a/packages/vitest/src/runtime/external-executor.ts +++ b/packages/vitest/src/runtime/external-executor.ts @@ -3,7 +3,7 @@ import vm from 'node:vm' import { fileURLToPath, pathToFileURL } from 'node:url' import { dirname } from 'node:path' -import { statSync } from 'node:fs' +import { existsSync, statSync } from 'node:fs' import { extname, join, normalize } from 'pathe' import { getCachedData, isNodeBuiltin, setCacheData } from 'vite-node/utils' import type { RuntimeRPC } from '../types/rpc' @@ -188,6 +188,14 @@ export class ExternalModulesExecutor { private async createModule(identifier: string): Promise { const { type, url, path } = this.getModuleInformation(identifier) + // create ERR_MODULE_NOT_FOUND on our own since latest NodeJS's import.meta.resolve doesn't throw on non-existing namespace or path + // https://github.com/nodejs/node/pull/49038 + if ((type === 'module' || type === 'commonjs') && !existsSync(path)) { + const error = new Error(`Cannot find module '${path}'`) + ;(error as any).code = 'ERR_MODULE_NOT_FOUND' + throw error + } + switch (type) { case 'data': return this.esm.createDataModule(identifier) diff --git a/test/vm-threads/src/external/not-found.js b/test/vm-threads/src/external/not-found.js new file mode 100644 index 000000000000..2348ffd8441f --- /dev/null +++ b/test/vm-threads/src/external/not-found.js @@ -0,0 +1,15 @@ +export async function importPackage() { + await import('@vitest/non-existing-package') +} + +export async function importPath() { + await import('./non-existing-path') +} + +export async function importBuiltin() { + await import('node:non-existing-builtin') +} + +export async function importNamespace() { + await import('non-existing-namespace:xyz') +} diff --git a/test/vm-threads/test/not-found.test.ts b/test/vm-threads/test/not-found.test.ts new file mode 100644 index 000000000000..d9a84d13a4b4 --- /dev/null +++ b/test/vm-threads/test/not-found.test.ts @@ -0,0 +1,36 @@ +import { expect, it } from 'vitest' + +// @ts-expect-error untyped +import * as notFound from '../src/external/not-found.js' + +it('path', async () => { + await expect(() => notFound.importPath()).rejects.toMatchObject({ + code: 'ERR_MODULE_NOT_FOUND', + message: expect.stringMatching(/Cannot find module '.*?non-existing-path'/), + }) +}) + +// NodeJs's import.meta.resolve throws ERR_MODULE_NOT_FOUND error only this case. +// For other cases, similar errors are fabricated by Vitest to mimic NodeJs's behavior. +it('package', async () => { + await expect(() => notFound.importPackage()).rejects.toMatchObject({ + code: 'ERR_MODULE_NOT_FOUND', + message: expect.stringContaining('Cannot find package \'@vitest/non-existing-package\''), + }) +}) + +it('builtin', async () => { + await expect(() => notFound.importBuiltin()).rejects.toMatchObject({ + code: 'ERR_MODULE_NOT_FOUND', + message: 'Cannot find module \'node:non-existing-builtin\'', + }) +}) + +// this test fails before node 20.3.0 since it throws a different error (cf. https://github.com/nodejs/node/pull/47824) +// > Only URLs with a scheme in: file and data are supported by the default ESM loader. Received protocol 'non-existing-namespace:' +it('namespace', async () => { + await expect(() => notFound.importNamespace()).rejects.toMatchObject({ + code: 'ERR_MODULE_NOT_FOUND', + message: 'Cannot find module \'non-existing-namespace:xyz\'', + }) +})