Skip to content

Commit baa12fd

Browse files
authored
fix: wrong error thrown if loader is used (#4807)
1 parent 60fafa4 commit baa12fd

File tree

6 files changed

+65
-4
lines changed

6 files changed

+65
-4
lines changed

lib/nodejs/esm-utils.js

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,15 +53,30 @@ exports.requireOrImport = hasStableEsmImplementation
5353
err.code === 'ERR_UNSUPPORTED_DIR_IMPORT'
5454
) {
5555
try {
56+
// Importing a file usually works, but the resolution of `import` is the ESM
57+
// resolution algorithm, and not the CJS resolution algorithm. So in this case
58+
// if we fail, we may have failed because we tried the ESM resolution and failed
59+
// So we try to `require` it
5660
return require(file);
5761
} catch (requireErr) {
58-
if (requireErr.code === 'ERR_REQUIRE_ESM') {
59-
// This happens when the test file is a JS file, but via type:module is actually ESM,
62+
if (
63+
requireErr.code === 'ERR_REQUIRE_ESM' ||
64+
(requireErr instanceof SyntaxError &&
65+
requireErr
66+
.toString()
67+
.includes('Cannot use import statement outside a module'))
68+
) {
69+
// ERR_REQUIRE_ESM happens when the test file is a JS file, but via type:module is actually ESM,
6070
// AND has an import to a file that doesn't exist.
61-
// This throws an `ERR_MODULE_NOT_FOUND` // error above,
71+
// This throws an `ERR_MODULE_NOT_FOUND` error above,
6272
// and when we try to `require` it here, it throws an `ERR_REQUIRE_ESM`.
6373
// What we want to do is throw the original error (the `ERR_MODULE_NOT_FOUND`),
6474
// and not the `ERR_REQUIRE_ESM` error, which is a red herring.
75+
//
76+
// SyntaxError happens when in an edge case: when we're using an ESM loader that loads
77+
// a `test.ts` file (i.e. unrecognized extension), and that file includes an unknown
78+
// import (which thows an ERR_MODULE_NOT_FOUND). require-ing it will throw the
79+
// syntax error, because we cannot require a file that has import-s.
6580
throw err;
6681
} else {
6782
throw requireErr;

test/integration/esm.spec.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,4 +81,25 @@ describe('esm', function () {
8181
'test-that-imports-non-existing-module'
8282
);
8383
});
84+
85+
it('should throw an ERR_MODULE_NOT_FOUND and not ERR_REQUIRE_ESM if file imports a non-existing module with a loader', async function () {
86+
const fixture =
87+
'esm/loader-with-module-not-found/test-that-imports-non-existing-module.fixture.ts';
88+
89+
const err = await runMochaAsync(
90+
fixture,
91+
[
92+
'--unhandled-rejections=warn',
93+
'--loader=./test/integration/fixtures/esm/loader-with-module-not-found/loader-that-recognizes-ts.mjs'
94+
],
95+
{
96+
stdio: 'pipe'
97+
}
98+
).catch(err => err);
99+
100+
expect(err.output, 'to contain', 'ERR_MODULE_NOT_FOUND').and(
101+
'to contain',
102+
'non-existent-package'
103+
);
104+
});
84105
});
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import path from 'path'
2+
import {fileURLToPath} from 'url'
3+
4+
/**
5+
* @param {string} specifier
6+
* @param {{
7+
* conditions: !Array<string>,
8+
* parentURL: !(string | undefined),
9+
* }} context
10+
* @param {Function} defaultResolve
11+
* @returns {Promise<{ url: string }>}
12+
*/
13+
export async function resolve(specifier, context, defaultResolve) {
14+
const extension = path.extname(
15+
fileURLToPath(/**@type {import('url').URL}*/ (new URL(specifier, context.parentURL))),
16+
)
17+
return await defaultResolve(specifier.replace('.ts', '.mjs'), context, defaultResolve)
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import 'non-existent-package';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// This file will be resolved to `test-that-imports-non-existing-module.fixture.mjs` by the loader
2+
import 'non-existent-package';

test/integration/helpers.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,11 @@ function createSubprocess(args, done, opts = {}) {
368368
* @returns {string} Resolved filepath
369369
*/
370370
function resolveFixturePath(fixture) {
371-
if (path.extname(fixture) !== '.js' && path.extname(fixture) !== '.mjs') {
371+
if (
372+
path.extname(fixture) !== '.js' &&
373+
path.extname(fixture) !== '.mjs' &&
374+
path.extname(fixture) !== '.ts'
375+
) {
372376
fixture += '.fixture.js';
373377
}
374378
return path.isAbsolute(fixture)

0 commit comments

Comments
 (0)