Skip to content

Commit ad86807

Browse files
MylesBorinscodebytere
authored andcommitted
module: better error for named exports from cjs
We do not support importing named exports from a CJS module. This change decorates the error message for missing named exports in the case where the module being imported is expected to be CJS by the ESM loader. Signed-off-by: Myles Borins <[email protected]> PR-URL: #33256 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Michaël Zasso <[email protected]>
1 parent 0cbee57 commit ad86807

File tree

15 files changed

+121
-0
lines changed

15 files changed

+121
-0
lines changed

lib/internal/modules/esm/module_job.js

+27
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
'use strict';
22

33
const {
4+
ArrayPrototypeJoin,
45
ObjectSetPrototypeOf,
56
PromiseAll,
67
SafeSet,
78
SafePromise,
9+
StringPrototypeIncludes,
10+
StringPrototypeMatch,
11+
StringPrototypeSplit,
812
} = primordials;
913

1014
const { ModuleWrap } = internalBinding('module_wrap');
@@ -93,6 +97,29 @@ class ModuleJob {
9397
}
9498
} catch (e) {
9599
decorateErrorStack(e);
100+
if (StringPrototypeIncludes(e.message,
101+
' does not provide an export named')) {
102+
const splitStack = StringPrototypeSplit(e.stack, '\n');
103+
const parentFileUrl = splitStack[0];
104+
const childSpecifier = StringPrototypeMatch(e.message, /module '(.*)' does/)[1];
105+
const childFileURL =
106+
await this.loader.resolve(childSpecifier, parentFileUrl);
107+
const format = await this.loader.getFormat(childFileURL);
108+
if (format === 'commonjs') {
109+
const importStatement = splitStack[1];
110+
const namedImports = StringPrototypeMatch(importStatement, /{.*}/)[0];
111+
e.message = `The requested module '${childSpecifier}' is expected ` +
112+
'to be of type CommonJS, which does not support named exports. ' +
113+
'CommonJS modules can be imported by importing the default ' +
114+
'export.\n' +
115+
'For example:\n' +
116+
`import pkg from '${childSpecifier}';\n` +
117+
`const ${namedImports} = pkg;`;
118+
const newStack = StringPrototypeSplit(e.stack, '\n');
119+
newStack[3] = `SyntaxError: ${e.message}`;
120+
e.stack = ArrayPrototypeJoin(newStack, '\n');
121+
}
122+
}
96123
throw e;
97124
}
98125
for (const dependencyJob of jobsInGraph) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import '../common/index.mjs';
2+
import { rejects } from 'assert';
3+
4+
const fixtureBase = '../fixtures/es-modules/package-cjs-named-error';
5+
6+
const expectedRelative = 'The requested module \'./fail.cjs\' is expected to ' +
7+
'be of type CommonJS, which does not support named exports. CommonJS ' +
8+
'modules can be imported by importing the default export.\n' +
9+
'For example:\n' +
10+
'import pkg from \'./fail.cjs\';\n' +
11+
'const { comeOn } = pkg;';
12+
13+
const expectedPackageHack = 'The requested module \'./json-hack/fail.js\' is ' +
14+
'expected to be of type CommonJS, which does not support named exports. ' +
15+
'CommonJS modules can be imported by importing the default export.\n' +
16+
'For example:\n' +
17+
'import pkg from \'./json-hack/fail.js\';\n' +
18+
'const { comeOn } = pkg;';
19+
20+
const expectedBare = 'The requested module \'deep-fail\' is expected to ' +
21+
'be of type CommonJS, which does not support named exports. CommonJS ' +
22+
'modules can be imported by importing the default export.\n' +
23+
'For example:\n' +
24+
'import pkg from \'deep-fail\';\n' +
25+
'const { comeOn } = pkg;';
26+
27+
rejects(async () => {
28+
await import(`${fixtureBase}/single-quote.mjs`);
29+
}, {
30+
name: 'SyntaxError',
31+
message: expectedRelative
32+
}, 'should support relative specifiers with single quotes');
33+
34+
rejects(async () => {
35+
await import(`${fixtureBase}/double-quote.mjs`);
36+
}, {
37+
name: 'SyntaxError',
38+
message: expectedRelative
39+
}, 'should support relative specifiers with double quotes');
40+
41+
rejects(async () => {
42+
await import(`${fixtureBase}/json-hack.mjs`);
43+
}, {
44+
name: 'SyntaxError',
45+
message: expectedPackageHack
46+
}, 'should respect recursive package.json for module type');
47+
48+
rejects(async () => {
49+
await import(`${fixtureBase}/bare-import-single.mjs`);
50+
}, {
51+
name: 'SyntaxError',
52+
message: expectedBare
53+
}, 'should support bare specifiers with single quotes');
54+
55+
rejects(async () => {
56+
await import(`${fixtureBase}/bare-import-double.mjs`);
57+
}, {
58+
name: 'SyntaxError',
59+
message: expectedBare
60+
}, 'should support bare specifiers with double quotes');
61+
62+
rejects(async () => {
63+
await import(`${fixtureBase}/escaped-single-quote.mjs`);
64+
}, /import pkg from '\.\/oh'no\.cjs'/, 'should support relative specifiers with escaped single quote');
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import { comeOn } from "deep-fail";
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import { comeOn } from 'deep-fail';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import { comeOn } from "./fail.cjs";
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import { value } from "./oh'no.cjs";
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module.exports = {
2+
comeOn: 'fhqwhgads'
3+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import { comeOn } from './json-hack/fail.js';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module.exports = {
2+
comeOn: 'fhqwhgads'
3+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"type": "commonjs"
3+
}

test/fixtures/es-modules/package-cjs-named-error/node_modules/deep-fail/index.js

+3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/fixtures/es-modules/package-cjs-named-error/node_modules/deep-fail/package.json

+4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module.exports = {
2+
value: 123
3+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"name": "package-cjs-named-error",
3+
"main": "index.mjs",
4+
"type": "module"
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import { comeOn } from './fail.cjs';

0 commit comments

Comments
 (0)