From b4c3f5c23a78339da0ea5781361361d2cf39fa80 Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Tue, 5 Mar 2024 17:19:35 +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 the module being require()ed has .mjs extension or there are other explicit indicators that it's an ES module, we load it as an ES module. If the graph is synchronous, we return the module namespace as the exports. If the graph contains top-level await, we throw an error before evaluating the module. If an additional flag --print-pending-tla is passed, we proceeds to evaluation but do not run the microtasks, only to find out where the TLA is and print their location to help users fix them. If there are not explicit indicators whether a .js file is CJS or ESM, we parse it as CJS first. If the parse error indicates that it contains ESM syntax, we parse it again as ESM. If the second parsing succeeds, we continue to treat it as ESM. --- .eslintignore | 2 + doc/api/cli.md | 17 ++ lib/internal/errors.js | 3 + lib/internal/modules/cjs/loader.js | 150 +++++++++++----- lib/internal/modules/esm/loader.js | 102 ++++++++--- lib/internal/modules/esm/module_job.js | 135 +++++++++------ lib/internal/modules/esm/module_map.js | 4 +- lib/internal/modules/esm/resolve.js | 11 +- lib/internal/modules/esm/translators.js | 160 +++++++----------- lib/internal/util/embedding.js | 2 +- src/module_wrap.cc | 150 ++++++++++++++++ src/module_wrap.h | 2 + src/node_contextify.cc | 110 ++++++++---- src/node_options.cc | 5 + src/node_options.h | 1 + .../test-esm-cjs-load-error-note.mjs | 6 +- test/es-module/test-esm-loader-modulemap.js | 2 +- .../test-require-module-entry-point-aou.js | 8 + .../test-require-module-entry-point.js | 8 + test/es-module/test-require-module.js | 61 +++++++ 20 files changed, 686 insertions(+), 253 deletions(-) create mode 100644 test/es-module/test-require-module-entry-point-aou.js create mode 100644 test/es-module/test-require-module-entry-point.js create mode 100644 test/es-module/test-require-module.js diff --git a/.eslintignore b/.eslintignore index 64f34660d87a8f..f72fbdfd842923 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-entry-point.js +test/es-module/test-require-module-entry-point-aou.js diff --git a/doc/api/cli.md b/doc/api/cli.md index bdae4a7e771391..bbfb111e051d4d 100644 --- a/doc/api/cli.md +++ b/doc/api/cli.md @@ -871,6 +871,22 @@ 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), it throws an error. + +By default, a `.js` file will be parsed as a CommonJS module first. If it +contains ES module syntax, Node.js will try to parse and evaluate the module +again as an ES module. If it turns out to be synchronous and can be evaluated +successfully, the module namespace object will be returned by `require()`. + ### `--experimental-sea-config`