Skip to content

Commit 1ac1dda

Browse files
joyeecheungmarco-ippolito
authored andcommitted
module: unflag --experimental-require-module
This unflags --experimental-require-module so require(esm) can be used without the flag. For now, when require() actually encounters an ESM, it will still emit an experimental warning. To opt out of the feature, --no-experimental-require-module can be used. There are some tests specifically testing ERR_REQUIRE_ESM. Some of them are repurposed to test --no-experimental-require-module. Some of them are modified to just expect loading require(esm) to work, when it's appropriate. PR-URL: #55085 Backport-PR-URL: #56927 Refs: #52697 Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Marco Ippolito <[email protected]> Reviewed-By: Rafael Gonzaga <[email protected]> Reviewed-By: Yagiz Nizipli <[email protected]> Reviewed-By: LiviaMedeiros <[email protected]> Reviewed-By: Antoine du Hamel <[email protected]> Reviewed-By: Filip Skokan <[email protected]> Reviewed-By: Michael Dawson <[email protected]> Reviewed-By: Richard Lau <[email protected]>
1 parent 683c93f commit 1ac1dda

20 files changed

+116
-68
lines changed

doc/api/cli.md

+26-4
Original file line numberDiff line numberDiff line change
@@ -992,7 +992,13 @@ Use the specified file as a security policy.
992992
### `--experimental-require-module`
993993

994994
<!-- YAML
995-
added: v20.17.0
995+
added:
996+
- v22.0.0
997+
- v20.17.0
998+
changes:
999+
- version: REPLACEME
1000+
pr-url: https://github.com/nodejs/node/pull/55085
1001+
description: This is now true by default.
9961002
-->
9971003

9981004
> Stability: 1.1 - Active Development
@@ -1555,6 +1561,24 @@ added: v16.6.0
15551561

15561562
Use this flag to disable top-level await in REPL.
15571563

1564+
### `--no-experimental-require-module`
1565+
1566+
<!-- YAML
1567+
added:
1568+
- v22.0.0
1569+
- v20.17.0
1570+
changes:
1571+
- version: REPLACEME
1572+
pr-url: https://github.com/nodejs/node/pull/55085
1573+
description: This is now false by default.
1574+
-->
1575+
1576+
> Stability: 1.1 - Active Development
1577+
1578+
Disable support for loading a synchronous ES module graph in `require()`.
1579+
1580+
See [Loading ECMAScript modules using `require()`][].
1581+
15581582
### `--no-extra-info-on-fatal-exception`
15591583

15601584
<!-- YAML
@@ -1764,9 +1788,7 @@ Identical to `-e` but prints the result.
17641788
added: v20.17.0
17651789
-->
17661790

1767-
This flag is only useful when `--experimental-require-module` is enabled.
1768-
1769-
If the ES module being `require()`'d contains top-level await, this flag
1791+
If the ES module being `require()`'d contains top-level `await`, this flag
17701792
allows Node.js to evaluate the module, try to locate the
17711793
top-level awaits, and print their location to help users find them.
17721794

doc/api/errors.md

+16-7
Original file line numberDiff line numberDiff line change
@@ -2535,8 +2535,8 @@ object.
25352535

25362536
> Stability: 1 - Experimental
25372537
2538-
When trying to `require()` a [ES Module][] under `--experimental-require-module`,
2539-
a CommonJS to ESM or ESM to CommonJS edge participates in an immediate cycle.
2538+
When trying to `require()` a [ES Module][], a CommonJS to ESM or ESM to CommonJS edge
2539+
participates in an immediate cycle.
25402540
This is not allowed because ES Modules cannot be evaluated while they are
25412541
already being evaluated.
25422542

@@ -2550,8 +2550,8 @@ module, and should be done lazily in an inner function.
25502550

25512551
> Stability: 1 - Experimental
25522552
2553-
When trying to `require()` a [ES Module][] under `--experimental-require-module`,
2554-
the module turns out to be asynchronous. That is, it contains top-level await.
2553+
When trying to `require()` a [ES Module][], the module turns out to be asynchronous.
2554+
That is, it contains top-level await.
25552555

25562556
To see where the top-level await is, use
25572557
`--experimental-print-required-tla` (this would execute the modules
@@ -2561,12 +2561,20 @@ before looking for the top-level awaits).
25612561

25622562
### `ERR_REQUIRE_ESM`
25632563

2564-
> Stability: 1 - Experimental
2564+
<!-- YAML
2565+
changes:
2566+
- version: REPLACEME
2567+
pr-url: https://github.com/nodejs/node/pull/55085
2568+
description: require() now supports loading synchronous ES modules by default.
2569+
-->
2570+
2571+
> Stability: 0 - Deprecated
25652572
25662573
An attempt was made to `require()` an [ES Module][].
25672574

2568-
To enable `require()` for synchronous module graphs (without
2569-
top-level `await`), use `--experimental-require-module`.
2575+
This error has been deprecated since `require()` now supports loading synchronous
2576+
ES modules. When `require()` encounters an ES module that contains top-level
2577+
`await`, it will throw [`ERR_REQUIRE_ASYNC_MODULE`][] instead.
25702578

25712579
<a id="ERR_SCRIPT_EXECUTION_INTERRUPTED"></a>
25722580

@@ -3908,6 +3916,7 @@ An error occurred trying to allocate memory. This should never happen.
39083916
[`ERR_INVALID_ARG_TYPE`]: #err_invalid_arg_type
39093917
[`ERR_MISSING_MESSAGE_PORT_IN_TRANSFER_LIST`]: #err_missing_message_port_in_transfer_list
39103918
[`ERR_MISSING_TRANSFERABLE_IN_TRANSFER_LIST`]: #err_missing_transferable_in_transfer_list
3919+
[`ERR_REQUIRE_ASYNC_MODULE`]: #err_require_async_module
39113920
[`EventEmitter`]: events.md#class-eventemitter
39123921
[`MessagePort`]: worker_threads.md#class-messageport
39133922
[`Object.getPrototypeOf`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getPrototypeOf

doc/api/esm.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -450,7 +450,7 @@ compatibility.
450450
### `require`
451451
452452
The CommonJS module `require` currently only supports loading synchronous ES
453-
modules when `--experimental-require-module` is enabled.
453+
modules (that is, ES modules that do not use top-level `await`).
454454
455455
See [Loading ECMAScript modules using `require()`][] for details.
456456

doc/api/modules.md

+17-17
Original file line numberDiff line numberDiff line change
@@ -172,20 +172,17 @@ relative, and based on the real path of the files making the calls to
172172

173173
<!-- YAML
174174
added: v20.17.0
175+
changes:
176+
- version: REPLACEME
177+
pr-url: https://github.com/nodejs/node/pull/55085
178+
description: require() now supports loading synchronous ES modules by default.
175179
-->
176180

177-
> Stability: 1.1 - Active Development. Enable this API with the
178-
> [`--experimental-require-module`][] CLI flag.
179-
180181
The `.mjs` extension is reserved for [ECMAScript Modules][].
181-
Currently, if the flag `--experimental-require-module` is not used, loading
182-
an ECMAScript module using `require()` will throw a [`ERR_REQUIRE_ESM`][]
183-
error, and users need to use [`import()`][] instead. See
184-
[Determining module system][] section for more info
182+
See [Determining module system][] section for more info
185183
regarding which files are parsed as ECMAScript modules.
186184

187-
If `--experimental-require-module` is enabled, and the ECMAScript module being
188-
loaded by `require()` meets the following requirements:
185+
`require()` only supports loading ECMAScript modules that meet the following requirements:
189186

190187
* The module is fully synchronous (contains no top-level `await`); and
191188
* One of these conditions are met:
@@ -194,8 +191,8 @@ loaded by `require()` meets the following requirements:
194191
3. The file has a `.js` extension, the closest `package.json` does not contain
195192
`"type": "commonjs"`, and the module contains ES module syntax.
196193

197-
`require()` will load the requested module as an ES Module, and return
198-
the module namespace object. In this case it is similar to dynamic
194+
If the ES Module being loaded meet the requirements, `require()` can load it and
195+
return the module namespace object. In this case it is similar to dynamic
199196
`import()` but is run synchronously and returns the name space object
200197
directly.
201198

@@ -214,7 +211,7 @@ class Point {
214211
export default Point;
215212
```
216213

217-
A CommonJS module can load them with `require()` under `--experimental-detect-module`:
214+
A CommonJS module can load them with `require()`:
218215

219216
```cjs
220217
const distance = require('./distance.mjs');
@@ -243,13 +240,19 @@ conventions. Code authored directly in CommonJS should avoid depending on it.
243240
If the module being `require()`'d contains top-level `await`, or the module
244241
graph it `import`s contains top-level `await`,
245242
[`ERR_REQUIRE_ASYNC_MODULE`][] will be thrown. In this case, users should
246-
load the asynchronous module using `import()`.
243+
load the asynchronous module using [`import()`][].
247244

248245
If `--experimental-print-required-tla` is enabled, instead of throwing
249246
`ERR_REQUIRE_ASYNC_MODULE` before evaluation, Node.js will evaluate the
250247
module, try to locate the top-level awaits, and print their location to
251248
help users fix them.
252249

250+
Support for loading ES modules using `require()` is currently
251+
experimental and can be disabled using `--no-experimental-require-module`.
252+
When `require()` actually encounters an ES module for the
253+
first time in the process, it will emit an experimental warning. The
254+
warning is expected to be removed when this feature stablizes.
255+
253256
## All together
254257

255258
<!-- type=misc -->
@@ -279,8 +282,7 @@ require(X) from module at path Y
279282
280283
MAYBE_DETECT_AND_LOAD(X)
281284
1. If X parses as a CommonJS module, load X as a CommonJS module. STOP.
282-
2. Else, if `--experimental-require-module` is
283-
enabled, and the source code of X can be parsed as ECMAScript module using
285+
2. Else, if the source code of X can be parsed as ECMAScript module using
284286
<a href="esm.md#resolver-algorithm-specification">DETECT_MODULE_SYNTAX defined in
285287
the ESM resolver</a>,
286288
a. Load X as an ECMAScript module. STOP.
@@ -1196,9 +1198,7 @@ This section was moved to
11961198
[GLOBAL_FOLDERS]: #loading-from-the-global-folders
11971199
[`"main"`]: packages.md#main
11981200
[`"type"`]: packages.md#type
1199-
[`--experimental-require-module`]: cli.md#--experimental-require-module
12001201
[`ERR_REQUIRE_ASYNC_MODULE`]: errors.md#err_require_async_module
1201-
[`ERR_REQUIRE_ESM`]: errors.md#err_require_esm
12021202
[`ERR_UNSUPPORTED_DIR_IMPORT`]: errors.md#err_unsupported_dir_import
12031203
[`MODULE_NOT_FOUND`]: errors.md#module_not_found
12041204
[`__dirname`]: #__dirname

doc/api/packages.md

+2-4
Original file line numberDiff line numberDiff line change
@@ -172,8 +172,7 @@ There is the CommonJS module loader:
172172
* It treats all files that lack `.json` or `.node` extensions as JavaScript
173173
text files.
174174
* It can only be used to [load ECMASCript modules from CommonJS modules][] if
175-
the module graph is synchronous (that contains no top-level `await`) when
176-
`--experimental-require-module` is enabled.
175+
the module graph is synchronous (that contains no top-level `await`).
177176
When used to load a JavaScript text file that is not an ECMAScript module,
178177
the file will be loaded as a CommonJS module.
179178

@@ -662,8 +661,7 @@ specific to least specific as conditions should be defined:
662661
* `"require"` - matches when the package is loaded via `require()`. The
663662
referenced file should be loadable with `require()` although the condition
664663
matches regardless of the module format of the target file. Expected
665-
formats include CommonJS, JSON, native addons, and ES modules
666-
if `--experimental-require-module` is enabled. _Always mutually
664+
formats include CommonJS, JSON, native addons, and ES modules. _Always mutually
667665
exclusive with `"import"`._
668666
* `"module-sync"` - matches no matter the package is loaded via `import`,
669667
`import()` or `require()`. The format is expected to be ES modules that does

lib/internal/modules/cjs/loader.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -435,7 +435,6 @@ function initializeCJS() {
435435
require('internal/modules/run_main').executeUserEntryPoint;
436436

437437
if (getOptionValue('--experimental-require-module')) {
438-
emitExperimentalWarning('Support for loading ES Module in require()');
439438
Module._extensions['.mjs'] = loadESMFromCJS;
440439
}
441440
}
@@ -1341,6 +1340,7 @@ function loadESMFromCJS(mod, filename) {
13411340
// ESM won't be accessible via process.mainModule.
13421341
setOwnProperty(process, 'mainModule', undefined);
13431342
} else {
1343+
emitExperimentalWarning('Support for loading ES Module in require()');
13441344
const {
13451345
wrap,
13461346
namespace,

lib/internal/modules/esm/load.js

-10
Original file line numberDiff line numberDiff line change
@@ -152,11 +152,6 @@ async function defaultLoad(url, context = kEmptyObject) {
152152

153153
validateAttributes(url, format, importAttributes);
154154

155-
// Use the synchronous commonjs translator which can deal with cycles.
156-
if (format === 'commonjs' && getOptionValue('--experimental-require-module')) {
157-
format = 'commonjs-sync';
158-
}
159-
160155
return {
161156
__proto__: null,
162157
format,
@@ -206,11 +201,6 @@ function defaultLoadSync(url, context = kEmptyObject) {
206201

207202
validateAttributes(url, format, importAttributes);
208203

209-
// Use the synchronous commonjs translator which can deal with cycles.
210-
if (format === 'commonjs' && getOptionValue('--experimental-require-module')) {
211-
format = 'commonjs-sync';
212-
}
213-
214204
return {
215205
__proto__: null,
216206
format,

lib/internal/modules/esm/loader.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -364,15 +364,15 @@ class ModuleLoader {
364364

365365
defaultLoadSync ??= require('internal/modules/esm/load').defaultLoadSync;
366366
const loadResult = defaultLoadSync(url, { format, importAttributes });
367-
const {
368-
format: finalFormat,
369-
source,
370-
} = loadResult;
367+
368+
// Use the synchronous commonjs translator which can deal with cycles.
369+
const finalFormat = loadResult.format === 'commonjs' ? 'commonjs-sync' : loadResult.format;
371370

372371
if (finalFormat === 'wasm') {
373372
assert.fail('WASM is currently unsupported by require(esm)');
374373
}
375374

375+
const { source } = loadResult;
376376
const isMain = (parentURL === undefined);
377377
const wrap = this.#translate(url, finalFormat, source, isMain);
378378
assert(wrap instanceof ModuleWrap, `Translator used for require(${url}) should not be async`);

lib/internal/modules/esm/module_job.js

+1
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,7 @@ class ModuleJobSync extends ModuleJobBase {
353353
}
354354

355355
runSync() {
356+
// TODO(joyeecheung): add the error decoration logic from the async instantiate.
356357
this.module.instantiateSync();
357358
setHasStartedUserESMExecution();
358359
const namespace = this.module.evaluateSync();

lib/internal/modules/esm/utils.js

+2-4
Original file line numberDiff line numberDiff line change
@@ -75,17 +75,15 @@ function initializeDefaultConditions() {
7575
const userConditions = getOptionValue('--conditions');
7676
const noAddons = getOptionValue('--no-addons');
7777
const addonConditions = noAddons ? [] : ['node-addons'];
78-
78+
const moduleConditions = getOptionValue('--experimental-require-module') ? ['module-sync'] : [];
7979
defaultConditions = ObjectFreeze([
8080
'node',
8181
'import',
82+
...moduleConditions,
8283
...addonConditions,
8384
...userConditions,
8485
]);
8586
defaultConditionsSet = new SafeSet(defaultConditions);
86-
if (getOptionValue('--experimental-require-module')) {
87-
defaultConditionsSet.add('module-sync');
88-
}
8987
}
9088

9189
/**

src/node_options.cc

+3-2
Original file line numberDiff line numberDiff line change
@@ -374,9 +374,10 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
374374
&EnvironmentOptions::print_required_tla,
375375
kAllowedInEnvvar);
376376
AddOption("--experimental-require-module",
377-
"Allow loading explicit ES Modules in require().",
377+
"Allow loading synchronous ES Modules in require().",
378378
&EnvironmentOptions::require_module,
379-
kAllowedInEnvvar);
379+
kAllowedInEnvvar,
380+
true);
380381
AddOption("--diagnostic-dir",
381382
"set dir for all output files"
382383
" (default: current working directory)",

src/node_options.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ class EnvironmentOptions : public Options {
117117
std::vector<std::string> conditions;
118118
bool detect_module = true;
119119
bool print_required_tla = false;
120-
bool require_module = false;
120+
bool require_module = true;
121121
std::string dns_result_order;
122122
bool enable_source_maps = false;
123123
bool experimental_eventsource = false;

test/es-module/test-cjs-esm-warn.js

+9-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
// Previously, this tested that require(esm) throws ERR_REQUIRE_ESM, which is no longer applicable
2+
// since require(esm) is now supported. The test has been repurposed to ensure that the old behavior
3+
// is preserved when the --no-experimental-require-module flag is used.
14
'use strict';
25

36
const { spawnPromisified } = require('../common');
@@ -22,7 +25,9 @@ describe('CJS ↔︎ ESM interop warnings', { concurrency: true }, () => {
2225
fixtures.path('/es-modules/package-type-module/cjs.js')
2326
);
2427
const basename = 'cjs.js';
25-
const { code, signal, stderr } = await spawnPromisified(execPath, [requiringCjsAsEsm]);
28+
const { code, signal, stderr } = await spawnPromisified(execPath, [
29+
'--no-experimental-require-module', requiringCjsAsEsm,
30+
]);
2631

2732
assert.ok(
2833
stderr.replaceAll('\r', '').includes(
@@ -48,7 +53,9 @@ describe('CJS ↔︎ ESM interop warnings', { concurrency: true }, () => {
4853
fixtures.path('/es-modules/package-type-module/esm.js')
4954
);
5055
const basename = 'esm.js';
51-
const { code, signal, stderr } = await spawnPromisified(execPath, [requiringEsm]);
56+
const { code, signal, stderr } = await spawnPromisified(execPath, [
57+
'--no-experimental-require-module', requiringEsm,
58+
]);
5259

5360
assert.ok(
5461
stderr.replace(/\r/g, '').includes(

test/es-module/test-esm-detect-ambiguous.mjs

+4-3
Original file line numberDiff line numberDiff line change
@@ -402,15 +402,16 @@ describe('Module syntax detection', { concurrency: !process.env.TEST_PARALLEL },
402402
});
403403
});
404404

405-
// Validate temporarily disabling `--abort-on-uncaught-exception`
406-
// while running `containsModuleSyntax`.
405+
// Checks the error caught during module detection does not trigger abort when
406+
// `--abort-on-uncaught-exception` is passed in (as that's a caught internal error).
407407
// Ref: https://github.com/nodejs/node/issues/50878
408408
describe('Wrapping a `require` of an ES module while using `--abort-on-uncaught-exception`', () => {
409409
it('should work', async () => {
410410
const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [
411411
'--abort-on-uncaught-exception',
412+
'--no-warnings',
412413
'--eval',
413-
'assert.throws(() => require("./package-type-module/esm.js"), { code: "ERR_REQUIRE_ESM" })',
414+
'require("./package-type-module/esm.js")',
414415
], {
415416
cwd: fixtures.path('es-modules'),
416417
});

test/es-module/test-esm-loader-hooks.mjs

+1
Original file line numberDiff line numberDiff line change
@@ -739,6 +739,7 @@ describe('Loader hooks', { concurrency: true }, () => {
739739

740740
describe('should use hooks', async () => {
741741
const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [
742+
'--no-experimental-require-module',
742743
'--import',
743744
fixtures.fileURL('es-module-loaders/builtin-named-exports.mjs'),
744745
fixtures.path('es-modules/require-esm-throws-with-loaders.js'),

0 commit comments

Comments
 (0)