diff --git a/lib/internal/modules/esm/load.js b/lib/internal/modules/esm/load.js index bcebc283828ad5..70d1b7fe83fa35 100644 --- a/lib/internal/modules/esm/load.js +++ b/lib/internal/modules/esm/load.js @@ -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; + } } } diff --git a/test/es-module/test-esm-dynamic-import-commonjs.js b/test/es-module/test-esm-dynamic-import-commonjs.js new file mode 100644 index 00000000000000..9022b83f1fcffa --- /dev/null +++ b/test/es-module/test-esm-dynamic-import-commonjs.js @@ -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()); diff --git a/test/es-module/test-esm-dynamic-import-commonjs.mjs b/test/es-module/test-esm-dynamic-import-commonjs.mjs new file mode 100644 index 00000000000000..6a8f2506c0eb05 --- /dev/null +++ b/test/es-module/test-esm-dynamic-import-commonjs.mjs @@ -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); diff --git a/test/es-module/test-esm-loader-with-source.mjs b/test/es-module/test-esm-loader-with-source.mjs new file mode 100644 index 00000000000000..41dca12dfc6bab --- /dev/null +++ b/test/es-module/test-esm-loader-with-source.mjs @@ -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'); diff --git a/test/fixtures/empty.cjs b/test/fixtures/empty.cjs new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/test/fixtures/es-module-loaders/preset-cjs-source.mjs b/test/fixtures/es-module-loaders/preset-cjs-source.mjs new file mode 100644 index 00000000000000..2cfa851a23f889 --- /dev/null +++ b/test/fixtures/es-module-loaders/preset-cjs-source.mjs @@ -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); +}