diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js index 95763de0701581..f99d0fc2a7a0eb 100644 --- a/lib/internal/modules/cjs/loader.js +++ b/lib/internal/modules/cjs/loader.js @@ -70,6 +70,7 @@ const { module_export_private_symbol, module_parent_private_symbol, }, + isInsideNodeModules, } = internalBinding('util'); const { kEvaluated, createRequiredModuleFacade } = internalBinding('module_wrap'); @@ -131,6 +132,7 @@ const { setOwnProperty, getLazy, isWindows, + isUnderNodeModules, } = require('internal/util'); const { makeContextifyScript, @@ -1328,6 +1330,7 @@ Module.prototype.require = function(id) { } }; +let emittedRequireModuleWarning = false; /** * Resolve and evaluate it synchronously as ESM if it's ESM. * @param {Module} mod CJS module instance @@ -1347,29 +1350,46 @@ function loadESMFromCJS(mod, filename, format, source) { setOwnProperty(process, 'mainModule', undefined); } else { const parent = mod[kModuleParent]; - let messagePrefix; - if (parent) { - // In the case of the module calling `require()`, it's more useful to know its absolute path. - let from = parent.filename || parent.id; - // In the case of the module being require()d, it's more useful to know the id passed into require(). - const to = mod.id || mod.filename; - if (from === 'internal/preload') { - from = '--require'; - } else if (from === '') { - from = 'The REPL'; - } else if (from === '.') { - from = 'The entry point'; - } else { - from &&= `CommonJS module ${from}`; + + if (!emittedRequireModuleWarning) { + let shouldEmitWarning = false; + // Check if the require() comes from node_modules. + if (parent) { + shouldEmitWarning = !isUnderNodeModules(parent.filename); + } else if (mod[kIsCachedByESMLoader]) { + // It comes from the require() built for `import cjs` and doesn't have a parent recorded + // in the CJS module instance. Inspect the stack trace to see if the require() + // comes from node_modules and reduce the noise. If there are more than 100 frames, + // just give up and assume it is under node_modules. + shouldEmitWarning = !isInsideNodeModules(100, true); } - if (from && to) { - messagePrefix = `${from} is loading ES Module ${to} using require().\n`; + if (shouldEmitWarning) { + let messagePrefix; + if (parent) { + // In the case of the module calling `require()`, it's more useful to know its absolute path. + let from = parent.filename || parent.id; + // In the case of the module being require()d, it's more useful to know the id passed into require(). + const to = mod.id || mod.filename; + if (from === 'internal/preload') { + from = '--require'; + } else if (from === '') { + from = 'The REPL'; + } else if (from === '.') { + from = 'The entry point'; + } else { + from &&= `CommonJS module ${from}`; + } + if (from && to) { + messagePrefix = `${from} is loading ES Module ${to} using require().\n`; + } + } + emitExperimentalWarning('Support for loading ES Module in require()', + messagePrefix, + undefined, + parent?.require); + emittedRequireModuleWarning = true; } } - emitExperimentalWarning('Support for loading ES Module in require()', - messagePrefix, - undefined, - parent?.require); const { wrap, namespace, diff --git a/test/es-module/test-require-node-modules-warning.js b/test/es-module/test-require-node-modules-warning.js new file mode 100644 index 00000000000000..837f174fd28950 --- /dev/null +++ b/test/es-module/test-require-node-modules-warning.js @@ -0,0 +1,59 @@ +'use strict'; + +// This checks the experimental warning for require(esm) is disabled when the +// require() comes from node_modules. +require('../common'); +const { spawnSyncAndAssert } = require('../common/child_process'); +const fixtures = require('../common/fixtures'); + +const warningRE = /Support for loading ES Module in require\(\)/; + +// The fixtures are placed in a directory that includes "node_modules" in its name +// to check false negatives. + +// require() in non-node_modules -> esm in node_modules should warn. +spawnSyncAndAssert( + process.execPath, + [fixtures.path('es-modules', 'test_node_modules', 'require-esm.js')], + { + trim: true, + stderr: warningRE, + stdout: 'world', + } +); + +// require() in non-node_modules -> require() in node_modules -> esm in node_modules +// should not warn. +spawnSyncAndAssert( + process.execPath, + [fixtures.path('es-modules', 'test_node_modules', 'require-require-esm.js')], + { + trim: true, + stderr: '', + stdout: 'world', + } +); + +// Import in non-node_modules -> require() in node_modules -> esm in node_modules +// should not warn. +spawnSyncAndAssert( + process.execPath, + [fixtures.path('es-modules', 'test_node_modules', 'import-require-esm.mjs')], + { + trim: true, + stderr: '', + stdout: 'world', + } +); + +// Import in non-node_modules -> import in node_modules -> +// require() in node_modules -> esm in node_modules should not warn. +spawnSyncAndAssert( + process.execPath, + [fixtures.path('es-modules', 'test_node_modules', 'import-import-require-esm.mjs')], + { + trim: true, + stderr: '', + stdout: 'world', + } +); diff --git a/test/fixtures/es-modules/test_node_modules/import-import-require-esm.mjs b/test/fixtures/es-modules/test_node_modules/import-import-require-esm.mjs new file mode 100644 index 00000000000000..d470088c38caee --- /dev/null +++ b/test/fixtures/es-modules/test_node_modules/import-import-require-esm.mjs @@ -0,0 +1,2 @@ +import mod from 'import-require-esm'; +console.log(mod.hello); diff --git a/test/fixtures/es-modules/test_node_modules/import-require-esm.mjs b/test/fixtures/es-modules/test_node_modules/import-require-esm.mjs new file mode 100644 index 00000000000000..2ad346de9e841c --- /dev/null +++ b/test/fixtures/es-modules/test_node_modules/import-require-esm.mjs @@ -0,0 +1,2 @@ +import mod from 'require-esm'; +console.log(mod.hello); diff --git a/test/fixtures/es-modules/test_node_modules/node_modules/esm/index.js b/test/fixtures/es-modules/test_node_modules/node_modules/esm/index.js new file mode 100644 index 00000000000000..35f468bf4856d8 --- /dev/null +++ b/test/fixtures/es-modules/test_node_modules/node_modules/esm/index.js @@ -0,0 +1 @@ +export const hello = 'world'; diff --git a/test/fixtures/es-modules/test_node_modules/node_modules/esm/package.json b/test/fixtures/es-modules/test_node_modules/node_modules/esm/package.json new file mode 100644 index 00000000000000..07aec65d5a4f31 --- /dev/null +++ b/test/fixtures/es-modules/test_node_modules/node_modules/esm/package.json @@ -0,0 +1,4 @@ +{ + "type": "module", + "main": "index.js" +} diff --git a/test/fixtures/es-modules/test_node_modules/node_modules/import-require-esm/index.js b/test/fixtures/es-modules/test_node_modules/node_modules/import-require-esm/index.js new file mode 100644 index 00000000000000..918bb5d5597e34 --- /dev/null +++ b/test/fixtures/es-modules/test_node_modules/node_modules/import-require-esm/index.js @@ -0,0 +1,2 @@ +import mod from 'require-esm'; +export default mod; diff --git a/test/fixtures/es-modules/test_node_modules/node_modules/import-require-esm/package.json b/test/fixtures/es-modules/test_node_modules/node_modules/import-require-esm/package.json new file mode 100644 index 00000000000000..07aec65d5a4f31 --- /dev/null +++ b/test/fixtures/es-modules/test_node_modules/node_modules/import-require-esm/package.json @@ -0,0 +1,4 @@ +{ + "type": "module", + "main": "index.js" +} diff --git a/test/fixtures/es-modules/test_node_modules/node_modules/require-esm/index.js b/test/fixtures/es-modules/test_node_modules/node_modules/require-esm/index.js new file mode 100644 index 00000000000000..ca6f0c264ad1fc --- /dev/null +++ b/test/fixtures/es-modules/test_node_modules/node_modules/require-esm/index.js @@ -0,0 +1,2 @@ +module.exports = require('esm'); + diff --git a/test/fixtures/es-modules/test_node_modules/require-esm.js b/test/fixtures/es-modules/test_node_modules/require-esm.js new file mode 100644 index 00000000000000..60ad3f7fff60c6 --- /dev/null +++ b/test/fixtures/es-modules/test_node_modules/require-esm.js @@ -0,0 +1,2 @@ +const { hello } = require('esm'); +console.log(hello); diff --git a/test/fixtures/es-modules/test_node_modules/require-require-esm.js b/test/fixtures/es-modules/test_node_modules/require-require-esm.js new file mode 100644 index 00000000000000..9fe255dce258a6 --- /dev/null +++ b/test/fixtures/es-modules/test_node_modules/require-require-esm.js @@ -0,0 +1,2 @@ +const { hello } = require('require-esm'); +console.log(hello);