esm: do not call getSource when format is commonjs

Ensure that `defaultLoad` does not uselessly access the file system to
get the source of modules that are known to be in CommonJS format.

This allows CommonJS imports to resolve in the current phase of the
event loop.

Refs: https://github.com/eslint/eslint/pull/17683
PR-URL: https://github.com/nodejs/node/pull/50465
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
This commit is contained in:
Francesco Trotta 2023-11-29 10:21:54 +01:00 committed by GitHub
parent 4eafcf8ff5
commit 2602c4642b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 69 additions and 9 deletions

View File

@ -132,20 +132,21 @@ async function defaultLoad(url, context = kEmptyObject) {
if (urlInstance.protocol === 'node:') {
source = null;
format ??= 'builtin';
} else {
let contextToPass = context;
} else if (format !== 'commonjs' || defaultType === 'module') {
if (source == null) {
({ responseURL, source } = await getSource(urlInstance, context));
contextToPass = { __proto__: context, source };
context = { __proto__: context, source };
}
// Now that we have the source for the module, run `defaultGetFormat` again in case we detect ESM syntax.
format ??= await defaultGetFormat(urlInstance, contextToPass);
if (format == null) {
// Now that we have the source for the module, run `defaultGetFormat` to detect its format.
format = await defaultGetFormat(urlInstance, context);
if (format === 'commonjs' && contextToPass !== context && defaultType !== 'module') {
// For backward compatibility reasons, we need to discard the source in
// order for the CJS loader to re-fetch it.
source = null;
if (format === 'commonjs') {
// For backward compatibility reasons, we need to discard the source in
// order for the CJS loader to re-fetch it.
source = null;
}
}
}

View File

@ -0,0 +1,19 @@
'use strict';
const common = require('../common');
const fixtures = require('../common/fixtures');
const assert = require('node:assert');
(async () => {
// Make sure that the CommonJS module lexer has been initialized.
// See https://github.com/nodejs/node/blob/v21.1.0/lib/internal/modules/esm/translators.js#L61-L81.
await import(fixtures.fileURL('empty.js'));
let tickDuringCJSImport = false;
process.nextTick(() => { tickDuringCJSImport = true; });
await import(fixtures.fileURL('empty.cjs'));
assert(!tickDuringCJSImport);
})().then(common.mustCall());

View File

@ -0,0 +1,9 @@
import '../common/index.mjs';
import fixtures from '../common/fixtures.js';
import assert from 'node:assert';
let tickDuringCJSImport = false;
process.nextTick(() => { tickDuringCJSImport = true; });
await import(fixtures.fileURL('empty.cjs'));
assert(!tickDuringCJSImport);

View File

@ -0,0 +1,10 @@
// Flags: --experimental-loader ./test/fixtures/es-module-loaders/preset-cjs-source.mjs
import '../common/index.mjs';
import * as fixtures from '../common/fixtures.mjs';
import assert from 'assert';
const { default: existingFileSource } = await import(fixtures.fileURL('es-modules', 'cjs-file.cjs'));
const { default: noSuchFileSource } = await import(new URL('./no-such-file.cjs', import.meta.url));
assert.strictEqual(existingFileSource, 'no .cjs file was read to get this source');
assert.strictEqual(noSuchFileSource, 'no .cjs file was read to get this source');

0
test/fixtures/empty.cjs vendored Normal file
View File

View File

@ -0,0 +1,21 @@
export function resolve(specifier, context, next) {
if (specifier.endsWith('/no-such-file.cjs')) {
// Shortcut to avoid ERR_MODULE_NOT_FOUND for non-existing file, but keep the url for the load hook.
return {
shortCircuit: true,
url: specifier,
};
}
return next(specifier);
}
export function load(href, context, next) {
if (href.endsWith('.cjs')) {
return {
format: 'commonjs',
shortCircuit: true,
source: 'module.exports = "no .cjs file was read to get this source";',
};
}
return next(href);
}