Skip to content

Commit 925e40c

Browse files
gntemtargos
authored andcommitted
module: add warning when import,export is detected in CJS context
This will allow users to know how to change their project to support ES modules. PR-URL: #28950 Reviewed-By: Bradley Farias <[email protected]> Reviewed-By: Rich Trott <[email protected]> Reviewed-By: Guy Bedford <[email protected]>
1 parent 53c7fac commit 925e40c

9 files changed

+196
-17
lines changed

β€Žlib/internal/modules/cjs/loader.js

+49-17
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,29 @@ const relativeResolveCache = Object.create(null);
8585

8686
let requireDepth = 0;
8787
let statCache = null;
88+
89+
function enrichCJSError(err) {
90+
const stack = err.stack.split('\n');
91+
92+
const lineWithErr = stack[1];
93+
94+
/*
95+
The regular expression below targets the most common import statement
96+
usage. However, some cases are not matching, cases like import statement
97+
after a comment block and/or after a variable definition.
98+
*/
99+
if (err.message.startsWith('Unexpected token export') ||
100+
(/^\s*import(?=[ {'"*])\s*(?![ (])/).test(lineWithErr)) {
101+
process.emitWarning(
102+
'To load an ES module, set "type": "module" in the package.json or use ' +
103+
'the .mjs extension.',
104+
undefined,
105+
undefined,
106+
undefined,
107+
true);
108+
}
109+
}
110+
88111
function stat(filename) {
89112
filename = path.toNamespacedPath(filename);
90113
if (statCache !== null) {
@@ -808,23 +831,32 @@ Module.prototype._compile = function(content, filename) {
808831
} : undefined,
809832
});
810833
} else {
811-
const compiled = compileFunction(
812-
content,
813-
filename,
814-
0,
815-
0,
816-
undefined,
817-
false,
818-
undefined,
819-
[],
820-
[
821-
'exports',
822-
'require',
823-
'module',
824-
'__filename',
825-
'__dirname',
826-
]
827-
);
834+
let compiled;
835+
try {
836+
compiled = compileFunction(
837+
content,
838+
filename,
839+
0,
840+
0,
841+
undefined,
842+
false,
843+
undefined,
844+
[],
845+
[
846+
'exports',
847+
'require',
848+
'module',
849+
'__filename',
850+
'__dirname',
851+
]
852+
);
853+
} catch (err) {
854+
if (experimentalModules) {
855+
enrichCJSError(err);
856+
}
857+
throw err;
858+
}
859+
828860
if (experimentalModules) {
829861
const { callbackMap } = internalBinding('module_wrap');
830862
callbackMap.set(compiled.cacheKey, {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
// Flags: --experimental-modules
2+
3+
import { mustCall } from '../common/index.mjs';
4+
import assert from 'assert';
5+
import fixtures from '../common/fixtures.js';
6+
import { spawn } from 'child_process';
7+
8+
const Export1 = fixtures.path('/es-modules/es-note-unexpected-export-1.cjs');
9+
const Export2 = fixtures.path('/es-modules/es-note-unexpected-export-2.cjs');
10+
const Import1 = fixtures.path('/es-modules/es-note-unexpected-import-1.cjs');
11+
const Import2 = fixtures.path('/es-modules/es-note-promiserej-import-2.cjs');
12+
const Import3 = fixtures.path('/es-modules/es-note-unexpected-import-3.cjs');
13+
const Import4 = fixtures.path('/es-modules/es-note-unexpected-import-4.cjs');
14+
const Import5 = fixtures.path('/es-modules/es-note-unexpected-import-5.cjs');
15+
16+
// Expect note to be included in the error output
17+
const expectedNote = 'To load an ES module, ' +
18+
'set "type": "module" in the package.json ' +
19+
'or use the .mjs extension.';
20+
21+
const expectedCode = 1;
22+
23+
const pExport1 = spawn(process.execPath, ['--experimental-modules', Export1]);
24+
let pExport1Stderr = '';
25+
pExport1.stderr.setEncoding('utf8');
26+
pExport1.stderr.on('data', (data) => {
27+
pExport1Stderr += data;
28+
});
29+
pExport1.on('close', mustCall((code) => {
30+
assert.strictEqual(code, expectedCode);
31+
assert.ok(pExport1Stderr.includes(expectedNote),
32+
`${expectedNote} not found in ${pExport1Stderr}`);
33+
}));
34+
35+
36+
const pExport2 = spawn(process.execPath, ['--experimental-modules', Export2]);
37+
let pExport2Stderr = '';
38+
pExport2.stderr.setEncoding('utf8');
39+
pExport2.stderr.on('data', (data) => {
40+
pExport2Stderr += data;
41+
});
42+
pExport2.on('close', mustCall((code) => {
43+
assert.strictEqual(code, expectedCode);
44+
assert.ok(pExport2Stderr.includes(expectedNote),
45+
`${expectedNote} not found in ${pExport2Stderr}`);
46+
}));
47+
// The flag --experimental-modules is not used here
48+
// the note must not be included in the output
49+
const pExport3 = spawn(process.execPath, [Export1]);
50+
let pExport3Stderr = '';
51+
pExport3.stderr.setEncoding('utf8');
52+
pExport3.stderr.on('data', (data) => {
53+
pExport3Stderr += data;
54+
});
55+
pExport3.on('close', mustCall((code) => {
56+
assert.strictEqual(code, expectedCode);
57+
assert.ok(!pExport3Stderr.includes(expectedNote),
58+
`${expectedNote} must not be included in ${pExport3Stderr}`);
59+
}));
60+
61+
const pImport1 = spawn(process.execPath, ['--experimental-modules', Import1]);
62+
let pImport1Stderr = '';
63+
pImport1.stderr.setEncoding('utf8');
64+
pImport1.stderr.on('data', (data) => {
65+
pImport1Stderr += data;
66+
});
67+
pImport1.on('close', mustCall((code) => {
68+
assert.strictEqual(code, expectedCode);
69+
assert.ok(pImport1Stderr.includes(expectedNote),
70+
`${expectedNote} not found in ${pExport1Stderr}`);
71+
}));
72+
73+
// Note this test shouldn't include the note
74+
const pImport2 = spawn(process.execPath, ['--experimental-modules', Import2]);
75+
let pImport2Stderr = '';
76+
pImport2.stderr.setEncoding('utf8');
77+
pImport2.stderr.on('data', (data) => {
78+
pImport2Stderr += data;
79+
});
80+
pImport2.on('close', mustCall((code) => {
81+
// As this exits normally we expect 0
82+
assert.strictEqual(code, 0);
83+
assert.ok(!pImport2Stderr.includes(expectedNote),
84+
`${expectedNote} must not be included in ${pImport2Stderr}`);
85+
}));
86+
87+
const pImport3 = spawn(process.execPath, ['--experimental-modules', Import3]);
88+
let pImport3Stderr = '';
89+
pImport3.stderr.setEncoding('utf8');
90+
pImport3.stderr.on('data', (data) => {
91+
pImport3Stderr += data;
92+
});
93+
pImport3.on('close', mustCall((code) => {
94+
assert.strictEqual(code, expectedCode);
95+
assert.ok(pImport3Stderr.includes(expectedNote),
96+
`${expectedNote} not found in ${pImport3Stderr}`);
97+
}));
98+
99+
100+
const pImport4 = spawn(process.execPath, ['--experimental-modules', Import4]);
101+
let pImport4Stderr = '';
102+
pImport4.stderr.setEncoding('utf8');
103+
pImport4.stderr.on('data', (data) => {
104+
pImport4Stderr += data;
105+
});
106+
pImport4.on('close', mustCall((code) => {
107+
assert.strictEqual(code, expectedCode);
108+
assert.ok(pImport4Stderr.includes(expectedNote),
109+
`${expectedNote} not found in ${pImport4Stderr}`);
110+
}));
111+
112+
// Must exit with zero and show note
113+
const pImport5 = spawn(process.execPath, ['--experimental-modules', Import5]);
114+
let pImport5Stderr = '';
115+
pImport5.stderr.setEncoding('utf8');
116+
pImport5.stderr.on('data', (data) => {
117+
pImport5Stderr += data;
118+
});
119+
pImport5.on('close', mustCall((code) => {
120+
assert.strictEqual(code, 0);
121+
assert.ok(!pImport5Stderr.includes(expectedNote),
122+
`${expectedNote} must not be included in ${pImport5Stderr}`);
123+
}));
124+
125+
// Must exit with zero and not show note
126+
const pImport6 = spawn(process.execPath, [Import1]);
127+
let pImport6Stderr = '';
128+
pImport6.stderr.setEncoding('utf8');
129+
pImport6.stderr.on('data', (data) => {
130+
pImport6Stderr += data;
131+
});
132+
pImport6.on('close', mustCall((code) => {
133+
assert.strictEqual(code, expectedCode);
134+
assert.ok(!pImport6Stderr.includes(expectedNote),
135+
`${expectedNote} must not be included in ${pImport6Stderr}`);
136+
}));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import('valid');
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
const example = 10;
2+
export default example;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
const config = 10;
2+
export {
3+
config
4+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import "invalid";
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import x, { y } from 'xyz'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import * as coordinates from 'xyz'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import ('invalid')

0 commit comments

Comments
Β (0)