From a1b3cd447a1e752865d85ee84e098ce3e33b918d Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Fri, 8 Mar 2024 18:28:37 +0100 Subject: [PATCH] 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`. --- .eslintignore | 2 + doc/api/cli.md | 35 ++++ doc/api/errors.md | 16 +- lib/internal/errors.js | 7 + lib/internal/modules/cjs/loader.js | 152 ++++++++++---- lib/internal/modules/esm/loader.js | 127 +++++++++++- lib/internal/modules/esm/module_job.js | 61 ++++-- lib/internal/modules/esm/module_map.js | 4 +- lib/internal/modules/esm/translators.js | 74 +++---- lib/internal/util/embedding.js | 2 +- src/module_wrap.cc | 185 ++++++++++++++++++ src/module_wrap.h | 8 + src/node_contextify.cc | 143 +++++++++++--- src/node_errors.h | 5 + src/node_options.cc | 12 ++ src/node_options.h | 2 + .../test-esm-cjs-load-error-note.mjs | 10 +- test/es-module/test-esm-loader-modulemap.js | 2 +- .../test-require-module-default-extension.js | 17 ++ ...t-require-module-detect-entry-point-aou.js | 8 + .../test-require-module-detect-entry-point.js | 8 + .../test-require-module-dont-detect-cjs.js | 11 ++ .../test-require-module-dynamic-import.js | 27 +++ test/es-module/test-require-module-preload.js | 74 +++++++ test/es-module/test-require-module-tla.js | 62 ++++++ .../test-require-module-with-detection.js | 18 ++ .../test-require-module-without-detection.js | 33 ++++ test/es-module/test-require-module.js | 52 +++++ test/fixtures/es-modules/import-esm.mjs | 3 + test/fixtures/es-modules/imported-esm.mjs | 1 + .../package-default-extension/index.cjs | 1 + .../package-default-extension/index.mjs | 1 + test/fixtures/es-modules/require-cjs.mjs | 5 + test/fixtures/es-modules/required-cjs.js | 3 + test/fixtures/es-modules/tla/execution.mjs | 3 + .../es-modules/tla/require-execution.js | 1 + test/fixtures/es-modules/tla/resolved.mjs | 1 + 37 files changed, 1030 insertions(+), 146 deletions(-) create mode 100644 test/es-module/test-require-module-default-extension.js create mode 100644 test/es-module/test-require-module-detect-entry-point-aou.js create mode 100644 test/es-module/test-require-module-detect-entry-point.js create mode 100644 test/es-module/test-require-module-dont-detect-cjs.js create mode 100644 test/es-module/test-require-module-dynamic-import.js create mode 100644 test/es-module/test-require-module-preload.js create mode 100644 test/es-module/test-require-module-tla.js create mode 100644 test/es-module/test-require-module-with-detection.js create mode 100644 test/es-module/test-require-module-without-detection.js create mode 100644 test/es-module/test-require-module.js create mode 100644 test/fixtures/es-modules/import-esm.mjs create mode 100644 test/fixtures/es-modules/imported-esm.mjs create mode 100644 test/fixtures/es-modules/package-default-extension/index.cjs create mode 100644 test/fixtures/es-modules/package-default-extension/index.mjs create mode 100644 test/fixtures/es-modules/require-cjs.mjs create mode 100644 test/fixtures/es-modules/required-cjs.js create mode 100644 test/fixtures/es-modules/tla/execution.mjs create mode 100644 test/fixtures/es-modules/tla/require-execution.js create mode 100644 test/fixtures/es-modules/tla/resolved.mjs diff --git a/.eslintignore b/.eslintignore index 64f34660d87a8f..673185b1ad3929 100644 --- a/.eslintignore +++ b/.eslintignore @@ -13,3 +13,5 @@ doc/changelogs/CHANGELOG_v1*.md !doc/changelogs/CHANGELOG_v18.md !doc/api_assets/*.js !.eslintrc.js +test/es-module/test-require-module-detect-entry-point.js +test/es-module/test-require-module-detect-entry-point-aou.js diff --git a/doc/api/cli.md b/doc/api/cli.md index bdae4a7e771391..cbc40a906505d5 100644 --- a/doc/api/cli.md +++ b/doc/api/cli.md @@ -871,6 +871,39 @@ added: v11.8.0 Use the specified file as a security policy. +### `--experimental-require-module` + + + +> Stability: 1.1 - Active Developement + +Supports loading a synchronous ES module graph in `require()`. If the module +graph is not synchronous (contains top-level await), Node.js throws an error +without executing the module. If `--print-pending-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 with +automatic syntax-based module type detection, use +`--experimental-require-module-with-detection`. + +### `--experimental-require-module-with-detection` + + + +> Stability: 1.1 - Active Developement + +In addition to what `--experimental-require-module` supports, when the module +being `require()`'d is not explicitly marked as an ES Module using the +`"type": "module"` field in `package.json` or the `.mjs` extension, it will +try to detect module type based on the syntax of the module. + ### `--experimental-sea-config`