Skip to content

Commit a1b3cd4

Browse files
committed
module: support require()ing synchronous ESM graphs
This patch adds `require()` support for synchronous ESM graphs under the flag --experimental-require-module. This is based on the the following design aspect of ESM: - The resolution can be synchronous (up to the host) - The evaluation of a synchronous graph (without top-level await) is also synchronous, and, by the time the module graph is instantiated (before evaluation starts), this is is already known. When --experimental-require-module is enabled, and require() resolves to an ES module, if the module is fully synchronous (contains no top-level await), the `require()` call returns the module name space object. Otherwise, Node.js throws an error without executing the module. If `--print-required-tla` is passed, Node.js will evaluate the module, try to locate the top-level awaits, and print their location to help users fix them. With this option alone, the module being `require()`'d should be explicitly marked as an ES module either using the `"type": "module"` field in `package.json` or the `.mjs` extension. To load implicit ES modules ending with `.js` using automatic syntax-based module type detection, use `--experimental-require-module-with-detection`.
1 parent b209b83 commit a1b3cd4

37 files changed

+1030
-146
lines changed

.eslintignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,5 @@ doc/changelogs/CHANGELOG_v1*.md
1313
!doc/changelogs/CHANGELOG_v18.md
1414
!doc/api_assets/*.js
1515
!.eslintrc.js
16+
test/es-module/test-require-module-detect-entry-point.js
17+
test/es-module/test-require-module-detect-entry-point-aou.js

doc/api/cli.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -871,6 +871,39 @@ added: v11.8.0
871871

872872
Use the specified file as a security policy.
873873

874+
### `--experimental-require-module`
875+
876+
<!-- YAML
877+
added: REPLACEME
878+
-->
879+
880+
> Stability: 1.1 - Active Developement
881+
882+
Supports loading a synchronous ES module graph in `require()`. If the module
883+
graph is not synchronous (contains top-level await), Node.js throws an error
884+
without executing the module. If `--print-pending-tla` is passed, Node.js
885+
will evaluate the module, try to locate the top-level awaits, and print
886+
their location to help users fix them.
887+
888+
With this option alone, the module being `require()`'d should be explicitly
889+
marked as an ES module either using the `"type": "module"` field in
890+
`package.json` or the `.mjs` extension. To load implicit ES modules with
891+
automatic syntax-based module type detection, use
892+
`--experimental-require-module-with-detection`.
893+
894+
### `--experimental-require-module-with-detection`
895+
896+
<!-- YAML
897+
added: REPLACEME
898+
-->
899+
900+
> Stability: 1.1 - Active Developement
901+
902+
In addition to what `--experimental-require-module` supports, when the module
903+
being `require()`'d is not explicitly marked as an ES Module using the
904+
`"type": "module"` field in `package.json` or the `.mjs` extension, it will
905+
try to detect module type based on the syntax of the module.
906+
874907
### `--experimental-sea-config`
875908

876909
<!-- YAML
@@ -2523,6 +2556,8 @@ Node.js options that are allowed are:
25232556
* `--experimental-network-imports`
25242557
* `--experimental-permission`
25252558
* `--experimental-policy`
2559+
* `--experimental-require-module-with-detection`
2560+
* `--experimental-require-module`
25262561
* `--experimental-shadow-realm`
25272562
* `--experimental-specifier-resolution`
25282563
* `--experimental-top-level-await`

doc/api/errors.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2487,13 +2487,27 @@ Accessing `Object.prototype.__proto__` has been forbidden using
24872487
[`Object.setPrototypeOf`][] should be used to get and set the prototype of an
24882488
object.
24892489

2490+
<a id="ERR_REQUIRE_ASYNC_MODULE"></a>
2491+
2492+
### `ERR_REQUIRE_ASYNC_MODULE`
2493+
2494+
> Stability: 1 - Experimental
2495+
2496+
When trying to `require()` a [ES Module][] under `--experimental-require-module`,
2497+
the module turns out to be asynchronous. That is, it contains top-level await.
2498+
To see where the top-level await is, use
2499+
`--print-pending-tla` (this would execute the modules before looking for
2500+
the top-level awaits).
2501+
24902502
<a id="ERR_REQUIRE_ESM"></a>
24912503

24922504
### `ERR_REQUIRE_ESM`
24932505

24942506
> Stability: 1 - Experimental
24952507
2496-
An attempt was made to `require()` an [ES Module][].
2508+
An attempt was made to `require()` an [ES Module][]. To enable `require()` for
2509+
synchronous module graphs (without top-level await), use
2510+
`--experimental-require-module`.
24972511

24982512
<a id="ERR_SCRIPT_EXECUTION_INTERRUPTED"></a>
24992513

lib/internal/errors.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ class NodeAggregateError extends AggregateError {
210210
}
211211

212212
const assert = require('internal/assert');
213+
const { getOptionValue } = require('internal/options');
213214

214215
// Lazily loaded
215216
let util;
@@ -1686,12 +1687,16 @@ E('ERR_PERFORMANCE_MEASURE_INVALID_OPTIONS', '%s', TypeError);
16861687
E('ERR_REQUIRE_ESM',
16871688
function(filename, hasEsmSyntax, parentPath = null, packageJsonPath = null) {
16881689
hideInternalStackFrames(this);
1690+
assert(!getOptionValue('--experimental-require-module'));
16891691
let msg = `require() of ES Module ${filename}${parentPath ? ` from ${
16901692
parentPath}` : ''} not supported.`;
1693+
const hint = '\nOr use --experimental-require-module if the module is synchronous ' +
1694+
'(contains no top-level await)';
16911695
if (!packageJsonPath) {
16921696
if (StringPrototypeEndsWith(filename, '.mjs'))
16931697
msg += `\nInstead change the require of ${filename} to a dynamic ` +
16941698
'import() which is available in all CommonJS modules.';
1699+
msg += hint;
16951700
return msg;
16961701
}
16971702
const path = require('path');
@@ -1700,6 +1705,7 @@ E('ERR_REQUIRE_ESM',
17001705
if (hasEsmSyntax) {
17011706
msg += `\nInstead change the require of ${basename} in ${parentPath} to` +
17021707
' a dynamic import() which is available in all CommonJS modules.';
1708+
msg += hint;
17031709
return msg;
17041710
}
17051711
msg += `\n${basename} is treated as an ES module file as it is a .js ` +
@@ -1710,6 +1716,7 @@ E('ERR_REQUIRE_ESM',
17101716
'modules, or change "type": "module" to "type": "commonjs" in ' +
17111717
`${packageJsonPath} to treat all .js files as CommonJS (using .mjs for ` +
17121718
'all ES modules instead).\n';
1719+
msg += hint;
17131720
return msg;
17141721
}, Error);
17151722
E('ERR_SCRIPT_EXECUTION_INTERRUPTED',

0 commit comments

Comments
 (0)