diff --git a/doc/api/cli.md b/doc/api/cli.md
index 62d3d37a061f78..104905d6740c3c 100644
--- a/doc/api/cli.md
+++ b/doc/api/cli.md
@@ -1054,6 +1054,10 @@ following permissions are restricted:
> Stability: 1.1 - Active Development
@@ -1695,6 +1699,22 @@ added: v16.6.0
Use this flag to disable top-level await in REPL.
+### `--no-experimental-require-module`
+
+
+
+> Stability: 1.1 - Active Development
+
+Disable support for loading a synchronous ES module graph in `require()`.
+
+See [Loading ECMAScript modules using `require()`][].
+
### `--no-experimental-websocket`
-This flag is only useful when `--experimental-require-module` is enabled.
-
-If the ES module being `require()`'d contains top-level await, this flag
+If the ES module being `require()`'d contains top-level `await`, this flag
allows Node.js to evaluate the module, try to locate the
top-level awaits, and print their location to help users find them.
diff --git a/doc/api/errors.md b/doc/api/errors.md
index 11aa1b56e9cd4d..64cad66ab50e68 100644
--- a/doc/api/errors.md
+++ b/doc/api/errors.md
@@ -2478,8 +2478,8 @@ Opening a QUIC stream failed.
> Stability: 1 - Experimental
-When trying to `require()` a [ES Module][] under `--experimental-require-module`,
-a CommonJS to ESM or ESM to CommonJS edge participates in an immediate cycle.
+When trying to `require()` a [ES Module][], a CommonJS to ESM or ESM to CommonJS edge
+participates in an immediate cycle.
This is not allowed because ES Modules cannot be evaluated while they are
already being evaluated.
@@ -2493,8 +2493,8 @@ module, and should be done lazily in an inner function.
> Stability: 1 - Experimental
-When trying to `require()` a [ES Module][] under `--experimental-require-module`,
-the module turns out to be asynchronous. That is, it contains top-level await.
+When trying to `require()` a [ES Module][], the module turns out to be asynchronous.
+That is, it contains top-level await.
To see where the top-level await is, use
`--experimental-print-required-tla` (this would execute the modules
@@ -2504,12 +2504,20 @@ before looking for the top-level awaits).
### `ERR_REQUIRE_ESM`
-> Stability: 1 - Experimental
+
+
+> Stability: 0 - Deprecated
An attempt was made to `require()` an [ES Module][].
-To enable `require()` for synchronous module graphs (without
-top-level `await`), use `--experimental-require-module`.
+This error has been deprecated since `require()` now supports loading synchronous
+ES modules. When `require()` encounters an ES module that contains top-level
+`await`, it will throw [`ERR_REQUIRE_ASYNC_MODULE`][] instead.
@@ -4123,6 +4131,7 @@ An error occurred trying to allocate memory. This should never happen.
[`ERR_INVALID_ARG_TYPE`]: #err_invalid_arg_type
[`ERR_MISSING_MESSAGE_PORT_IN_TRANSFER_LIST`]: #err_missing_message_port_in_transfer_list
[`ERR_MISSING_TRANSFERABLE_IN_TRANSFER_LIST`]: #err_missing_transferable_in_transfer_list
+[`ERR_REQUIRE_ASYNC_MODULE`]: #err_require_async_module
[`EventEmitter`]: events.md#class-eventemitter
[`MessagePort`]: worker_threads.md#class-messageport
[`Object.getPrototypeOf`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getPrototypeOf
diff --git a/doc/api/esm.md b/doc/api/esm.md
index 8f2b43563f4c6b..dcb33f512add1d 100644
--- a/doc/api/esm.md
+++ b/doc/api/esm.md
@@ -468,7 +468,7 @@ compatibility.
### `require`
The CommonJS module `require` currently only supports loading synchronous ES
-modules when `--experimental-require-module` is enabled.
+modules (that is, ES modules that do not use top-level `await`).
See [Loading ECMAScript modules using `require()`][] for details.
diff --git a/doc/api/modules.md b/doc/api/modules.md
index 5005bbb85eec2d..1f55fefd793075 100644
--- a/doc/api/modules.md
+++ b/doc/api/modules.md
@@ -171,21 +171,24 @@ relative, and based on the real path of the files making the calls to
## Loading ECMAScript modules using `require()`
-> Stability: 1.1 - Active Development. Enable this API with the
-> [`--experimental-require-module`][] CLI flag.
+> Stability: 1.2 - Release candidate
The `.mjs` extension is reserved for [ECMAScript Modules][].
-Currently, if the flag `--experimental-require-module` is not used, loading
-an ECMAScript module using `require()` will throw a [`ERR_REQUIRE_ESM`][]
-error, and users need to use [`import()`][] instead. See
-[Determining module system][] section for more info
+See [Determining module system][] section for more info
regarding which files are parsed as ECMAScript modules.
-If `--experimental-require-module` is enabled, and the ECMAScript module being
-loaded by `require()` meets the following requirements:
+`require()` only supports loading ECMAScript modules that meet the following requirements:
* The module is fully synchronous (contains no top-level `await`); and
* One of these conditions are met:
@@ -194,8 +197,8 @@ loaded by `require()` meets the following requirements:
3. The file has a `.js` extension, the closest `package.json` does not contain
`"type": "commonjs"`, and the module contains ES module syntax.
-`require()` will load the requested module as an ES Module, and return
-the module namespace object. In this case it is similar to dynamic
+If the ES Module being loaded meet the requirements, `require()` can load it and
+return the module namespace object. In this case it is similar to dynamic
`import()` but is run synchronously and returns the name space object
directly.
@@ -208,13 +211,12 @@ export function distance(a, b) { return (b.x - a.x) ** 2 + (b.y - a.y) ** 2; }
```mjs
// point.mjs
-class Point {
+export default class Point {
constructor(x, y) { this.x = x; this.y = y; }
}
-export default Point;
```
-A CommonJS module can load them with `require()` under `--experimental-detect-module`:
+A CommonJS module can load them with `require()`:
```cjs
const distance = require('./distance.mjs');
@@ -240,16 +242,81 @@ This property is experimental and can change in the future. It should only be us
by tools converting ES modules into CommonJS modules, following existing ecosystem
conventions. Code authored directly in CommonJS should avoid depending on it.
+When a ES Module contains both named exports and a default export, the result returned by `require()`
+is the module namespace object, which places the default export in the `.default` property, similar to
+the results returned by `import()`.
+To customize what should be returned by `require(esm)` directly, the ES Module can export the
+desired value using the string name `"module.exports"`.
+
+
+
+```mjs
+// point.mjs
+export default class Point {
+ constructor(x, y) { this.x = x; this.y = y; }
+}
+
+// `distance` is lost to CommonJS consumers of this module, unless it's
+// added to `Point` as a static property.
+export function distance(a, b) { return (b.x - a.x) ** 2 + (b.y - a.y) ** 2; }
+export { Point as 'module.exports' }
+```
+
+
+
+```cjs
+const Point = require('./point.mjs');
+console.log(Point); // [class Point]
+
+// Named exports are lost when 'module.exports' is used
+const { distance } = require('./point.mjs');
+console.log(distance); // undefined
+```
+
+Notice in the example above, when the `module.exports` export name is used, named exports
+will be lost to CommonJS consumers. To allow CommonJS consumers to continue accessing
+named exports, the module can make sure that the default export is an object with the
+named exports attached to it as properties. For example with the example above,
+`distance` can be attached to the default export, the `Point` class, as a static method.
+
+
+
+```mjs
+export function distance(a, b) { return (b.x - a.x) ** 2 + (b.y - a.y) ** 2; }
+
+export default class Point {
+ constructor(x, y) { this.x = x; this.y = y; }
+ static distance = distance;
+}
+
+export { Point as 'module.exports' }
+```
+
+
+
+```cjs
+const Point = require('./point.mjs');
+console.log(Point); // [class Point]
+
+const { distance } = require('./point.mjs');
+console.log(distance); // [Function: distance]
+```
+
If the module being `require()`'d contains top-level `await`, or the module
graph it `import`s contains top-level `await`,
[`ERR_REQUIRE_ASYNC_MODULE`][] will be thrown. In this case, users should
-load the asynchronous module using `import()`.
+load the asynchronous module using [`import()`][].
If `--experimental-print-required-tla` is enabled, instead of throwing
`ERR_REQUIRE_ASYNC_MODULE` before evaluation, Node.js will evaluate the
module, try to locate the top-level awaits, and print their location to
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.
This feature can be detected by checking if
[`process.features.require_module`][] is `true`.
@@ -282,8 +349,7 @@ require(X) from module at path Y
MAYBE_DETECT_AND_LOAD(X)
1. If X parses as a CommonJS module, load X as a CommonJS module. STOP.
-2. Else, if `--experimental-require-module` is
- enabled, and the source code of X can be parsed as ECMAScript module using
+2. Else, if the source code of X can be parsed as ECMAScript module using
DETECT_MODULE_SYNTAX defined in
the ESM resolver,
a. Load X as an ECMAScript module. STOP.
@@ -1199,9 +1265,7 @@ This section was moved to
[GLOBAL_FOLDERS]: #loading-from-the-global-folders
[`"main"`]: packages.md#main
[`"type"`]: packages.md#type
-[`--experimental-require-module`]: cli.md#--experimental-require-module
[`ERR_REQUIRE_ASYNC_MODULE`]: errors.md#err_require_async_module
-[`ERR_REQUIRE_ESM`]: errors.md#err_require_esm
[`ERR_UNSUPPORTED_DIR_IMPORT`]: errors.md#err_unsupported_dir_import
[`MODULE_NOT_FOUND`]: errors.md#module_not_found
[`__dirname`]: #__dirname
diff --git a/doc/api/packages.md b/doc/api/packages.md
index 5648f56df68996..13100dd65a33e7 100644
--- a/doc/api/packages.md
+++ b/doc/api/packages.md
@@ -172,8 +172,7 @@ There is the CommonJS module loader:
* It treats all files that lack `.json` or `.node` extensions as JavaScript
text files.
* It can only be used to [load ECMASCript modules from CommonJS modules][] if
- the module graph is synchronous (that contains no top-level `await`) when
- `--experimental-require-module` is enabled.
+ the module graph is synchronous (that contains no top-level `await`).
When used to load a JavaScript text file that is not an ECMAScript module,
the file will be loaded as a CommonJS module.
@@ -662,8 +661,7 @@ specific to least specific as conditions should be defined:
* `"require"` - matches when the package is loaded via `require()`. The
referenced file should be loadable with `require()` although the condition
matches regardless of the module format of the target file. Expected
- formats include CommonJS, JSON, native addons, and ES modules
- if `--experimental-require-module` is enabled. _Always mutually
+ formats include CommonJS, JSON, native addons, and ES modules. _Always mutually
exclusive with `"import"`._
* `"module-sync"` - matches no matter the package is loaded via `import`,
`import()` or `require()`. The format is expected to be ES modules that does
diff --git a/lib/buffer.js b/lib/buffer.js
index 6294eae2fb5093..2c56b6c504db9b 100644
--- a/lib/buffer.js
+++ b/lib/buffer.js
@@ -79,10 +79,10 @@ const {
ONLY_ENUMERABLE,
},
getOwnNonIndexProperties,
+ isInsideNodeModules,
} = internalBinding('util');
const {
customInspectSymbol,
- isInsideNodeModules,
lazyDOMException,
normalizeEncoding,
kIsEncodingSymbol,
@@ -178,13 +178,15 @@ function showFlaggedDeprecation() {
if (bufferWarningAlreadyEmitted ||
++nodeModulesCheckCounter > 10000 ||
(!require('internal/options').getOptionValue('--pending-deprecation') &&
- isInsideNodeModules())) {
+ isInsideNodeModules(100, true))) {
// We don't emit a warning, because we either:
// - Already did so, or
// - Already checked too many times whether a call is coming
// from node_modules and want to stop slowing down things, or
// - We aren't running with `--pending-deprecation` enabled,
// and the code is inside `node_modules`.
+ // - We found node_modules in up to the topmost 100 frames, or
+ // there are more than 100 frames and we don't want to search anymore.
return;
}
diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js
index b0210e4d96348c..d28a295c3af9ec 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');
@@ -124,6 +125,7 @@ const { pathToFileURL, fileURLToPath, isURL } = require('internal/url');
const {
pendingDeprecate,
emitExperimentalWarning,
+ isUnderNodeModules,
kEmptyObject,
setOwnProperty,
getLazy,
@@ -146,7 +148,6 @@ const { safeGetenv } = internalBinding('credentials');
const {
getCjsConditions,
initializeCjsConditions,
- isUnderNodeModules,
loadBuiltinModule,
makeRequireFunction,
setHasStartedUserCJSExecution,
@@ -438,7 +439,6 @@ function initializeCJS() {
Module._extensions['.ts'] = loadTS;
}
if (getOptionValue('--experimental-require-module')) {
- emitExperimentalWarning('Support for loading ES Module in require()');
Module._extensions['.mjs'] = loadESMFromCJS;
if (tsEnabled) {
Module._extensions['.mts'] = loadESMFromCJS;
@@ -1343,6 +1343,7 @@ Module.prototype.require = function(id) {
}
};
+let emittedRequireModuleWarning = false;
/**
* Resolved path to `process.argv[1]` will be lazily placed here
* (needed for setting breakpoint when called with `--inspect-brk`).
@@ -1375,10 +1376,51 @@ function loadESMFromCJS(mod, filename) {
// ESM won't be accessible via process.mainModule.
setOwnProperty(process, 'mainModule', undefined);
} else {
+ const parent = mod[kModuleParent];
+
+ 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 (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;
+ }
+ }
const {
wrap,
namespace,
- } = cascadedLoader.importSyncForRequire(mod, filename, source, isMain, mod[kModuleParent]);
+ } = cascadedLoader.importSyncForRequire(mod, filename, source, isMain, parent);
// Tooling in the ecosystem have been using the __esModule property to recognize
// transpiled ESM in consuming code. For example, a 'log' package written in ESM:
//
@@ -1416,10 +1458,13 @@ function loadESMFromCJS(mod, filename) {
// createRequiredModuleFacade() to `wrap` which is a ModuleWrap wrapping
// over the original module.
- // We don't do this to modules that don't have default exports to avoid
- // the unnecessary overhead. If __esModule is already defined, we will
- // also skip the extension to allow users to override it.
- if (!ObjectHasOwn(namespace, 'default') || ObjectHasOwn(namespace, '__esModule')) {
+ // We don't do this to modules that are marked as CJS ESM or that
+ // don't have default exports to avoid the unnecessary overhead.
+ // If __esModule is already defined, we will also skip the extension
+ // to allow users to override it.
+ if (ObjectHasOwn(namespace, 'module.exports')) {
+ mod.exports = namespace['module.exports'];
+ } else if (!ObjectHasOwn(namespace, 'default') || ObjectHasOwn(namespace, '__esModule')) {
mod.exports = namespace;
} else {
mod.exports = createRequiredModuleFacade(wrap);
@@ -1435,7 +1480,7 @@ function loadESMFromCJS(mod, filename) {
* @param {'commonjs'|undefined} format Intended format of the module.
*/
function wrapSafe(filename, content, cjsModuleInstance, format) {
- assert(format !== 'module'); // ESM should be handled in loadESMFromCJS().
+ assert(format !== 'module', 'ESM should be handled in loadESMFromCJS()');
const hostDefinedOptionId = vm_dynamic_import_default_internal;
const importModuleDynamically = vm_dynamic_import_default_internal;
if (patched) {
@@ -1465,7 +1510,17 @@ function wrapSafe(filename, content, cjsModuleInstance, format) {
};
}
- const shouldDetectModule = (format !== 'commonjs' && getOptionValue('--experimental-detect-module'));
+ let shouldDetectModule = false;
+ if (format !== 'commonjs') {
+ if (cjsModuleInstance?.[kIsMainSymbol]) {
+ // For entry points, format detection is used unless explicitly disabled.
+ shouldDetectModule = getOptionValue('--experimental-detect-module');
+ } else {
+ // For modules being loaded by `require()`, if require(esm) is disabled,
+ // don't try to reparse to detect format and just throw for ESM syntax.
+ shouldDetectModule = getOptionValue('--experimental-require-module');
+ }
+ }
const result = compileFunctionForCJSLoader(content, filename, false /* is_sea_main */, shouldDetectModule);
// Cache the source map for the module if present.
@@ -1495,8 +1550,6 @@ Module.prototype._compile = function(content, filename, format) {
}
}
- // TODO(joyeecheung): when the module is the entry point, consider allowing TLA.
- // Only modules being require()'d really need to avoid TLA.
if (format === 'module') {
// Pass the source into the .mjs extension handler indirectly through the cache.
this[kModuleSource] = content;
diff --git a/lib/internal/modules/esm/load.js b/lib/internal/modules/esm/load.js
index 8b157f0f461c7b..d56dae3f001b1c 100644
--- a/lib/internal/modules/esm/load.js
+++ b/lib/internal/modules/esm/load.js
@@ -3,7 +3,10 @@
const {
RegExpPrototypeExec,
} = primordials;
-const { kEmptyObject } = require('internal/util');
+const {
+ isUnderNodeModules,
+ kEmptyObject,
+} = require('internal/util');
const { defaultGetFormat } = require('internal/modules/esm/get_format');
const { validateAttributes, emitImportAssertionWarning } = require('internal/modules/esm/assert');
@@ -14,9 +17,6 @@ const defaultType =
getOptionValue('--experimental-default-type');
const { Buffer: { from: BufferFrom } } = require('buffer');
-const {
- isUnderNodeModules,
-} = require('internal/modules/helpers');
const { URL } = require('internal/url');
const {
@@ -131,11 +131,6 @@ async function defaultLoad(url, context = kEmptyObject) {
validateAttributes(url, format, importAttributes);
- // Use the synchronous commonjs translator which can deal with cycles.
- if (format === 'commonjs' && getOptionValue('--experimental-require-module')) {
- format = 'commonjs-sync';
- }
-
if (getOptionValue('--experimental-strip-types') &&
(format === 'module-typescript' || format === 'commonjs-typescript') &&
isUnderNodeModules(url)) {
@@ -191,11 +186,6 @@ function defaultLoadSync(url, context = kEmptyObject) {
validateAttributes(url, format, importAttributes);
- // Use the synchronous commonjs translator which can deal with cycles.
- if (format === 'commonjs' && getOptionValue('--experimental-require-module')) {
- format = 'commonjs-sync';
- }
-
return {
__proto__: null,
format,
diff --git a/lib/internal/modules/esm/loader.js b/lib/internal/modules/esm/loader.js
index 0337d0ceaa732b..262006f8b3a542 100644
--- a/lib/internal/modules/esm/loader.js
+++ b/lib/internal/modules/esm/loader.js
@@ -373,15 +373,15 @@ class ModuleLoader {
defaultLoadSync ??= require('internal/modules/esm/load').defaultLoadSync;
const loadResult = defaultLoadSync(url, { format, importAttributes });
- const {
- format: finalFormat,
- source,
- } = loadResult;
+
+ // Use the synchronous commonjs translator which can deal with cycles.
+ const finalFormat = loadResult.format === 'commonjs' ? 'commonjs-sync' : loadResult.format;
if (finalFormat === 'wasm') {
assert.fail('WASM is currently unsupported by require(esm)');
}
+ const { source } = loadResult;
const isMain = (parentURL === undefined);
const wrap = this.#translate(url, finalFormat, source, isMain);
assert(wrap instanceof ModuleWrap, `Translator used for require(${url}) should not be async`);
diff --git a/lib/internal/modules/esm/module_job.js b/lib/internal/modules/esm/module_job.js
index 62206fcc44c2d1..ece30c3864d6a7 100644
--- a/lib/internal/modules/esm/module_job.js
+++ b/lib/internal/modules/esm/module_job.js
@@ -22,7 +22,7 @@ let debug = require('internal/util/debuglog').debuglog('esm', (fn) => {
debug = fn;
});
-const { ModuleWrap, kEvaluated } = internalBinding('module_wrap');
+const { ModuleWrap, kInstantiated } = internalBinding('module_wrap');
const {
privateSymbols: {
entry_point_module_private_symbol,
@@ -354,13 +354,30 @@ class ModuleJobSync extends ModuleJobBase {
}
async run() {
+ // This path is hit by a require'd module that is imported again.
const status = this.module.getStatus();
- assert(status === kEvaluated,
- `A require()-d module that is imported again must be evaluated. Status = ${status}`);
- return { __proto__: null, module: this.module };
+ if (status > kInstantiated) {
+ if (this.evaluationPromise) {
+ await this.evaluationPromise;
+ }
+ return { __proto__: null, module: this.module };
+ } else if (status === kInstantiated) {
+ // The evaluation may have been canceled because instantiateSync() detected TLA first.
+ // But when it is imported again, it's fine to re-evaluate it asynchronously.
+ const timeout = -1;
+ const breakOnSigint = false;
+ this.evaluationPromise = this.module.evaluate(timeout, breakOnSigint);
+ await this.evaluationPromise;
+ this.evaluationPromise = undefined;
+ return { __proto__: null, module: this.module };
+ }
+
+ assert.fail('Unexpected status of a module that is imported again after being required. ' +
+ `Status = ${status}`);
}
runSync() {
+ // TODO(joyeecheung): add the error decoration logic from the async instantiate.
this.module.instantiateSync();
setHasStartedUserESMExecution();
const namespace = this.module.evaluateSync();
diff --git a/lib/internal/modules/esm/utils.js b/lib/internal/modules/esm/utils.js
index 2799af0f8dd492..99061e62976e7c 100644
--- a/lib/internal/modules/esm/utils.js
+++ b/lib/internal/modules/esm/utils.js
@@ -75,17 +75,15 @@ function initializeDefaultConditions() {
const userConditions = getOptionValue('--conditions');
const noAddons = getOptionValue('--no-addons');
const addonConditions = noAddons ? [] : ['node-addons'];
-
+ const moduleConditions = getOptionValue('--experimental-require-module') ? ['module-sync'] : [];
defaultConditions = ObjectFreeze([
'node',
'import',
+ ...moduleConditions,
...addonConditions,
...userConditions,
]);
defaultConditionsSet = new SafeSet(defaultConditions);
- if (getOptionValue('--experimental-require-module')) {
- defaultConditionsSet.add('module-sync');
- }
}
/**
diff --git a/lib/internal/modules/helpers.js b/lib/internal/modules/helpers.js
index 1add21bb541cf4..11adec360cc98c 100644
--- a/lib/internal/modules/helpers.js
+++ b/lib/internal/modules/helpers.js
@@ -2,7 +2,6 @@
const {
ArrayPrototypeForEach,
- ArrayPrototypeIncludes,
ObjectDefineProperty,
ObjectFreeze,
ObjectPrototypeHasOwnProperty,
@@ -11,7 +10,6 @@ const {
StringPrototypeCharCodeAt,
StringPrototypeIncludes,
StringPrototypeSlice,
- StringPrototypeSplit,
StringPrototypeStartsWith,
} = primordials;
const {
@@ -380,12 +378,6 @@ function stripTypeScriptTypes(source, filename) {
return `${code}\n\n//# sourceURL=${filename}`;
}
-function isUnderNodeModules(filename) {
- const resolvedPath = path.resolve(filename);
- const normalizedPath = path.normalize(resolvedPath);
- const splitPath = StringPrototypeSplit(normalizedPath, path.sep);
- return ArrayPrototypeIncludes(splitPath, 'node_modules');
-}
/**
* Enable on-disk compiled cache for all user modules being complied in the current Node.js instance
@@ -486,7 +478,6 @@ module.exports = {
getCjsConditions,
getCompileCacheDir,
initializeCjsConditions,
- isUnderNodeModules,
loadBuiltinModule,
makeRequireFunction,
normalizeReferrerURL,
diff --git a/lib/internal/util.js b/lib/internal/util.js
index 0973d4efdf2257..3fc0dc9c207fb5 100644
--- a/lib/internal/util.js
+++ b/lib/internal/util.js
@@ -2,7 +2,6 @@
const {
ArrayFrom,
- ArrayIsArray,
ArrayPrototypePush,
ArrayPrototypeSlice,
ArrayPrototypeSort,
@@ -34,9 +33,7 @@ const {
SafeSet,
SafeWeakMap,
SafeWeakRef,
- StringPrototypeIncludes,
StringPrototypeReplace,
- StringPrototypeStartsWith,
StringPrototypeToLowerCase,
StringPrototypeToUpperCase,
Symbol,
@@ -259,11 +256,20 @@ function slowCases(enc) {
}
}
-function emitExperimentalWarning(feature) {
+/**
+ * @param {string} feature Feature name used in the warning message
+ * @param {string} messagePrefix Prefix of the warning message
+ * @param {string} code See documentation of process.emitWarning
+ * @param {string} ctor See documentation of process.emitWarning
+ */
+function emitExperimentalWarning(feature, messagePrefix, code, ctor) {
if (experimentalWarnings.has(feature)) return;
- const msg = `${feature} is an experimental feature and might change at any time`;
experimentalWarnings.add(feature);
- process.emitWarning(msg, 'ExperimentalWarning');
+ let msg = `${feature} is an experimental feature and might change at any time`;
+ if (messagePrefix) {
+ msg = messagePrefix + msg;
+ }
+ process.emitWarning(msg, 'ExperimentalWarning', code, ctor);
}
function filterDuplicateStrings(items, low) {
@@ -481,6 +487,10 @@ function spliceOne(list, index) {
const kNodeModulesRE = /^(?:.*)[\\/]node_modules[\\/]/;
+function isUnderNodeModules(filename) {
+ return filename && (RegExpPrototypeExec(kNodeModulesRE, filename) !== null);
+}
+
let getStructuredStackImpl;
function lazyGetStructuredStack() {
@@ -509,31 +519,6 @@ function getStructuredStack() {
return getStructuredStackImpl();
}
-function isInsideNodeModules() {
- const stack = getStructuredStack();
-
- // Iterate over all stack frames and look for the first one not coming
- // from inside Node.js itself:
- if (ArrayIsArray(stack)) {
- for (const frame of stack) {
- const filename = frame.getFileName();
-
- if (
- filename == null ||
- StringPrototypeStartsWith(filename, 'node:') === true ||
- (
- filename[0] !== '/' &&
- StringPrototypeIncludes(filename, '\\') === false
- )
- ) {
- continue;
- }
- return RegExpPrototypeExec(kNodeModulesRE, filename) !== null;
- }
- }
- return false;
-}
-
function once(callback, { preserveReturnValue = false } = kEmptyObject) {
let called = false;
let returnValue;
@@ -907,7 +892,7 @@ module.exports = {
getSystemErrorName,
guessHandleType,
isError,
- isInsideNodeModules,
+ isUnderNodeModules,
isMacOS,
isWindows,
join,
diff --git a/src/node_options.cc b/src/node_options.cc
index d3b59690e917af..4bdb5f97a03ed5 100644
--- a/src/node_options.cc
+++ b/src/node_options.cc
@@ -374,9 +374,10 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
&EnvironmentOptions::print_required_tla,
kAllowedInEnvvar);
AddOption("--experimental-require-module",
- "Allow loading explicit ES Modules in require().",
+ "Allow loading synchronous ES Modules in require().",
&EnvironmentOptions::require_module,
- kAllowedInEnvvar);
+ kAllowedInEnvvar,
+ true);
AddOption("--diagnostic-dir",
"set dir for all output files"
" (default: current working directory)",
diff --git a/src/node_options.h b/src/node_options.h
index fc7f898a6b9b60..b3246f5dcb3ab1 100644
--- a/src/node_options.h
+++ b/src/node_options.h
@@ -117,7 +117,7 @@ class EnvironmentOptions : public Options {
std::vector conditions;
bool detect_module = true;
bool print_required_tla = false;
- bool require_module = false;
+ bool require_module = true;
std::string dns_result_order;
bool enable_source_maps = false;
bool experimental_eventsource = false;
diff --git a/src/node_util.cc b/src/node_util.cc
index c99e26752c7d93..77da3235182efe 100644
--- a/src/node_util.cc
+++ b/src/node_util.cc
@@ -298,6 +298,48 @@ static void GetCallSite(const FunctionCallbackInfo& args) {
args.GetReturnValue().Set(callsites);
}
+static void IsInsideNodeModules(const FunctionCallbackInfo& args) {
+ Isolate* isolate = args.GetIsolate();
+ CHECK_EQ(args.Length(), 2);
+ CHECK(args[0]->IsInt32()); // frame_limit
+ // The second argument is the default value.
+
+ int frames_limit = args[0].As()->Value();
+ Local stack =
+ StackTrace::CurrentStackTrace(isolate, frames_limit);
+ int frame_count = stack->GetFrameCount();
+
+ // If the search requires looking into more than |frames_limit| frames, give
+ // up and return the specified default value.
+ if (frame_count == frames_limit) {
+ return args.GetReturnValue().Set(args[1]);
+ }
+
+ bool result = false;
+ for (int i = 0; i < frame_count; ++i) {
+ Local stack_frame = stack->GetFrame(isolate, i);
+ Local script_name = stack_frame->GetScriptName();
+
+ if (script_name.IsEmpty() || script_name->Length() == 0) {
+ continue;
+ }
+ Utf8Value script_name_utf8(isolate, script_name);
+ std::string_view script_name_str = script_name_utf8.ToStringView();
+ if (script_name_str.starts_with("node:")) {
+ continue;
+ }
+ if (script_name_str.find("/node_modules/") != std::string::npos ||
+ script_name_str.find("\\node_modules\\") != std::string::npos ||
+ script_name_str.find("/node_modules\\") != std::string::npos ||
+ script_name_str.find("\\node_modules/") != std::string::npos) {
+ result = true;
+ break;
+ }
+ }
+
+ args.GetReturnValue().Set(result);
+}
+
void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
registry->Register(GetPromiseDetails);
registry->Register(GetProxyDetails);
@@ -313,6 +355,7 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
registry->Register(FastGuessHandleType);
registry->Register(fast_guess_handle_type_.GetTypeInfo());
registry->Register(ParseEnv);
+ registry->Register(IsInsideNodeModules);
}
void Initialize(Local