module: only emit require(esm) warning under --trace-require-module

require(esm) is relatively stable now and the experimental warning
has run its course - it's now more troublesome than useful.
This patch changes it to no longer emit a warning unless
`--trace-require-module` is explicitly used. The flag supports
two modes:

- `--trace-require-module=all`: emit warnings for all usages
- `--trace-require-module=no-node-modules`: emit warnings for
  usages that do not come from a `node_modules` folder.

PR-URL: https://github.com/nodejs/node/pull/56194
Fixes: https://github.com/nodejs/node/issues/55417
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Geoffrey Booth <webadmin@geoffreybooth.com>
Reviewed-By: Marco Ippolito <marcoippolito54@gmail.com>
Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com>
This commit is contained in:
Joyee Cheung
2024-12-09 15:47:25 +01:00
committed by Node.js GitHub Bot
parent 586814bcd9
commit b6c9dbe7e1
11 changed files with 80 additions and 48 deletions

View File

@@ -2616,6 +2616,18 @@ added:
Prints a stack trace whenever an environment is exited proactively,
i.e. invoking `process.exit()`.
### `--trace-require-module=mode`
<!-- YAML
added:
- REPLACEME
-->
Prints information about usage of [Loading ECMAScript modules using `require()`][].
When `mode` is `all`, all usage is printed. When `mode` is `no-node-modules`, usage
from the `node_modules` folder is excluded.
### `--trace-sigint`
<!-- YAML
@@ -3126,6 +3138,7 @@ one is included in the list below.
* `--trace-event-file-pattern`
* `--trace-events-enabled`
* `--trace-exit`
* `--trace-require-module`
* `--trace-sigint`
* `--trace-sync-io`
* `--trace-tls`

View File

@@ -175,6 +175,11 @@ added:
- v22.0.0
- v20.17.0
changes:
- version:
- REPLACEME
pr-url: https://github.com/nodejs/node/pull/56194
description: This feature no longer emits an experimental warning by default,
though the warning can still be emitted by --trace-require-module.
- version:
- v23.0.0
- v22.12.0
@@ -319,9 +324,8 @@ help users fix them.
Support for loading ES modules using `require()` is currently
experimental and can be disabled using `--no-experimental-require-module`.
When `require()` actually encounters an ES module for the
first time in the process, it will emit an experimental warning. The
warning is expected to be removed when this feature stablizes.
To print where this feature is used, use [`--trace-require-module`][].
This feature can be detected by checking if
[`process.features.require_module`][] is `true`.
@@ -1271,6 +1275,7 @@ This section was moved to
[GLOBAL_FOLDERS]: #loading-from-the-global-folders
[`"main"`]: packages.md#main
[`"type"`]: packages.md#type
[`--trace-require-module`]: cli.md#--trace-require-modulemode
[`ERR_REQUIRE_ASYNC_MODULE`]: errors.md#err_require_async_module
[`ERR_UNSUPPORTED_DIR_IMPORT`]: errors.md#err_unsupported_dir_import
[`MODULE_NOT_FOUND`]: errors.md#module_not_found

View File

@@ -1499,7 +1499,7 @@ Module.prototype.require = function(id) {
}
};
let emittedRequireModuleWarning = false;
let requireModuleWarningMode;
/**
* Resolve and evaluate it synchronously as ESM if it's ESM.
* @param {Module} mod CJS module instance
@@ -1520,17 +1520,22 @@ function loadESMFromCJS(mod, filename, format, source) {
} else {
const parent = mod[kModuleParent];
if (!emittedRequireModuleWarning) {
requireModuleWarningMode ??= getOptionValue('--trace-require-module');
if (requireModuleWarningMode) {
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 (requireModuleWarningMode === 'no-node-modules') {
// 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);
}
} else {
shouldEmitWarning = true;
}
if (shouldEmitWarning) {
let messagePrefix;
@@ -1556,7 +1561,7 @@ function loadESMFromCJS(mod, filename, format, source) {
messagePrefix,
undefined,
parent?.require);
emittedRequireModuleWarning = true;
requireModuleWarningMode = true;
}
}
const {

View File

@@ -135,6 +135,11 @@ void EnvironmentOptions::CheckOptions(std::vector<std::string>* errors,
errors->push_back("--heapsnapshot-near-heap-limit must not be negative");
}
if (!trace_require_module.empty() && trace_require_module != "all" &&
trace_require_module != "no-node-modules") {
errors->push_back("invalid value for --trace-require-module");
}
if (test_runner) {
if (test_isolation == "none") {
debug_options_.allow_attaching_debugger = true;
@@ -771,6 +776,13 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
&EnvironmentOptions::trace_env_native_stack,
kAllowedInEnvvar);
AddOption(
"--trace-require-module",
"Print access to require(esm). Options are 'all' (print all usage) and "
"'no-node-modules' (excluding usage from the node_modules folder)",
&EnvironmentOptions::trace_require_module,
kAllowedInEnvvar);
AddOption("--extra-info-on-fatal-exception",
"hide extra information on fatal exception that causes exit",
&EnvironmentOptions::extra_info_on_fatal_exception,

View File

@@ -210,6 +210,7 @@ class EnvironmentOptions : public Options {
bool trace_env = false;
bool trace_env_js_stack = false;
bool trace_env_native_stack = false;
std::string trace_require_module;
bool extra_info_on_fatal_exception = true;
std::string unhandled_rejections;
std::vector<std::string> userland_loaders;

View File

@@ -5,8 +5,6 @@ const { spawnSyncAndAssert } = require('../common/child_process');
const { fixturesDir } = require('../common/fixtures');
function testPreload(preloadFlag) {
// The warning is only emitted when ESM is loaded by --require.
const isRequire = preloadFlag === '--require';
// Test named exports.
{
spawnSyncAndAssert(
@@ -22,8 +20,6 @@ function testPreload(preloadFlag) {
},
{
stdout: 'A',
stderr: isRequire ?
/ExperimentalWarning: --require is loading ES Module .*module-named-exports\.mjs using require/ : undefined,
trim: true,
}
);
@@ -43,8 +39,6 @@ function testPreload(preloadFlag) {
cwd: fixturesDir
},
{
stderr: isRequire ?
/ExperimentalWarning: --require is loading ES Module .*import-esm\.mjs using require/ : undefined,
stdout: /^world\s+A$/,
trim: true,
}
@@ -66,8 +60,6 @@ function testPreload(preloadFlag) {
},
{
stdout: /^ok\s+A$/,
stderr: isRequire ?
/ExperimentalWarning: --require is loading ES Module .*cjs-exports\.mjs using require/ : undefined,
trim: true,
}
);
@@ -90,8 +82,6 @@ function testPreload(preloadFlag) {
},
{
stdout: /^world\s+A$/,
stderr: isRequire ?
/ExperimentalWarning: --require is loading ES Module .*require-cjs\.mjs using require/ : undefined,
trim: true,
}
);
@@ -117,7 +107,6 @@ testPreload('--import');
},
{
stdout: /^package-type-module\s+A$/,
stderr: /ExperimentalWarning: --require is loading ES Module .*package-type-module[\\/]index\.js using require/,
trim: true,
}
);

View File

@@ -1,8 +1,6 @@
'use strict';
// This checks the warning and the stack trace emitted by the require(esm)
// experimental warning. It can get removed when `require(esm)` becomes stable.
// This checks the warning and the stack trace emitted by --trace-require-module=all.
require('../common');
const { spawnSyncAndAssert } = require('../common/child_process');
const fixtures = require('../common/fixtures');
@@ -10,6 +8,7 @@ const assert = require('assert');
spawnSyncAndAssert(process.execPath, [
'--trace-warnings',
'--trace-require-module=all',
fixtures.path('es-modules', 'require-module.js'),
], {
trim: true,
@@ -33,3 +32,12 @@ spawnSyncAndAssert(process.execPath, [
);
}
});
spawnSyncAndAssert(process.execPath, [
'--trace-require-module=1',
fixtures.path('es-modules', 'require-module.js'),
], {
status: 9,
trim: true,
stderr: /invalid value for --trace-require-module/
});

View File

@@ -3,16 +3,6 @@
const common = require('../common');
const assert = require('assert');
const path = require('path');
// Only the first load will trigger the warning.
common.expectWarning(
'ExperimentalWarning',
`CommonJS module ${__filename} is loading ES Module ` +
`${path.resolve(__dirname, '../fixtures/es-module-loaders/module-named-exports.mjs')} using require().\n` +
'Support for loading ES Module in require() is an experimental feature ' +
'and might change at any time'
);
// Test named exports.
{

View File

@@ -1,7 +1,7 @@
'use strict';
// This checks the experimental warning for require(esm) is disabled when the
// require() comes from node_modules.
// This checks the warning and the stack trace emitted by
// --trace-require-module=no-node-modules.
require('../common');
const { spawnSyncAndAssert } = require('../common/child_process');
const fixtures = require('../common/fixtures');
@@ -14,7 +14,10 @@ const warningRE = /Support for loading ES Module in require\(\)/;
// require() in non-node_modules -> esm in node_modules should warn.
spawnSyncAndAssert(
process.execPath,
[fixtures.path('es-modules', 'test_node_modules', 'require-esm.js')],
[
'--trace-require-module=no-node-modules',
fixtures.path('es-modules', 'test_node_modules', 'require-esm.js'),
],
{
trim: true,
stderr: warningRE,
@@ -26,7 +29,10 @@ spawnSyncAndAssert(
// should not warn.
spawnSyncAndAssert(
process.execPath,
[fixtures.path('es-modules', 'test_node_modules', 'require-require-esm.js')],
[
'--trace-require-module=no-node-modules',
fixtures.path('es-modules', 'test_node_modules', 'require-require-esm.js'),
],
{
trim: true,
stderr: '',
@@ -38,7 +44,10 @@ spawnSyncAndAssert(
// should not warn.
spawnSyncAndAssert(
process.execPath,
[fixtures.path('es-modules', 'test_node_modules', 'import-require-esm.mjs')],
[
'--trace-require-module=no-node-modules',
fixtures.path('es-modules', 'test_node_modules', 'import-require-esm.mjs'),
],
{
trim: true,
stderr: '',
@@ -50,7 +59,10 @@ spawnSyncAndAssert(
// 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')],
[
'--trace-require-module=no-node-modules',
fixtures.path('es-modules', 'test_node_modules', 'import-import-require-esm.mjs'),
],
{
trim: true,
stderr: '',

View File

@@ -116,7 +116,6 @@ test('execute a .cts file importing a .mts file export', async () => {
fixtures.path('typescript/cts/test-require-mts-module.cts'),
]);
match(result.stderr, /Support for loading ES Module in require\(\) is an experimental feature and might change at any time/);
match(result.stdout, /Hello, TypeScript!/);
strictEqual(result.code, 0);
});

View File

@@ -175,7 +175,6 @@ test('expect failure of a TypeScript file requiring ES module syntax', async ()
fixtures.path('typescript/ts/test-require-module.ts'),
]);
match(result.stderr, /Support for loading ES Module in require\(\) is an experimental feature and might change at any time/);
match(result.stdout, /Hello, TypeScript!/);
strictEqual(result.code, 0);
});
@@ -231,7 +230,6 @@ test('execute a TypeScript file with CommonJS syntax requiring .mts using requir
fixtures.path('typescript/ts/test-require-mts.ts'),
]);
match(result.stderr, /Support for loading ES Module in require\(\) is an experimental feature and might change at any time/);
match(result.stdout, /Hello, TypeScript!/);
strictEqual(result.code, 0);
});