Skip to content

Commit 402675a

Browse files
committed
module: resolve format for all situations with auto module detection on
1 parent d4442a9 commit 402675a

12 files changed

+43
-46
lines changed

lib/internal/modules/esm/get_format.js

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,12 @@ let typelessPackageJsonFilesWarnedAbout;
9292
function getFileProtocolModuleFormat(url, context = { __proto__: null }, ignoreErrors) {
9393
const { source } = context;
9494
const ext = extname(url);
95+
const deduceFormat = (fromSource, fromUrl) => {
96+
const { existsSync, readFileSync } = require('fs');
97+
const realSource = fromSource ?? existsSync(fromUrl) ? readFileSync(fromUrl).toString() : undefined;
98+
return realSource ? // Do we have a source? check for module syntax
99+
(containsModuleSyntax(realSource, fileURLToPath(fromUrl), fromUrl) ? 'module' : 'commonjs') : 'commonjs';
100+
};
95101

96102
if (ext === '.js') {
97103
const { type: packageType, pjsonPath } = getPackageScopeConfig(url);
@@ -113,9 +119,7 @@ function getFileProtocolModuleFormat(url, context = { __proto__: null }, ignoreE
113119
// `source` is undefined when this is called from `defaultResolve`;
114120
// but this gets called again from `defaultLoad`/`defaultLoadSync`.
115121
if (getOptionValue('--experimental-detect-module')) {
116-
const format = source ?
117-
(containsModuleSyntax(`${source}`, fileURLToPath(url), url) ? 'module' : 'commonjs') :
118-
null;
122+
const format = deduceFormat(source, url);
119123
if (format === 'module') {
120124
// This module has a .js extension, a package.json with no `type` field, and ESM syntax.
121125
// Warn about the missing `type` field so that the user can avoid the performance penalty of detection.
@@ -155,12 +159,8 @@ function getFileProtocolModuleFormat(url, context = { __proto__: null }, ignoreE
155159
}
156160
default: { // The user did not pass `--experimental-default-type`.
157161
if (getOptionValue('--experimental-detect-module')) {
158-
if (!source) { return null; }
159162
const format = getFormatOfExtensionlessFile(url);
160-
if (format === 'module') {
161-
return containsModuleSyntax(`${source}`, fileURLToPath(url), url) ? 'module' : 'commonjs';
162-
}
163-
return format;
163+
return (format === 'module') ? deduceFormat(source, url) : format;
164164
}
165165
return 'commonjs';
166166
}

lib/internal/modules/esm/loader.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ class ModuleLoader {
321321
* @returns {ModuleJobBase}
322322
*/
323323
getModuleJobForRequire(specifier, parentURL, importAttributes) {
324-
assert(getOptionValue('--experimental-require-module'));
324+
assert(getOptionValue('--experimental-require-module') || getOptionValue('--experimental-detect-module'));
325325

326326
if (canParse(specifier)) {
327327
const protocol = new URL(specifier).protocol;

src/node_options.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ class EnvironmentOptions : public Options {
110110
public:
111111
bool abort_on_uncaught_exception = false;
112112
std::vector<std::string> conditions;
113-
bool detect_module = false;
113+
bool detect_module = true;
114114
bool print_required_tla = false;
115115
bool require_module = false;
116116
std::string dns_result_order;

test/es-module/test-esm-cjs-exports.js

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,17 @@ describe('ESM: importing CJS', { concurrency: !process.env.TEST_PARALLEL }, () =
1717
assert.strictEqual(stdout, 'ok\n');
1818
});
1919

20-
it('should error on invalid CJS exports', async () => {
21-
const invalidEntry = fixtures.path('/es-modules/cjs-exports-invalid.mjs');
22-
const { code, signal, stderr } = await spawnPromisified(execPath, [invalidEntry]);
20+
it('should not error if .mjs imports .js with ESM syntax', async () => {
21+
// a .mjs that imports a .js gets automatically correctly resolved by automatic syntax detection
22+
// a warning that this detection happened implicitly is being generated.
23+
const cjsExportsEntry = fixtures.path('/es-modules/cjs-exports-import.mjs');
24+
const { code, signal, stderr, stdout } = await spawnPromisified(execPath, [cjsExportsEntry]);
2325

24-
assert.strictEqual(code, 1);
26+
console.log(`stderr: ${stderr}`);
27+
console.log(`stdout: ${stdout}`);
28+
assert.strictEqual(code, 0);
2529
assert.strictEqual(signal, null);
26-
assert.ok(stderr.includes('Warning: To load an ES module'));
27-
assert.ok(stderr.includes('Unexpected token \'export\''));
30+
assert.strictEqual(stdout, '');
31+
assert.match(stderr, /[MODULE_TYPELESS_PACKAGE_JSON]/);
2832
});
2933
});

test/es-module/test-esm-detect-ambiguous.mjs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,8 @@ describe('--experimental-detect-module', { concurrency: !process.env.TEST_PARALL
172172
]);
173173

174174
strictEqual(stderr, '');
175-
strictEqual(stdout, 'null\nexecuted\n');
175+
// 'module' format resolved correctly because noext-esm contains ESM syntax
176+
strictEqual(stdout, 'module\nexecuted\n');
176177
strictEqual(code, 0);
177178
strictEqual(signal, null);
178179

test/es-module/test-esm-extensionless-esm-and-wasm.mjs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,14 +61,16 @@ describe('extensionless Wasm modules within a "type": "module" package scope', {
6161

6262
describe('extensionless ES modules within no package scope', { concurrency: !process.env.TEST_PARALLEL }, () => {
6363
// This succeeds with `--experimental-default-type=module`
64-
it('should error as the entry point', async () => {
64+
it('should not error as the entry point', async () => {
65+
// Should not error because detect-module recognizes the ESM syntax
6566
const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [
67+
'--no-warnings',
6668
fixtures.path('es-modules/noext-esm'),
6769
]);
6870

69-
match(stderr, /SyntaxError/);
70-
strictEqual(stdout, '');
71-
strictEqual(code, 1);
71+
strictEqual(stderr, '');
72+
match(stdout, /executed/);
73+
strictEqual(code, 0);
7274
strictEqual(signal, null);
7375
});
7476

test/es-module/test-esm-import-flag.mjs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ describe('import modules using --import', { concurrency: !process.env.TEST_PARAL
135135
assert.strictEqual(signal, null);
136136
});
137137

138-
it('should import when running --check fails', async () => {
138+
it('should import when running --check succeed', async () => {
139139
const { code, signal, stderr, stdout } = await spawnPromisified(
140140
execPath,
141141
[
@@ -146,9 +146,9 @@ describe('import modules using --import', { concurrency: !process.env.TEST_PARAL
146146
]
147147
);
148148

149-
assert.match(stderr, /SyntaxError: Unexpected token 'export'/);
149+
assert.strictEqual(stderr, '');
150150
assert.match(stdout, /^\.mjs file\r?\n$/);
151-
assert.strictEqual(code, 1);
151+
assert.strictEqual(code, 0);
152152
assert.strictEqual(signal, null);
153153
});
154154

test/es-module/test-esm-resolve-type.mjs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,14 +52,12 @@ try {
5252
/**
5353
* create a test module and try to resolve it by module name.
5454
* check the result is as expected
55-
*
56-
* for test-module-ne: everything .js that is not 'module' is 'commonjs'
5755
*/
5856
for (const [ moduleName, moduleExtenstion, moduleType, expectedResolvedType ] of
5957
[ [ 'test-module-mainjs', 'js', 'module', 'module'],
6058
[ 'test-module-mainmjs', 'mjs', 'module', 'module'],
6159
[ 'test-module-cjs', 'js', 'commonjs', 'commonjs'],
62-
[ 'test-module-ne', 'js', undefined, 'commonjs'],
60+
[ 'test-module-ne', 'js', undefined, 'module'], // Resolves to module because syntax was recognized as esm
6361
]) {
6462
process.chdir(previousCwd);
6563
tmpdir.refresh();

test/es-module/test-require-module-implicit.js

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,14 @@
22
'use strict';
33

44
// Tests that require()ing modules without explicit module type information
5-
// warns and errors.
5+
// does not warn or error. It works instead.
66
require('../common');
77
const assert = require('assert');
88
const { isModuleNamespaceObject } = require('util/types');
9+
const { strictEqual } = require('node:assert');
910

10-
assert.throws(() => {
11-
require('../fixtures/es-modules/package-without-type/noext-esm');
12-
}, {
13-
message: /Unexpected token 'export'/
14-
});
15-
16-
assert.throws(() => {
17-
require('../fixtures/es-modules/loose.js');
18-
}, {
19-
message: /Unexpected token 'export'/
20-
});
11+
strictEqual(require('../fixtures/es-modules/package-without-type/noext-esm').default, 'module');
12+
strictEqual(require('../fixtures/es-modules/loose.js').default, 'module');
2113

2214
{
2315
// .mjs should not be matched as default extensions.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
import * as mjs_from_js from './invalid-cjs.js';
2+
export default mjs_from_js;

0 commit comments

Comments
 (0)