mirror of
https://github.com/nodejs/node.git
synced 2026-10-14 10:26:26 -04:00
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.
If `--experimental-require-module` is enabled, and the ECMAScript
module being loaded by `require()` meets the following requirements:
- Explicitly marked as an ES module with a `"type": "module"` field in
the closest package.json or a `.mjs` extension.
- Fully synchronous (contains no top-level `await`).
`require()` will load the requested module as an ES Module, and return
the module name space object. In this case it is similar to dynamic
`import()` but is run synchronously and returns the name space object
directly.
```mjs
// point.mjs
export function distance(a, b) {
return (b.x - a.x) ** 2 + (b.y - a.y) ** 2;
}
class Point {
constructor(x, y) { this.x = x; this.y = y; }
}
export default Point;
```
```cjs
const required = require('./point.mjs');
// [Module: null prototype] {
// default: [class Point],
// distance: [Function: distance]
// }
console.log(required);
(async () => {
const imported = await import('./point.mjs');
console.log(imported === required); // true
})();
```
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()`.
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.
PR-URL: https://github.com/nodejs/node/pull/51977
Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Guy Bedford <guybedford@gmail.com>
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
Reviewed-By: Geoffrey Booth <webadmin@geoffreybooth.com>
This commit is contained in:
63 files changed
+1170
-79
No files matched your search
@@ -877,6 +877,18 @@ added: v11.8.0
|
||||
|
||||
Use the specified file as a security policy.
|
||||
|
||||
### `--experimental-require-module`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
> Stability: 1.1 - Active Developement
|
||||
|
||||
Supports loading a synchronous ES module graph in `require()`.
|
||||
|
||||
See [Loading ECMAScript modules using `require()`][].
|
||||
|
||||
### `--experimental-sea-config`
|
||||
|
||||
<!-- YAML
|
||||
@@ -1584,6 +1596,18 @@ changes:
|
||||
|
||||
Identical to `-e` but prints the result.
|
||||
|
||||
### `--experimental-print-required-tla`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
This flag is only useful when `--experimental-require-module` is enabled.
|
||||
|
||||
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.
|
||||
|
||||
### `--prof`
|
||||
|
||||
<!-- YAML
|
||||
@@ -2534,6 +2558,8 @@ Node.js options that are allowed are:
|
||||
* `--experimental-network-imports`
|
||||
* `--experimental-permission`
|
||||
* `--experimental-policy`
|
||||
* `--experimental-print-required-tla`
|
||||
* `--experimental-require-module`
|
||||
* `--experimental-shadow-realm`
|
||||
* `--experimental-specifier-resolution`
|
||||
* `--experimental-top-level-await`
|
||||
@@ -3038,6 +3064,7 @@ node --stack-trace-limit=12 -p -e "Error.stackTraceLimit" # prints 12
|
||||
[ExperimentalWarning: `vm.measureMemory` is an experimental feature]: vm.md#vmmeasurememoryoptions
|
||||
[Fetch API]: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API
|
||||
[File System Permissions]: permissions.md#file-system-permissions
|
||||
[Loading ECMAScript modules using `require()`]: modules.md#loading-ecmascript-modules-using-require
|
||||
[Module customization hooks]: module.md#customization-hooks
|
||||
[Module customization hooks: enabling]: module.md#enabling
|
||||
[Modules loaders]: packages.md#modules-loaders
|
||||
|
||||
@@ -2487,6 +2487,19 @@ Accessing `Object.prototype.__proto__` has been forbidden using
|
||||
[`Object.setPrototypeOf`][] should be used to get and set the prototype of an
|
||||
object.
|
||||
|
||||
<a id="ERR_REQUIRE_ASYNC_MODULE"></a>
|
||||
|
||||
### `ERR_REQUIRE_ASYNC_MODULE`
|
||||
|
||||
> 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.
|
||||
|
||||
To see where the top-level await is, use
|
||||
`--experimental-print-required-tla` (this would execute the modules
|
||||
before looking for the top-level awaits).
|
||||
|
||||
<a id="ERR_REQUIRE_ESM"></a>
|
||||
|
||||
### `ERR_REQUIRE_ESM`
|
||||
@@ -2495,6 +2508,9 @@ object.
|
||||
|
||||
An attempt was made to `require()` an [ES Module][].
|
||||
|
||||
To enable `require()` for synchronous module graphs (without
|
||||
top-level `await`), use `--experimental-require-module`.
|
||||
|
||||
<a id="ERR_SCRIPT_EXECUTION_INTERRUPTED"></a>
|
||||
|
||||
### `ERR_SCRIPT_EXECUTION_INTERRUPTED`
|
||||
|
||||
+4
-4
@@ -466,11 +466,10 @@ compatibility.
|
||||
|
||||
### `require`
|
||||
|
||||
The CommonJS module `require` always treats the files it references as CommonJS.
|
||||
The CommonJS module `require` currently only supports loading synchronous ES
|
||||
modules when `--experimental-require-module` is enabled.
|
||||
|
||||
Using `require` to load an ES module is not supported because ES modules have
|
||||
asynchronous execution. Instead, use [`import()`][] to load an ES module
|
||||
from a CommonJS module.
|
||||
See [Loading ECMAScript modules using `require()`][] for details.
|
||||
|
||||
### CommonJS Namespaces
|
||||
|
||||
@@ -1142,6 +1141,7 @@ resolution for ESM specifiers is [commonjs-extension-resolution-loader][].
|
||||
[Import Attributes]: #import-attributes
|
||||
[Import Attributes proposal]: https://github.com/tc39/proposal-import-attributes
|
||||
[JSON modules]: #json-modules
|
||||
[Loading ECMAScript modules using `require()`]: modules.md#loading-ecmascript-modules-using-require
|
||||
[Module customization hooks]: module.md#customization-hooks
|
||||
[Node.js Module Resolution And Loading Algorithm]: #resolution-algorithm-specification
|
||||
[Terminology]: #terminology
|
||||
|
||||
+67
-10
@@ -167,16 +167,60 @@ variable. Since the module lookups using `node_modules` folders are all
|
||||
relative, and based on the real path of the files making the calls to
|
||||
`require()`, the packages themselves can be anywhere.
|
||||
|
||||
## The `.mjs` extension
|
||||
## Loading ECMAScript modules using `require()`
|
||||
|
||||
Due to the synchronous nature of `require()`, it is not possible to use it to
|
||||
load ECMAScript module files. Attempting to do so will throw a
|
||||
[`ERR_REQUIRE_ESM`][] error. Use [`import()`][] instead.
|
||||
|
||||
The `.mjs` extension is reserved for [ECMAScript Modules][] which cannot be
|
||||
loaded via `require()`. See [Determining module system][] section for more info
|
||||
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
|
||||
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:
|
||||
|
||||
* Explicitly marked as an ES module with a `"type": "module"` field in
|
||||
the closest package.json or a `.mjs` extension.
|
||||
* Fully synchronous (contains no top-level `await`).
|
||||
|
||||
`require()` will load the requested module as an ES Module, and return
|
||||
the module name space object. In this case it is similar to dynamic
|
||||
`import()` but is run synchronously and returns the name space object
|
||||
directly.
|
||||
|
||||
```mjs
|
||||
// point.mjs
|
||||
export function distance(a, b) { return (b.x - a.x) ** 2 + (b.y - a.y) ** 2; }
|
||||
class Point {
|
||||
constructor(x, y) { this.x = x; this.y = y; }
|
||||
}
|
||||
export default Point;
|
||||
```
|
||||
|
||||
```cjs
|
||||
const required = require('./point.mjs');
|
||||
// [Module: null prototype] {
|
||||
// default: [class Point],
|
||||
// distance: [Function: distance]
|
||||
// }
|
||||
console.log(required);
|
||||
|
||||
(async () => {
|
||||
const imported = await import('./point.mjs');
|
||||
console.log(imported === required); // true
|
||||
})();
|
||||
```
|
||||
|
||||
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()`.
|
||||
|
||||
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.
|
||||
|
||||
## All together
|
||||
|
||||
<!-- type=misc -->
|
||||
@@ -206,12 +250,24 @@ require(X) from module at path Y
|
||||
|
||||
LOAD_AS_FILE(X)
|
||||
1. If X is a file, load X as its file extension format. STOP
|
||||
2. If X.js is a file, load X.js as JavaScript text. STOP
|
||||
3. If X.json is a file, parse X.json to a JavaScript Object. STOP
|
||||
2. If X.js is a file,
|
||||
a. Find the closest package scope SCOPE to X.
|
||||
b. If no scope was found, load X.js as a CommonJS module. STOP.
|
||||
c. If the SCOPE/package.json contains "type" field,
|
||||
1. If the "type" field is "module", load X.js as an ECMAScript module. STOP.
|
||||
2. Else, load X.js as an CommonJS module. STOP.
|
||||
3. If X.json is a file, load X.json to a JavaScript Object. STOP
|
||||
4. If X.node is a file, load X.node as binary addon. STOP
|
||||
5. If X.mjs is a file, and `--experimental-require-module` is enabled,
|
||||
load X.mjs as an ECMAScript module. STOP
|
||||
|
||||
LOAD_INDEX(X)
|
||||
1. If X/index.js is a file, load X/index.js as JavaScript text. STOP
|
||||
1. If X/index.js is a file
|
||||
a. Find the closest package scope SCOPE to X.
|
||||
b. If no scope was found, load X/index.js as a CommonJS module. STOP.
|
||||
c. If the SCOPE/package.json contains "type" field,
|
||||
1. If the "type" field is "module", load X/index.js as an ECMAScript module. STOP.
|
||||
2. Else, load X/index.js as an CommonJS module. STOP.
|
||||
2. If X/index.json is a file, parse X/index.json to a JavaScript object. STOP
|
||||
3. If X/index.node is a file, load X/index.node as binary addon. STOP
|
||||
|
||||
@@ -1085,6 +1141,7 @@ This section was moved to
|
||||
[GLOBAL_FOLDERS]: #loading-from-the-global-folders
|
||||
[`"main"`]: packages.md#main
|
||||
[`"type"`]: packages.md#type
|
||||
[`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
|
||||
|
||||
+10
-9
@@ -133,14 +133,15 @@ There is the CommonJS module loader:
|
||||
`process.dlopen()`.
|
||||
* It treats all files that lack `.json` or `.node` extensions as JavaScript
|
||||
text files.
|
||||
* It cannot be used to load ECMAScript modules (although it is possible to
|
||||
[load ECMASCript modules from CommonJS modules][]). When used to load a
|
||||
JavaScript text file that is not an ECMAScript module, it loads it as a
|
||||
CommonJS module.
|
||||
* 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.
|
||||
When used to load a JavaScript text file that is not an ECMAScript module,
|
||||
the file will be loaded as a CommonJS module.
|
||||
|
||||
There is the ECMAScript module loader:
|
||||
|
||||
* It is asynchronous.
|
||||
* It is asynchronous, unless it's being used to load modules for `require()`.
|
||||
* It is responsible for handling `import` statements and `import()` expressions.
|
||||
* It is not monkey patchable, can be customized using [loader hooks][].
|
||||
* It does not support folders as modules, directory indexes (e.g.
|
||||
@@ -623,9 +624,9 @@ 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, and native addons but not ES modules as
|
||||
`require()` doesn't support them. _Always mutually exclusive with
|
||||
`"import"`._
|
||||
formats include CommonJS, JSON, native addons, and ES modules
|
||||
if `--experimental-require-module` is enabled. _Always mutually
|
||||
exclusive with `"import"`._
|
||||
* `"default"` - the generic fallback that always matches. Can be a CommonJS
|
||||
or ES module file. _This condition should always come last._
|
||||
|
||||
@@ -1371,7 +1372,7 @@ This field defines [subpath imports][] for the current package.
|
||||
[entry points]: #package-entry-points
|
||||
[folders as modules]: modules.md#folders-as-modules
|
||||
[import maps]: https://github.com/WICG/import-maps
|
||||
[load ECMASCript modules from CommonJS modules]: modules.md#the-mjs-extension
|
||||
[load ECMASCript modules from CommonJS modules]: modules.md#loading-ecmascript-modules-using-require
|
||||
[loader hooks]: esm.md#loaders
|
||||
[packages folder mapping]: https://github.com/WICG/import-maps#packages-via-trailing-slashes
|
||||
[self-reference]: #self-referencing-a-package-using-its-name
|
||||
|
||||
@@ -60,10 +60,11 @@ const {
|
||||
StringPrototypeSlice,
|
||||
StringPrototypeSplit,
|
||||
StringPrototypeStartsWith,
|
||||
Symbol,
|
||||
} = primordials;
|
||||
|
||||
// Map used to store CJS parsing data.
|
||||
const cjsParseCache = new SafeWeakMap();
|
||||
// Map used to store CJS parsing data or for ESM loading.
|
||||
const cjsSourceCache = new SafeWeakMap();
|
||||
/**
|
||||
* Map of already-loaded CJS modules to use.
|
||||
*/
|
||||
@@ -72,12 +73,14 @@ const cjsExportsCache = new SafeWeakMap();
|
||||
// Set first due to cycle with ESM loader functions.
|
||||
module.exports = {
|
||||
cjsExportsCache,
|
||||
cjsParseCache,
|
||||
cjsSourceCache,
|
||||
initializeCJS,
|
||||
Module,
|
||||
wrapSafe,
|
||||
};
|
||||
|
||||
const kIsMainSymbol = Symbol('kIsMainSymbol');
|
||||
|
||||
const { BuiltinModule } = require('internal/bootstrap/realm');
|
||||
const {
|
||||
maybeCacheSourceMap,
|
||||
@@ -395,6 +398,11 @@ function initializeCJS() {
|
||||
// TODO(joyeecheung): deprecate this in favor of a proper hook?
|
||||
Module.runMain =
|
||||
require('internal/modules/run_main').executeUserEntryPoint;
|
||||
|
||||
if (getOptionValue('--experimental-require-module')) {
|
||||
emitExperimentalWarning('Support for loading ES Module in require()');
|
||||
Module._extensions['.mjs'] = loadESMFromCJS;
|
||||
}
|
||||
}
|
||||
|
||||
// Given a module name, and a list of paths to test, returns the first
|
||||
@@ -600,6 +608,19 @@ function resolveExports(nmPath, request) {
|
||||
}
|
||||
}
|
||||
|
||||
// We don't cache this in case user extends the extensions.
|
||||
function getDefaultExtensions() {
|
||||
const extensions = ObjectKeys(Module._extensions);
|
||||
if (!getOptionValue('--experimental-require-module')) {
|
||||
return extensions;
|
||||
}
|
||||
// If the .mjs extension is added by --experimental-require-module,
|
||||
// remove it from the supported default extensions to maintain
|
||||
// compatibility.
|
||||
// TODO(joyeecheung): allow both .mjs and .cjs?
|
||||
return ArrayPrototypeFilter(extensions, (ext) => ext !== '.mjs' || Module._extensions['.mjs'] !== loadESMFromCJS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the absolute path to a module.
|
||||
* @param {string} request Relative or absolute file path
|
||||
@@ -701,7 +722,7 @@ Module._findPath = function(request, paths, isMain) {
|
||||
if (!filename) {
|
||||
// Try it with each of the extensions
|
||||
if (exts === undefined) {
|
||||
exts = ObjectKeys(Module._extensions);
|
||||
exts = getDefaultExtensions();
|
||||
}
|
||||
filename = tryExtensions(basePath, exts, isMain);
|
||||
}
|
||||
@@ -710,7 +731,7 @@ Module._findPath = function(request, paths, isMain) {
|
||||
if (!filename && rc === 1) { // Directory.
|
||||
// try it with each of the extensions at "index"
|
||||
if (exts === undefined) {
|
||||
exts = ObjectKeys(Module._extensions);
|
||||
exts = getDefaultExtensions();
|
||||
}
|
||||
filename = tryPackage(basePath, exts, isMain, request);
|
||||
}
|
||||
@@ -987,7 +1008,7 @@ Module._load = function(request, parent, isMain) {
|
||||
if (cachedModule !== undefined) {
|
||||
updateChildren(parent, cachedModule, true);
|
||||
if (!cachedModule.loaded) {
|
||||
const parseCachedModule = cjsParseCache.get(cachedModule);
|
||||
const parseCachedModule = cjsSourceCache.get(cachedModule);
|
||||
if (!parseCachedModule || parseCachedModule.loaded) {
|
||||
return getExportsForCircularRequire(cachedModule);
|
||||
}
|
||||
@@ -1009,6 +1030,9 @@ Module._load = function(request, parent, isMain) {
|
||||
setOwnProperty(process, 'mainModule', module);
|
||||
setOwnProperty(module.require, 'main', process.mainModule);
|
||||
module.id = '.';
|
||||
module[kIsMainSymbol] = true;
|
||||
} else {
|
||||
module[kIsMainSymbol] = false;
|
||||
}
|
||||
|
||||
reportModuleToWatchMode(filename);
|
||||
@@ -1244,6 +1268,20 @@ let resolvedArgv;
|
||||
let hasPausedEntry = false;
|
||||
/** @type {import('vm').Script} */
|
||||
|
||||
/**
|
||||
* Resolve and evaluate it synchronously as ESM if it's ESM.
|
||||
* @param {Module} mod CJS module instance
|
||||
* @param {string} filename Absolute path of the file.
|
||||
*/
|
||||
function loadESMFromCJS(mod, filename) {
|
||||
const source = getMaybeCachedSource(mod, filename);
|
||||
const cascadedLoader = require('internal/modules/esm/loader').getOrInitializeCascadedLoader();
|
||||
const isMain = mod[kIsMainSymbol];
|
||||
// TODO(joyeecheung): we may want to invent optional special handling for default exports here.
|
||||
// For now, it's good enough to be identical to what `import()` returns.
|
||||
mod.exports = cascadedLoader.importSyncForRequire(filename, source, isMain);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps the given content in a script and runs it in a new context.
|
||||
* @param {string} filename The name of the file being loaded
|
||||
@@ -1269,11 +1307,16 @@ function wrapSafe(filename, content, cjsModuleInstance, codeCache) {
|
||||
);
|
||||
|
||||
// Cache the source map for the module if present.
|
||||
if (script.sourceMapURL) {
|
||||
maybeCacheSourceMap(filename, content, this, false, undefined, script.sourceMapURL);
|
||||
const { sourceMapURL } = script;
|
||||
if (sourceMapURL) {
|
||||
maybeCacheSourceMap(filename, content, this, false, undefined, sourceMapURL);
|
||||
}
|
||||
|
||||
return runScriptInThisContext(script, true, false);
|
||||
return {
|
||||
__proto__: null,
|
||||
function: runScriptInThisContext(script, true, false),
|
||||
sourceMapURL,
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -1291,7 +1334,7 @@ function wrapSafe(filename, content, cjsModuleInstance, codeCache) {
|
||||
maybeCacheSourceMap(filename, content, this, false, undefined, result.sourceMapURL);
|
||||
}
|
||||
|
||||
return result.function;
|
||||
return result;
|
||||
} catch (err) {
|
||||
if (process.mainModule === cjsModuleInstance) {
|
||||
const { enrichCJSError } = require('internal/modules/esm/translators');
|
||||
@@ -1306,8 +1349,9 @@ function wrapSafe(filename, content, cjsModuleInstance, codeCache) {
|
||||
* `exports`) to the file. Returns exception, if any.
|
||||
* @param {string} content The source code of the module
|
||||
* @param {string} filename The file path of the module
|
||||
* @param {boolean} loadAsESM Whether it's known to be ESM via .mjs or "type" in package.json.
|
||||
*/
|
||||
Module.prototype._compile = function(content, filename) {
|
||||
Module.prototype._compile = function(content, filename, loadAsESM = false) {
|
||||
let moduleURL;
|
||||
let redirects;
|
||||
const manifest = policy()?.manifest;
|
||||
@@ -1317,8 +1361,20 @@ Module.prototype._compile = function(content, filename) {
|
||||
manifest.assertIntegrity(moduleURL, content);
|
||||
}
|
||||
|
||||
const compiledWrapper = wrapSafe(filename, content, this);
|
||||
// TODO(joyeecheung): when the module is the entry point, consider allowing TLA.
|
||||
// Only modules being require()'d really need to avoid TLA.
|
||||
if (loadAsESM) {
|
||||
// Pass the source into the .mjs extension handler indirectly through the cache.
|
||||
cjsSourceCache.set(this, { source: content });
|
||||
loadESMFromCJS(this, filename);
|
||||
return;
|
||||
}
|
||||
|
||||
const { function: compiledWrapper } = wrapSafe(filename, content, this);
|
||||
|
||||
// TODO(joyeecheung): the detection below is unnecessarily complex. Using the
|
||||
// kIsMainSymbol, or a kBreakOnStartSymbol that gets passed from
|
||||
// higher level instead of doing hacky detection here.
|
||||
let inspectorWrapper = null;
|
||||
if (getOptionValue('--inspect-brk') && process._eval == null) {
|
||||
if (!resolvedArgv) {
|
||||
@@ -1362,24 +1418,43 @@ Module.prototype._compile = function(content, filename) {
|
||||
};
|
||||
|
||||
/**
|
||||
* Native handler for `.js` files.
|
||||
* @param {Module} module The module to compile
|
||||
* @param {string} filename The file path of the module
|
||||
* Get the source code of a module, using cached ones if it's cached.
|
||||
* @param {Module} mod Module instance whose source is potentially already cached.
|
||||
* @param {string} filename Absolute path to the file of the module.
|
||||
* @returns {string}
|
||||
*/
|
||||
Module._extensions['.js'] = function(module, filename) {
|
||||
// If already analyzed the source, then it will be cached.
|
||||
const cached = cjsParseCache.get(module);
|
||||
function getMaybeCachedSource(mod, filename) {
|
||||
const cached = cjsSourceCache.get(mod);
|
||||
let content;
|
||||
if (cached?.source) {
|
||||
content = cached.source;
|
||||
cached.source = undefined;
|
||||
} else {
|
||||
// TODO(joyeecheung): we can read a buffer instead to speed up
|
||||
// compilation.
|
||||
content = fs.readFileSync(filename, 'utf8');
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Built-in handler for `.js` files.
|
||||
* @param {Module} module The module to compile
|
||||
* @param {string} filename The file path of the module
|
||||
*/
|
||||
Module._extensions['.js'] = function(module, filename) {
|
||||
// If already analyzed the source, then it will be cached.
|
||||
const content = getMaybeCachedSource(module, filename);
|
||||
|
||||
if (StringPrototypeEndsWith(filename, '.js')) {
|
||||
const pkg = packageJsonReader.getNearestParentPackageJSON(filename);
|
||||
// Function require shouldn't be used in ES modules.
|
||||
if (pkg?.data.type === 'module') {
|
||||
if (getOptionValue('--experimental-require-module')) {
|
||||
module._compile(content, filename, true);
|
||||
return;
|
||||
}
|
||||
|
||||
// This is an error path because `require` of a `.js` file in a `"type": "module"` scope is not allowed.
|
||||
const parent = moduleParentCache.get(module);
|
||||
const parentPath = parent?.filename;
|
||||
@@ -1412,7 +1487,8 @@ Module._extensions['.js'] = function(module, filename) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
module._compile(content, filename);
|
||||
|
||||
module._compile(content, filename, false);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,16 +15,25 @@ const {
|
||||
hardenRegExp,
|
||||
} = primordials;
|
||||
|
||||
const assert = require('internal/assert');
|
||||
const {
|
||||
ERR_REQUIRE_ESM,
|
||||
ERR_NETWORK_IMPORT_DISALLOWED,
|
||||
ERR_UNKNOWN_MODULE_FORMAT,
|
||||
} = require('internal/errors').codes;
|
||||
const { getOptionValue } = require('internal/options');
|
||||
const { isURL } = require('internal/url');
|
||||
const { emitExperimentalWarning } = require('internal/util');
|
||||
const { isURL, pathToFileURL, URL } = require('internal/url');
|
||||
const { emitExperimentalWarning, kEmptyObject } = require('internal/util');
|
||||
const {
|
||||
registerModule,
|
||||
getDefaultConditions,
|
||||
} = require('internal/modules/esm/utils');
|
||||
const { kImplicitAssertType } = require('internal/modules/esm/assert');
|
||||
const {
|
||||
maybeCacheSourceMap,
|
||||
} = require('internal/source_map/source_map_cache');
|
||||
const { canParse } = internalBinding('url');
|
||||
const { ModuleWrap } = internalBinding('module_wrap');
|
||||
let defaultResolve, defaultLoad, defaultLoadSync, importMetaInitializer;
|
||||
|
||||
/**
|
||||
@@ -184,8 +193,6 @@ class ModuleLoader {
|
||||
|
||||
async eval(source, url, isEntryPoint = false) {
|
||||
const evalInstance = (url) => {
|
||||
const { ModuleWrap } = internalBinding('module_wrap');
|
||||
const { registerModule } = require('internal/modules/esm/utils');
|
||||
const module = new ModuleWrap(url, undefined, source, 0, 0);
|
||||
registerModule(module, {
|
||||
__proto__: null,
|
||||
@@ -197,7 +204,7 @@ class ModuleLoader {
|
||||
|
||||
return module;
|
||||
};
|
||||
const ModuleJob = require('internal/modules/esm/module_job');
|
||||
const { ModuleJob } = require('internal/modules/esm/module_job');
|
||||
const job = new ModuleJob(
|
||||
this, url, undefined, evalInstance, false, false);
|
||||
this.loadCache.set(url, undefined, job);
|
||||
@@ -250,6 +257,118 @@ class ModuleLoader {
|
||||
return job;
|
||||
}
|
||||
|
||||
/**
|
||||
* This constructs (creates, instantiates and evaluates) a module graph that
|
||||
* is require()'d.
|
||||
* @param {string} filename Resolved filename of the module being require()'d
|
||||
* @param {string} source Source code. TODO(joyeecheung): pass the raw buffer.
|
||||
* @param {string} isMain Whether this module is a main module.
|
||||
* @returns {ModuleNamespaceObject}
|
||||
*/
|
||||
importSyncForRequire(filename, source, isMain) {
|
||||
const url = pathToFileURL(filename).href;
|
||||
let job = this.loadCache.get(url, kImplicitAssertType);
|
||||
// This module is already loaded, check whether it's synchronous and return the
|
||||
// namespace.
|
||||
if (job !== undefined) {
|
||||
return job.module.getNamespaceSync();
|
||||
}
|
||||
|
||||
// TODO(joyeecheung): refactor this so that we pre-parse in C++ and hit the
|
||||
// cache here, or use a carrier object to carry the compiled module script
|
||||
// into the constructor to ensure cache hit.
|
||||
const wrap = new ModuleWrap(url, undefined, source, 0, 0);
|
||||
// Cache the source map for the module if present.
|
||||
if (wrap.sourceMapURL) {
|
||||
maybeCacheSourceMap(url, source, null, false, undefined, wrap.sourceMapURL);
|
||||
}
|
||||
const { registerModule } = require('internal/modules/esm/utils');
|
||||
// TODO(joyeecheung): refactor so that the default options are shared across
|
||||
// the built-in loaders.
|
||||
registerModule(wrap, {
|
||||
__proto__: null,
|
||||
initializeImportMeta: (meta, wrap) => this.importMetaInitialize(meta, { url }),
|
||||
importModuleDynamically: (specifier, wrap, importAttributes) => {
|
||||
return this.import(specifier, url, importAttributes);
|
||||
},
|
||||
});
|
||||
|
||||
const inspectBrk = (isMain && getOptionValue('--inspect-brk'));
|
||||
|
||||
const { ModuleJobSync } = require('internal/modules/esm/module_job');
|
||||
job = new ModuleJobSync(this, url, kEmptyObject, wrap, isMain, inspectBrk);
|
||||
this.loadCache.set(url, kImplicitAssertType, job);
|
||||
return job.runSync().namespace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve individual module requests and create or get the cached ModuleWraps for
|
||||
* each of them. This is only used to create a module graph being require()'d.
|
||||
* @param {string} specifier Specifier of the the imported module.
|
||||
* @param {string} parentURL Where the import comes from.
|
||||
* @param {object} importAttributes import attributes from the import statement.
|
||||
* @returns {ModuleWrap}
|
||||
*/
|
||||
getModuleWrapForRequire(specifier, parentURL, importAttributes) {
|
||||
assert(getOptionValue('--experimental-require-module'));
|
||||
|
||||
if (canParse(specifier)) {
|
||||
const protocol = new URL(specifier).protocol;
|
||||
if (protocol === 'https:' || protocol === 'http:') {
|
||||
throw new ERR_NETWORK_IMPORT_DISALLOWED(specifier, parentURL,
|
||||
'ES modules cannot be loaded by require() from the network');
|
||||
}
|
||||
assert(protocol === 'file:' || protocol === 'node:' || protocol === 'data:');
|
||||
}
|
||||
|
||||
const requestKey = this.#resolveCache.serializeKey(specifier, importAttributes);
|
||||
let resolveResult = this.#resolveCache.get(requestKey, parentURL);
|
||||
if (resolveResult == null) {
|
||||
resolveResult = this.defaultResolve(specifier, parentURL, importAttributes);
|
||||
this.#resolveCache.set(requestKey, parentURL, resolveResult);
|
||||
}
|
||||
|
||||
const { url, format } = resolveResult;
|
||||
const resolvedImportAttributes = resolveResult.importAttributes ?? importAttributes;
|
||||
let job = this.loadCache.get(url, resolvedImportAttributes.type);
|
||||
if (job !== undefined) {
|
||||
// This module is previously imported before. We will return the module now and check
|
||||
// asynchronicity of the entire graph later, after the graph is instantiated.
|
||||
return job.module;
|
||||
}
|
||||
|
||||
defaultLoadSync ??= require('internal/modules/esm/load').defaultLoadSync;
|
||||
const loadResult = defaultLoadSync(url, { format, importAttributes });
|
||||
const { responseURL, source } = loadResult;
|
||||
let { format: finalFormat } = loadResult;
|
||||
this.validateLoadResult(url, finalFormat);
|
||||
if (finalFormat === 'commonjs') {
|
||||
finalFormat = 'commonjs-sync';
|
||||
} else if (finalFormat === 'wasm') {
|
||||
assert.fail('WASM is currently unsupported by require(esm)');
|
||||
}
|
||||
|
||||
const translator = getTranslators().get(finalFormat);
|
||||
if (!translator) {
|
||||
throw new ERR_UNKNOWN_MODULE_FORMAT(finalFormat, responseURL);
|
||||
}
|
||||
|
||||
const isMain = (parentURL === undefined);
|
||||
const wrap = FunctionPrototypeCall(translator, this, responseURL, source, isMain);
|
||||
assert(wrap instanceof ModuleWrap); // No asynchronous translators should be called.
|
||||
|
||||
if (process.env.WATCH_REPORT_DEPENDENCIES && process.send) {
|
||||
process.send({ 'watch:import': [url] });
|
||||
}
|
||||
|
||||
const inspectBrk = (isMain && getOptionValue('--inspect-brk'));
|
||||
const { ModuleJobSync } = require('internal/modules/esm/module_job');
|
||||
job = new ModuleJobSync(this, url, importAttributes, wrap, isMain, inspectBrk);
|
||||
|
||||
this.loadCache.set(url, importAttributes.type, job);
|
||||
return job.module;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and cache an object representing a loaded module.
|
||||
* @param {string} url The absolute URL that was resolved for this module
|
||||
@@ -277,8 +396,9 @@ class ModuleLoader {
|
||||
(url, isMain) => callTranslator(this.loadSync(url, context), isMain) :
|
||||
async (url, isMain) => callTranslator(await this.load(url, context), isMain);
|
||||
|
||||
const isMain = parentURL === undefined;
|
||||
const inspectBrk = (
|
||||
parentURL === undefined &&
|
||||
isMain &&
|
||||
getOptionValue('--inspect-brk')
|
||||
);
|
||||
|
||||
@@ -286,13 +406,13 @@ class ModuleLoader {
|
||||
process.send({ 'watch:import': [url] });
|
||||
}
|
||||
|
||||
const ModuleJob = require('internal/modules/esm/module_job');
|
||||
const { ModuleJob } = require('internal/modules/esm/module_job');
|
||||
const job = new ModuleJob(
|
||||
this,
|
||||
url,
|
||||
importAttributes,
|
||||
moduleProvider,
|
||||
parentURL === undefined,
|
||||
isMain,
|
||||
inspectBrk,
|
||||
sync,
|
||||
);
|
||||
|
||||
@@ -8,9 +8,9 @@ const {
|
||||
ObjectSetPrototypeOf,
|
||||
PromiseResolve,
|
||||
PromisePrototypeThen,
|
||||
ReflectApply,
|
||||
RegExpPrototypeExec,
|
||||
RegExpPrototypeSymbolReplace,
|
||||
ReflectApply,
|
||||
SafePromiseAllReturnArrayLike,
|
||||
SafePromiseAllReturnVoid,
|
||||
SafeSet,
|
||||
@@ -20,7 +20,7 @@ const {
|
||||
globalThis,
|
||||
} = primordials;
|
||||
|
||||
const { ModuleWrap } = internalBinding('module_wrap');
|
||||
const { ModuleWrap, kEvaluated } = internalBinding('module_wrap');
|
||||
const {
|
||||
privateSymbols: {
|
||||
entry_point_module_private_symbol,
|
||||
@@ -52,24 +52,29 @@ const isCommonJSGlobalLikeNotDefinedError = (errorMessage) =>
|
||||
(globalLike) => errorMessage === `${globalLike} is not defined`,
|
||||
);
|
||||
|
||||
/* A ModuleJob tracks the loading of a single Module, and the ModuleJobs of
|
||||
* its dependencies, over time. */
|
||||
class ModuleJob {
|
||||
// `loader` is the Loader instance used for loading dependencies.
|
||||
// `moduleProvider` is a function
|
||||
constructor(loader, url, importAttributes = { __proto__: null },
|
||||
moduleProvider, isMain, inspectBrk, sync = false) {
|
||||
class ModuleJobBase {
|
||||
constructor(loader, url, importAttributes, moduleWrapMaybePromise, isMain, inspectBrk) {
|
||||
this.loader = loader;
|
||||
this.importAttributes = importAttributes;
|
||||
this.isMain = isMain;
|
||||
this.inspectBrk = inspectBrk;
|
||||
|
||||
this.url = url;
|
||||
this.module = moduleWrapMaybePromise;
|
||||
}
|
||||
}
|
||||
|
||||
this.module = undefined;
|
||||
/* A ModuleJob tracks the loading of a single Module, and the ModuleJobs of
|
||||
* its dependencies, over time. */
|
||||
class ModuleJob extends ModuleJobBase {
|
||||
// `loader` is the Loader instance used for loading dependencies.
|
||||
constructor(loader, url, importAttributes = { __proto__: null },
|
||||
moduleProvider, isMain, inspectBrk, sync = false) {
|
||||
const modulePromise = ReflectApply(moduleProvider, loader, [url, isMain]);
|
||||
super(loader, url, importAttributes, modulePromise, isMain, inspectBrk);
|
||||
// Expose the promise to the ModuleWrap directly for linking below.
|
||||
// `this.module` is also filled in below.
|
||||
this.modulePromise = ReflectApply(moduleProvider, loader, [url, isMain]);
|
||||
this.modulePromise = modulePromise;
|
||||
|
||||
if (sync) {
|
||||
this.module = this.modulePromise;
|
||||
@@ -255,5 +260,41 @@ class ModuleJob {
|
||||
return { __proto__: null, module: this.module };
|
||||
}
|
||||
}
|
||||
ObjectSetPrototypeOf(ModuleJob.prototype, null);
|
||||
module.exports = ModuleJob;
|
||||
|
||||
// This is a fully synchronous job and does not spawn additional threads in any way.
|
||||
// All the steps are ensured to be synchronous and it throws on instantiating
|
||||
// an asynchronous graph.
|
||||
class ModuleJobSync extends ModuleJobBase {
|
||||
constructor(loader, url, importAttributes, moduleWrap, isMain, inspectBrk) {
|
||||
super(loader, url, importAttributes, moduleWrap, isMain, inspectBrk, true);
|
||||
assert(this.module instanceof ModuleWrap);
|
||||
const moduleRequests = this.module.getModuleRequestsSync();
|
||||
for (let i = 0; i < moduleRequests.length; ++i) {
|
||||
const { 0: specifier, 1: attributes } = moduleRequests[i];
|
||||
const wrap = this.loader.getModuleWrapForRequire(specifier, url, attributes);
|
||||
const isLast = (i === moduleRequests.length - 1);
|
||||
// TODO(joyeecheung): make the resolution callback deal with both promisified
|
||||
// an raw module wraps, then we don't need to wrap it with a promise here.
|
||||
this.module.cacheResolvedWrapsSync(specifier, PromiseResolve(wrap), isLast);
|
||||
}
|
||||
}
|
||||
|
||||
async run() {
|
||||
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 };
|
||||
}
|
||||
|
||||
runSync() {
|
||||
this.module.instantiateSync();
|
||||
setHasStartedUserESMExecution();
|
||||
const namespace = this.module.evaluateSync();
|
||||
return { __proto__: null, module: this.module, namespace };
|
||||
}
|
||||
}
|
||||
|
||||
ObjectSetPrototypeOf(ModuleJobBase.prototype, null);
|
||||
module.exports = {
|
||||
ModuleJob, ModuleJobSync, ModuleJobBase,
|
||||
};
|
||||
@@ -97,8 +97,8 @@ class LoadCache extends SafeMap {
|
||||
validateString(url, 'url');
|
||||
validateString(type, 'type');
|
||||
|
||||
const ModuleJob = require('internal/modules/esm/module_job');
|
||||
if (job instanceof ModuleJob !== true &&
|
||||
const { ModuleJobBase } = require('internal/modules/esm/module_job');
|
||||
if (job instanceof ModuleJobBase !== true &&
|
||||
typeof job !== 'function') {
|
||||
throw new ERR_INVALID_ARG_TYPE('job', 'ModuleJob', job);
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ const {
|
||||
} = require('internal/modules/helpers');
|
||||
const {
|
||||
Module: CJSModule,
|
||||
cjsParseCache,
|
||||
cjsSourceCache,
|
||||
cjsExportsCache,
|
||||
} = require('internal/modules/cjs/loader');
|
||||
const { fileURLToPath, pathToFileURL, URL } = require('internal/url');
|
||||
@@ -81,7 +81,7 @@ let cjsParse;
|
||||
*/
|
||||
async function initCJSParse() {
|
||||
if (typeof WebAssembly === 'undefined') {
|
||||
cjsParse = require('internal/deps/cjs-module-lexer/lexer').parse;
|
||||
initCJSParseSync();
|
||||
} else {
|
||||
const { parse, init } =
|
||||
require('internal/deps/cjs-module-lexer/dist/lexer');
|
||||
@@ -89,11 +89,19 @@ async function initCJSParse() {
|
||||
await init();
|
||||
cjsParse = parse;
|
||||
} catch {
|
||||
cjsParse = require('internal/deps/cjs-module-lexer/lexer').parse;
|
||||
initCJSParseSync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function initCJSParseSync() {
|
||||
// TODO(joyeecheung): implement a binding that directly compiles using
|
||||
// v8::WasmModuleObject::Compile() synchronously.
|
||||
if (cjsParse === undefined) {
|
||||
cjsParse = require('internal/deps/cjs-module-lexer/lexer').parse;
|
||||
}
|
||||
}
|
||||
|
||||
const translators = new SafeMap();
|
||||
exports.translators = translators;
|
||||
exports.enrichCJSError = enrichCJSError;
|
||||
@@ -162,7 +170,7 @@ async function importModuleDynamically(specifier, { url }, attributes) {
|
||||
}
|
||||
|
||||
// Strategy for loading a standard JavaScript module.
|
||||
translators.set('module', async function moduleStrategy(url, source, isMain) {
|
||||
translators.set('module', function moduleStrategy(url, source, isMain) {
|
||||
assertBufferSource(source, true, 'load');
|
||||
source = stringify(source);
|
||||
debug(`Translating StandardModule ${url}`);
|
||||
@@ -323,6 +331,16 @@ function createCJSModuleWrap(url, source, isMain, loadCJS = loadCJSModule) {
|
||||
|
||||
}
|
||||
|
||||
translators.set('commonjs-sync', function requireCommonJS(url, source, isMain) {
|
||||
initCJSParseSync();
|
||||
assert(!isMain); // This is only used by imported CJS modules.
|
||||
|
||||
return createCJSModuleWrap(url, source, isMain, (module, source, url, filename) => {
|
||||
assert(module === CJSModule._cache[filename]);
|
||||
CJSModule._load(filename, null, false);
|
||||
});
|
||||
});
|
||||
|
||||
// Handle CommonJS modules referenced by `require` calls.
|
||||
// This translator function must be sync, as `require` is sync.
|
||||
translators.set('require-commonjs', (url, source, isMain) => {
|
||||
@@ -371,7 +389,7 @@ function cjsPreparseModuleExports(filename, source) {
|
||||
// TODO: Do we want to keep hitting the user mutable CJS loader here?
|
||||
let module = CJSModule._cache[filename];
|
||||
if (module) {
|
||||
const cached = cjsParseCache.get(module);
|
||||
const cached = cjsSourceCache.get(module);
|
||||
if (cached) {
|
||||
return { module, exportNames: cached.exportNames };
|
||||
}
|
||||
@@ -395,7 +413,7 @@ function cjsPreparseModuleExports(filename, source) {
|
||||
const exportNames = new SafeSet(new SafeArrayIterator(exports));
|
||||
|
||||
// Set first for cycles.
|
||||
cjsParseCache.set(module, { source, exportNames });
|
||||
cjsSourceCache.set(module, { source, exportNames });
|
||||
|
||||
if (reexports.length) {
|
||||
module.filename = filename;
|
||||
@@ -522,6 +540,8 @@ translators.set('wasm', async function(url, source) {
|
||||
|
||||
let compiled;
|
||||
try {
|
||||
// TODO(joyeecheung): implement a binding that directly compiles using
|
||||
// v8::WasmModuleObject::Compile() synchronously.
|
||||
compiled = await WebAssembly.compile(source);
|
||||
} catch (err) {
|
||||
err.message = errPath(url) + ': ' + err.message;
|
||||
|
||||
@@ -16,7 +16,7 @@ const { getCodePath, isSea } = internalBinding('sea');
|
||||
|
||||
function embedderRunCjs(contents) {
|
||||
const filename = process.execPath;
|
||||
const compiledWrapper = wrapSafe(
|
||||
const { function: compiledWrapper } = wrapSafe(
|
||||
isSea() ? getCodePath() : filename,
|
||||
contents);
|
||||
|
||||
|
||||
@@ -321,6 +321,61 @@ static Local<Object> createImportAttributesContainer(
|
||||
return attributes;
|
||||
}
|
||||
|
||||
void ModuleWrap::GetModuleRequestsSync(
|
||||
const FunctionCallbackInfo<Value>& args) {
|
||||
Realm* realm = Realm::GetCurrent(args);
|
||||
Isolate* isolate = args.GetIsolate();
|
||||
|
||||
Local<Object> that = args.This();
|
||||
|
||||
ModuleWrap* obj;
|
||||
ASSIGN_OR_RETURN_UNWRAP(&obj, that);
|
||||
|
||||
CHECK(!obj->linked_);
|
||||
|
||||
Local<Module> module = obj->module_.Get(isolate);
|
||||
Local<FixedArray> module_requests = module->GetModuleRequests();
|
||||
const int module_requests_length = module_requests->Length();
|
||||
|
||||
std::vector<Local<Value>> requests;
|
||||
requests.reserve(module_requests_length);
|
||||
// call the dependency resolve callbacks
|
||||
for (int i = 0; i < module_requests_length; i++) {
|
||||
Local<ModuleRequest> module_request =
|
||||
module_requests->Get(realm->context(), i).As<ModuleRequest>();
|
||||
Local<FixedArray> raw_attributes = module_request->GetImportAssertions();
|
||||
std::vector<Local<Value>> request = {
|
||||
module_request->GetSpecifier(),
|
||||
createImportAttributesContainer(realm, isolate, raw_attributes, 3),
|
||||
};
|
||||
requests.push_back(Array::New(isolate, request.data(), request.size()));
|
||||
}
|
||||
|
||||
args.GetReturnValue().Set(
|
||||
Array::New(isolate, requests.data(), requests.size()));
|
||||
}
|
||||
|
||||
void ModuleWrap::CacheResolvedWrapsSync(
|
||||
const FunctionCallbackInfo<Value>& args) {
|
||||
Isolate* isolate = args.GetIsolate();
|
||||
|
||||
CHECK_EQ(args.Length(), 3);
|
||||
CHECK(args[0]->IsString());
|
||||
CHECK(args[1]->IsPromise());
|
||||
CHECK(args[2]->IsBoolean());
|
||||
|
||||
ModuleWrap* dependent;
|
||||
ASSIGN_OR_RETURN_UNWRAP(&dependent, args.This());
|
||||
|
||||
Utf8Value specifier(isolate, args[0]);
|
||||
dependent->resolve_cache_[specifier.ToString()].Reset(isolate,
|
||||
args[1].As<Promise>());
|
||||
|
||||
if (args[2].As<v8::Boolean>()->Value()) {
|
||||
dependent->linked_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
void ModuleWrap::Link(const FunctionCallbackInfo<Value>& args) {
|
||||
Realm* realm = Realm::GetCurrent(args);
|
||||
Isolate* isolate = args.GetIsolate();
|
||||
@@ -486,6 +541,129 @@ void ModuleWrap::Evaluate(const FunctionCallbackInfo<Value>& args) {
|
||||
args.GetReturnValue().Set(result.ToLocalChecked());
|
||||
}
|
||||
|
||||
void ModuleWrap::InstantiateSync(const FunctionCallbackInfo<Value>& args) {
|
||||
Realm* realm = Realm::GetCurrent(args);
|
||||
Isolate* isolate = args.GetIsolate();
|
||||
ModuleWrap* obj;
|
||||
ASSIGN_OR_RETURN_UNWRAP(&obj, args.This());
|
||||
Local<Context> context = obj->context();
|
||||
Local<Module> module = obj->module_.Get(isolate);
|
||||
Environment* env = realm->env();
|
||||
|
||||
{
|
||||
TryCatchScope try_catch(env);
|
||||
USE(module->InstantiateModule(context, ResolveModuleCallback));
|
||||
|
||||
// clear resolve cache on instantiate
|
||||
obj->resolve_cache_.clear();
|
||||
|
||||
if (try_catch.HasCaught() && !try_catch.HasTerminated()) {
|
||||
CHECK(!try_catch.Message().IsEmpty());
|
||||
CHECK(!try_catch.Exception().IsEmpty());
|
||||
AppendExceptionLine(env,
|
||||
try_catch.Exception(),
|
||||
try_catch.Message(),
|
||||
ErrorHandlingMode::MODULE_ERROR);
|
||||
try_catch.ReThrow();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If --experimental-print-required-tla is true, proceeds to evaluation even
|
||||
// if it's async because we want to search for the TLA and help users locate
|
||||
// them.
|
||||
if (module->IsGraphAsync() && !env->options()->print_required_tla) {
|
||||
THROW_ERR_REQUIRE_ASYNC_MODULE(env);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void ModuleWrap::EvaluateSync(const FunctionCallbackInfo<Value>& args) {
|
||||
Realm* realm = Realm::GetCurrent(args);
|
||||
Isolate* isolate = args.GetIsolate();
|
||||
ModuleWrap* obj;
|
||||
ASSIGN_OR_RETURN_UNWRAP(&obj, args.This());
|
||||
Local<Context> context = obj->context();
|
||||
Local<Module> module = obj->module_.Get(isolate);
|
||||
Environment* env = realm->env();
|
||||
|
||||
Local<Value> result;
|
||||
{
|
||||
TryCatchScope try_catch(env);
|
||||
if (!module->Evaluate(context).ToLocal(&result)) {
|
||||
if (try_catch.HasCaught()) {
|
||||
if (!try_catch.HasTerminated()) {
|
||||
try_catch.ReThrow();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CHECK(result->IsPromise());
|
||||
Local<Promise> promise = result.As<Promise>();
|
||||
if (promise->State() == Promise::PromiseState::kRejected) {
|
||||
Local<Value> exception = promise->Result();
|
||||
Local<v8::Message> message =
|
||||
v8::Exception::CreateMessage(isolate, exception);
|
||||
AppendExceptionLine(
|
||||
env, exception, message, ErrorHandlingMode::MODULE_ERROR);
|
||||
isolate->ThrowException(exception);
|
||||
return;
|
||||
}
|
||||
|
||||
if (module->IsGraphAsync()) {
|
||||
CHECK(env->options()->print_required_tla);
|
||||
auto stalled = module->GetStalledTopLevelAwaitMessage(isolate);
|
||||
if (stalled.size() != 0) {
|
||||
for (auto pair : stalled) {
|
||||
Local<v8::Message> message = std::get<1>(pair);
|
||||
|
||||
std::string reason = "Error: unexpected top-level await at ";
|
||||
std::string info =
|
||||
FormatErrorMessage(isolate, context, "", message, true);
|
||||
reason += info;
|
||||
FPrintF(stderr, "%s\n", reason);
|
||||
}
|
||||
}
|
||||
THROW_ERR_REQUIRE_ASYNC_MODULE(env);
|
||||
return;
|
||||
}
|
||||
|
||||
CHECK_EQ(promise->State(), Promise::PromiseState::kFulfilled);
|
||||
|
||||
args.GetReturnValue().Set(module->GetModuleNamespace());
|
||||
}
|
||||
|
||||
void ModuleWrap::GetNamespaceSync(const FunctionCallbackInfo<Value>& args) {
|
||||
Realm* realm = Realm::GetCurrent(args);
|
||||
Isolate* isolate = args.GetIsolate();
|
||||
ModuleWrap* obj;
|
||||
ASSIGN_OR_RETURN_UNWRAP(&obj, args.This());
|
||||
Local<Module> module = obj->module_.Get(isolate);
|
||||
|
||||
switch (module->GetStatus()) {
|
||||
case v8::Module::Status::kUninstantiated:
|
||||
case v8::Module::Status::kInstantiating:
|
||||
return realm->env()->ThrowError(
|
||||
"cannot get namespace, module has not been instantiated");
|
||||
case v8::Module::Status::kEvaluating:
|
||||
return THROW_ERR_REQUIRE_ASYNC_MODULE(realm->env());
|
||||
case v8::Module::Status::kInstantiated:
|
||||
case v8::Module::Status::kEvaluated:
|
||||
case v8::Module::Status::kErrored:
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
if (module->IsGraphAsync()) {
|
||||
return THROW_ERR_REQUIRE_ASYNC_MODULE(realm->env());
|
||||
}
|
||||
Local<Value> result = module->GetModuleNamespace();
|
||||
args.GetReturnValue().Set(result);
|
||||
}
|
||||
|
||||
void ModuleWrap::GetNamespace(const FunctionCallbackInfo<Value>& args) {
|
||||
Realm* realm = Realm::GetCurrent(args);
|
||||
Isolate* isolate = args.GetIsolate();
|
||||
@@ -818,6 +996,12 @@ void ModuleWrap::CreatePerIsolateProperties(IsolateData* isolate_data,
|
||||
ModuleWrap::kInternalFieldCount);
|
||||
|
||||
SetProtoMethod(isolate, tpl, "link", Link);
|
||||
SetProtoMethod(isolate, tpl, "getModuleRequestsSync", GetModuleRequestsSync);
|
||||
SetProtoMethod(
|
||||
isolate, tpl, "cacheResolvedWrapsSync", CacheResolvedWrapsSync);
|
||||
SetProtoMethod(isolate, tpl, "instantiateSync", InstantiateSync);
|
||||
SetProtoMethod(isolate, tpl, "evaluateSync", EvaluateSync);
|
||||
SetProtoMethod(isolate, tpl, "getNamespaceSync", GetNamespaceSync);
|
||||
SetProtoMethod(isolate, tpl, "instantiate", Instantiate);
|
||||
SetProtoMethod(isolate, tpl, "evaluate", Evaluate);
|
||||
SetProtoMethod(isolate, tpl, "setExport", SetSyntheticExport);
|
||||
@@ -869,6 +1053,11 @@ void ModuleWrap::RegisterExternalReferences(
|
||||
registry->Register(New);
|
||||
|
||||
registry->Register(Link);
|
||||
registry->Register(GetModuleRequestsSync);
|
||||
registry->Register(CacheResolvedWrapsSync);
|
||||
registry->Register(InstantiateSync);
|
||||
registry->Register(EvaluateSync);
|
||||
registry->Register(GetNamespaceSync);
|
||||
registry->Register(Instantiate);
|
||||
registry->Register(Evaluate);
|
||||
registry->Register(SetSyntheticExport);
|
||||
|
||||
@@ -79,6 +79,14 @@ class ModuleWrap : public BaseObject {
|
||||
~ModuleWrap() override;
|
||||
|
||||
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void GetModuleRequestsSync(
|
||||
const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void CacheResolvedWrapsSync(
|
||||
const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void InstantiateSync(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void EvaluateSync(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void GetNamespaceSync(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
|
||||
static void Link(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void Instantiate(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void Evaluate(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
|
||||
@@ -92,6 +92,7 @@ void OOMErrorHandler(const char* location, const v8::OOMDetails& details);
|
||||
V(ERR_MODULE_NOT_FOUND, Error) \
|
||||
V(ERR_NON_CONTEXT_AWARE_DISABLED, Error) \
|
||||
V(ERR_OUT_OF_RANGE, RangeError) \
|
||||
V(ERR_REQUIRE_ASYNC_MODULE, Error) \
|
||||
V(ERR_SCRIPT_EXECUTION_INTERRUPTED, Error) \
|
||||
V(ERR_SCRIPT_EXECUTION_TIMEOUT, Error) \
|
||||
V(ERR_STRING_TOO_LONG, Error) \
|
||||
@@ -188,6 +189,10 @@ ERRORS_WITH_CODE(V)
|
||||
"creating Workers") \
|
||||
V(ERR_NON_CONTEXT_AWARE_DISABLED, \
|
||||
"Loading non context-aware native addons has been disabled") \
|
||||
V(ERR_REQUIRE_ASYNC_MODULE, \
|
||||
"require() cannot be used on an ESM graph with top-level await. Use " \
|
||||
"import() instead. To see where the top-level await comes from, use " \
|
||||
"--experimental-print-required-tla.") \
|
||||
V(ERR_SCRIPT_EXECUTION_INTERRUPTED, \
|
||||
"Script execution was interrupted by `SIGINT`") \
|
||||
V(ERR_TLS_PSK_SET_IDENTIY_HINT_FAILED, "Failed to set PSK identity hint") \
|
||||
|
||||
@@ -355,6 +355,17 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
|
||||
"ES module syntax, try again to evaluate them as ES modules",
|
||||
&EnvironmentOptions::detect_module,
|
||||
kAllowedInEnvvar);
|
||||
AddOption("--experimental-print-required-tla",
|
||||
"Print pending top-level await. If --experimental-require-module "
|
||||
"is true, evaluate asynchronous graphs loaded by `require()` but "
|
||||
"do not run the microtasks, in order to to find and print "
|
||||
"top-level await in the graph",
|
||||
&EnvironmentOptions::print_required_tla,
|
||||
kAllowedInEnvvar);
|
||||
AddOption("--experimental-require-module",
|
||||
"Allow loading explicit ES Modules in require().",
|
||||
&EnvironmentOptions::require_module,
|
||||
kAllowedInEnvvar);
|
||||
AddOption("--diagnostic-dir",
|
||||
"set dir for all output files"
|
||||
" (default: current working directory)",
|
||||
|
||||
@@ -105,6 +105,8 @@ class EnvironmentOptions : public Options {
|
||||
bool abort_on_uncaught_exception = false;
|
||||
std::vector<std::string> conditions;
|
||||
bool detect_module = false;
|
||||
bool print_required_tla = false;
|
||||
bool require_module = false;
|
||||
std::string dns_result_order;
|
||||
bool enable_source_maps = false;
|
||||
bool experimental_fetch = true;
|
||||
|
||||
@@ -6,16 +6,16 @@ import { describe, it } from 'node:test';
|
||||
|
||||
|
||||
// Expect note to be included in the error output
|
||||
const expectedNote = 'To load an ES module, ' +
|
||||
'set "type": "module" in the package.json ' +
|
||||
'or use the .mjs extension.';
|
||||
// Don't match the following sentence because it can change as features are
|
||||
// added.
|
||||
const expectedNote = 'Warning: To load an ES module';
|
||||
|
||||
const mustIncludeMessage = {
|
||||
getMessage: () => (stderr) => `${expectedNote} not found in ${stderr}`,
|
||||
getMessage: (stderr) => `${expectedNote} not found in ${stderr}`,
|
||||
includeNote: true,
|
||||
};
|
||||
const mustNotIncludeMessage = {
|
||||
getMessage: () => (stderr) => `${expectedNote} must not be included in ${stderr}`,
|
||||
getMessage: (stderr) => `${expectedNote} must not be included in ${stderr}`,
|
||||
includeNote: false,
|
||||
};
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ require('../common');
|
||||
const { strictEqual, throws } = require('assert');
|
||||
const { createModuleLoader } = require('internal/modules/esm/loader');
|
||||
const { LoadCache, ResolveCache } = require('internal/modules/esm/module_map');
|
||||
const ModuleJob = require('internal/modules/esm/module_job');
|
||||
const { ModuleJob } = require('internal/modules/esm/module_job');
|
||||
const createDynamicModule = require(
|
||||
'internal/modules/esm/create_dynamic_module');
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
// Flags: --experimental-require-module
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
|
||||
(async () => {
|
||||
await import('../fixtures/es-modules/tla/resolved.mjs');
|
||||
assert.throws(() => {
|
||||
require('../fixtures/es-modules/tla/resolved.mjs');
|
||||
}, {
|
||||
code: 'ERR_REQUIRE_ASYNC_MODULE',
|
||||
});
|
||||
})().then(common.mustCall());
|
||||
@@ -0,0 +1,35 @@
|
||||
// Flags: --experimental-require-module
|
||||
'use strict';
|
||||
|
||||
require('../common');
|
||||
const assert = require('assert');
|
||||
const { isModuleNamespaceObject } = require('util/types');
|
||||
|
||||
// If only "require" exports are defined, return "require" exports.
|
||||
{
|
||||
const mod = require('../fixtures/es-modules/exports-require-only/load.cjs');
|
||||
assert.deepStrictEqual({ ...mod }, { type: 'cjs' });
|
||||
assert(!isModuleNamespaceObject(mod));
|
||||
}
|
||||
|
||||
// If only "import" exports are defined, throw ERR_PACKAGE_PATH_NOT_EXPORTED
|
||||
// instead of falling back to it, because the request comes from require().
|
||||
assert.throws(() => {
|
||||
require('../fixtures/es-modules/exports-import-only/load.cjs');
|
||||
}, {
|
||||
code: 'ERR_PACKAGE_PATH_NOT_EXPORTED'
|
||||
});
|
||||
|
||||
// If both are defined, "require" is used.
|
||||
{
|
||||
const mod = require('../fixtures/es-modules/exports-both/load.cjs');
|
||||
assert.deepStrictEqual({ ...mod }, { type: 'cjs' });
|
||||
assert(!isModuleNamespaceObject(mod));
|
||||
}
|
||||
|
||||
// If "import" and "default" are defined, "default" is used.
|
||||
{
|
||||
const mod = require('../fixtures/es-modules/exports-import-default/load.cjs');
|
||||
assert.deepStrictEqual({ ...mod }, { type: 'cjs' });
|
||||
assert(!isModuleNamespaceObject(mod));
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// Flags: --experimental-require-module
|
||||
'use strict';
|
||||
|
||||
require('../common');
|
||||
const assert = require('assert');
|
||||
const { isModuleNamespaceObject } = require('util/types');
|
||||
|
||||
const mod = require('../fixtures/es-modules/package-default-extension/index.mjs');
|
||||
assert.deepStrictEqual({ ...mod }, { entry: 'mjs' });
|
||||
assert(isModuleNamespaceObject(mod));
|
||||
|
||||
assert.throws(() => {
|
||||
const mod = require('../fixtures/es-modules/package-default-extension');
|
||||
console.log(mod); // In case it succeeds, log the result for debugging.
|
||||
}, {
|
||||
code: 'MODULE_NOT_FOUND',
|
||||
});
|
||||
@@ -0,0 +1,32 @@
|
||||
// Flags: --experimental-require-module
|
||||
'use strict';
|
||||
|
||||
// Tests that previously dynamically import()'ed results are reference equal to
|
||||
// require()'d results.
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const path = require('path');
|
||||
const { pathToFileURL } = require('url');
|
||||
|
||||
(async () => {
|
||||
const modules = [
|
||||
'../fixtures/es-module-loaders/module-named-exports.mjs',
|
||||
'../fixtures/es-modules/import-esm.mjs',
|
||||
'../fixtures/es-modules/require-cjs.mjs',
|
||||
'../fixtures/es-modules/cjs-exports.mjs',
|
||||
'../common/index.mjs',
|
||||
'../fixtures/es-modules/package-type-module/index.js',
|
||||
];
|
||||
for (const id of modules) {
|
||||
const url = pathToFileURL(path.resolve(__dirname, id));
|
||||
const imported = await import(url);
|
||||
const required = require(id);
|
||||
assert.strictEqual(imported, required,
|
||||
`import()'ed and require()'ed result of ${id} was not reference equal`);
|
||||
}
|
||||
|
||||
const id = '../fixtures/es-modules/data-import.mjs';
|
||||
const imported = await import(id);
|
||||
const required = require(id);
|
||||
assert.strictEqual(imported.data, required.data);
|
||||
})().then(common.mustCall());
|
||||
@@ -0,0 +1,32 @@
|
||||
// Flags: --experimental-require-module
|
||||
'use strict';
|
||||
|
||||
// Tests that previously dynamically require()'ed results are reference equal to
|
||||
// import()'d results.
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const { pathToFileURL } = require('url');
|
||||
const path = require('path');
|
||||
|
||||
(async () => {
|
||||
const modules = [
|
||||
'../fixtures/es-module-loaders/module-named-exports.mjs',
|
||||
'../fixtures/es-modules/import-esm.mjs',
|
||||
'../fixtures/es-modules/require-cjs.mjs',
|
||||
'../fixtures/es-modules/cjs-exports.mjs',
|
||||
'../common/index.mjs',
|
||||
'../fixtures/es-modules/package-type-module/index.js',
|
||||
];
|
||||
for (const id of modules) {
|
||||
const url = pathToFileURL(path.resolve(__dirname, id));
|
||||
const required = require(id);
|
||||
const imported = await import(url);
|
||||
assert.strictEqual(imported, required,
|
||||
`import()'ed and require()'ed result of ${id} was not reference equal`);
|
||||
}
|
||||
|
||||
const id = '../fixtures/es-modules/data-import.mjs';
|
||||
const required = require(id);
|
||||
const imported = await import(id);
|
||||
assert.strictEqual(imported.data, required.data);
|
||||
})().then(common.mustCall());
|
||||
@@ -0,0 +1,48 @@
|
||||
// Flags: --experimental-require-module
|
||||
'use strict';
|
||||
|
||||
require('../common');
|
||||
const assert = require('assert');
|
||||
const { spawnSyncAndExit } = require('../common/child_process');
|
||||
const fixtures = require('../common/fixtures');
|
||||
|
||||
spawnSyncAndExit(process.execPath, [
|
||||
'--experimental-require-module',
|
||||
fixtures.path('es-modules/require-syntax-error.cjs'),
|
||||
], {
|
||||
status: 1,
|
||||
signal: null,
|
||||
stderr(output) {
|
||||
assert.match(output, /var foo bar;/);
|
||||
assert.match(output, /SyntaxError: Unexpected identifier 'bar'/);
|
||||
return true;
|
||||
},
|
||||
});
|
||||
|
||||
spawnSyncAndExit(process.execPath, [
|
||||
'--experimental-require-module',
|
||||
fixtures.path('es-modules/require-reference-error.cjs'),
|
||||
], {
|
||||
status: 1,
|
||||
signal: null,
|
||||
trim: true,
|
||||
stdout: 'executed',
|
||||
stderr(output) {
|
||||
assert.match(output, /module\.exports = { hello: 'world' };/);
|
||||
assert.match(output, /ReferenceError: module is not defined/);
|
||||
return true;
|
||||
},
|
||||
});
|
||||
|
||||
spawnSyncAndExit(process.execPath, [
|
||||
'--experimental-require-module',
|
||||
fixtures.path('es-modules/require-throw-error.cjs'),
|
||||
], {
|
||||
status: 1,
|
||||
signal: null,
|
||||
stderr(output) {
|
||||
assert.match(output, /throw new Error\('test'\);/);
|
||||
assert.match(output, /Error: test/);
|
||||
return true;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,33 @@
|
||||
// Flags: --experimental-require-module
|
||||
'use strict';
|
||||
|
||||
// Tests that require()ing modules without explicit module type information
|
||||
// warns and errors.
|
||||
require('../common');
|
||||
const assert = require('assert');
|
||||
const { isModuleNamespaceObject } = require('util/types');
|
||||
|
||||
assert.throws(() => {
|
||||
require('../fixtures/es-modules/package-without-type/noext-esm');
|
||||
}, {
|
||||
message: /Unexpected token 'export'/
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
require('../fixtures/es-modules/loose.js');
|
||||
}, {
|
||||
message: /Unexpected token 'export'/
|
||||
});
|
||||
|
||||
{
|
||||
// .mjs should not be matched as default extensions.
|
||||
const id = '../fixtures/es-modules/should-not-be-resolved';
|
||||
assert.throws(() => {
|
||||
require(id);
|
||||
}, {
|
||||
code: 'MODULE_NOT_FOUND'
|
||||
});
|
||||
const mod = require(`${id}.mjs`);
|
||||
assert.deepStrictEqual({ ...mod }, { hello: 'world' });
|
||||
assert(isModuleNamespaceObject(mod));
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
'use strict';
|
||||
|
||||
require('../common');
|
||||
const { spawnSyncAndExitWithoutError } = require('../common/child_process');
|
||||
const fixtures = require('../common/fixtures');
|
||||
|
||||
const stderr = /ExperimentalWarning: Support for loading ES Module in require/;
|
||||
|
||||
// Test named exports.
|
||||
{
|
||||
spawnSyncAndExitWithoutError(
|
||||
process.execPath,
|
||||
[ '--experimental-require-module', '-r', fixtures.path('../fixtures/es-module-loaders/module-named-exports.mjs') ],
|
||||
{
|
||||
stderr,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Test ESM that import ESM.
|
||||
{
|
||||
spawnSyncAndExitWithoutError(
|
||||
process.execPath,
|
||||
[ '--experimental-require-module', '-r', fixtures.path('../fixtures/es-modules/import-esm.mjs') ],
|
||||
{
|
||||
stderr,
|
||||
stdout: 'world',
|
||||
trim: true,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Test ESM that import CJS.
|
||||
{
|
||||
spawnSyncAndExitWithoutError(
|
||||
process.execPath,
|
||||
[ '--experimental-require-module', '-r', fixtures.path('../fixtures/es-modules/cjs-exports.mjs') ],
|
||||
{
|
||||
stdout: 'ok',
|
||||
stderr,
|
||||
trim: true,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Test ESM that require() CJS.
|
||||
// Can't use the common/index.mjs here because that checks the globals, and
|
||||
// -r injects a bunch of globals.
|
||||
{
|
||||
spawnSyncAndExitWithoutError(
|
||||
process.execPath,
|
||||
[ '--experimental-require-module', '-r', fixtures.path('../fixtures/es-modules/require-cjs.mjs') ],
|
||||
{
|
||||
stdout: 'world',
|
||||
stderr,
|
||||
trim: true,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Test "type": "module" and "main" field in package.json.
|
||||
{
|
||||
spawnSyncAndExitWithoutError(
|
||||
process.execPath,
|
||||
[ '--experimental-require-module', '-r', fixtures.path('../fixtures/es-modules/package-type-module') ],
|
||||
{
|
||||
stdout: 'package-type-module',
|
||||
stderr,
|
||||
trim: true,
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// Flags: --experimental-require-module
|
||||
'use strict';
|
||||
|
||||
require('../common');
|
||||
const assert = require('assert');
|
||||
|
||||
assert.throws(() => {
|
||||
require('../fixtures/es-modules/network-import.mjs');
|
||||
}, {
|
||||
code: 'ERR_NETWORK_IMPORT_DISALLOWED'
|
||||
});
|
||||
@@ -0,0 +1,63 @@
|
||||
// Flags: --experimental-require-module
|
||||
'use strict';
|
||||
|
||||
require('../common');
|
||||
const assert = require('assert');
|
||||
const { spawnSyncAndExit } = require('../common/child_process');
|
||||
const fixtures = require('../common/fixtures');
|
||||
|
||||
const message = /require\(\) cannot be used on an ESM graph with top-level await/;
|
||||
const code = 'ERR_REQUIRE_ASYNC_MODULE';
|
||||
|
||||
assert.throws(() => {
|
||||
require('../fixtures/es-modules/tla/rejected.mjs');
|
||||
}, { message, code });
|
||||
|
||||
assert.throws(() => {
|
||||
require('../fixtures/es-modules/tla/unresolved.mjs');
|
||||
}, { message, code });
|
||||
|
||||
|
||||
assert.throws(() => {
|
||||
require('../fixtures/es-modules/tla/resolved.mjs');
|
||||
}, { message, code });
|
||||
|
||||
// Test TLA in inner graphs.
|
||||
assert.throws(() => {
|
||||
require('../fixtures/es-modules/tla/parent.mjs');
|
||||
}, { message, code });
|
||||
|
||||
{
|
||||
spawnSyncAndExit(process.execPath, [
|
||||
'--experimental-require-module',
|
||||
fixtures.path('es-modules/tla/require-execution.js'),
|
||||
], {
|
||||
signal: null,
|
||||
status: 1,
|
||||
stderr(output) {
|
||||
assert.doesNotMatch(output, /I am executed/);
|
||||
assert.match(output, message);
|
||||
return true;
|
||||
},
|
||||
stdout: ''
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
spawnSyncAndExit(process.execPath, [
|
||||
'--experimental-require-module',
|
||||
'--experimental-print-required-tla',
|
||||
fixtures.path('es-modules/tla/require-execution.js'),
|
||||
], {
|
||||
signal: null,
|
||||
status: 1,
|
||||
stderr(output) {
|
||||
assert.match(output, /I am executed/);
|
||||
assert.match(output, /Error: unexpected top-level await at.*execution\.mjs:3/);
|
||||
assert.match(output, /await Promise\.resolve\('hi'\)/);
|
||||
assert.match(output, message);
|
||||
return true;
|
||||
},
|
||||
stdout: ''
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// Flags: --experimental-require-module
|
||||
'use strict';
|
||||
|
||||
require('../common');
|
||||
const assert = require('assert');
|
||||
|
||||
const modules = [
|
||||
'../fixtures/es-module-loaders/module-named-exports.mjs',
|
||||
'../fixtures/es-modules/import-esm.mjs',
|
||||
'../fixtures/es-modules/require-cjs.mjs',
|
||||
'../fixtures/es-modules/cjs-exports.mjs',
|
||||
'../common/index.mjs',
|
||||
'../fixtures/es-modules/package-type-module/index.js',
|
||||
];
|
||||
|
||||
for (const id of modules) {
|
||||
const first = require(id);
|
||||
const second = require(id);
|
||||
assert.strictEqual(first, second,
|
||||
`the results of require('${id}') twice are not reference equal`);
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
// Flags: --experimental-require-module
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const { isModuleNamespaceObject } = require('util/types');
|
||||
|
||||
common.expectWarning(
|
||||
'ExperimentalWarning',
|
||||
'Support for loading ES Module in require() is an experimental feature ' +
|
||||
'and might change at any time'
|
||||
);
|
||||
|
||||
// Test named exports.
|
||||
{
|
||||
const mod = require('../fixtures/es-module-loaders/module-named-exports.mjs');
|
||||
assert.deepStrictEqual({ ...mod }, { foo: 'foo', bar: 'bar' });
|
||||
assert(isModuleNamespaceObject(mod));
|
||||
}
|
||||
|
||||
// Test ESM that import ESM.
|
||||
{
|
||||
const mod = require('../fixtures/es-modules/import-esm.mjs');
|
||||
assert.deepStrictEqual({ ...mod }, { hello: 'world' });
|
||||
assert(isModuleNamespaceObject(mod));
|
||||
}
|
||||
|
||||
// Test ESM that import CJS.
|
||||
{
|
||||
const mod = require('../fixtures/es-modules/cjs-exports.mjs');
|
||||
assert.deepStrictEqual({ ...mod }, {});
|
||||
assert(isModuleNamespaceObject(mod));
|
||||
}
|
||||
|
||||
// Test ESM that require() CJS.
|
||||
{
|
||||
const mjs = require('../common/index.mjs');
|
||||
// Only comparing a few properties because the ESM version of test/common doesn't
|
||||
// re-export everything from the CJS version.
|
||||
assert.strictEqual(common.mustCall, mjs.mustCall);
|
||||
assert.strictEqual(common.localIPv6Hosts, mjs.localIPv6Hosts);
|
||||
assert(!isModuleNamespaceObject(common));
|
||||
assert(isModuleNamespaceObject(mjs));
|
||||
}
|
||||
|
||||
// Test "type": "module" and "main" field in package.json.
|
||||
// Also, test default export.
|
||||
{
|
||||
const mod = require('../fixtures/es-modules/package-type-module');
|
||||
assert.deepStrictEqual({ ...mod }, { default: 'package-type-module' });
|
||||
assert(isModuleNamespaceObject(mod));
|
||||
}
|
||||
|
||||
// Test data: import.
|
||||
{
|
||||
const mod = require('../fixtures/es-modules/data-import.mjs');
|
||||
assert.deepStrictEqual({ ...mod }, {
|
||||
data: { hello: 'world' },
|
||||
id: 'data:text/javascript,export default %7B%20hello%3A%20%22world%22%20%7D'
|
||||
});
|
||||
assert(isModuleNamespaceObject(mod));
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
export { default as data } from 'data:text/javascript,export default %7B%20hello%3A%20%22world%22%20%7D';
|
||||
export const id = 'data:text/javascript,export default %7B%20hello%3A%20%22world%22%20%7D';
|
||||
@@ -1,4 +1,3 @@
|
||||
{
|
||||
"type": "module"
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = require('dep');
|
||||
+1
@@ -0,0 +1 @@
|
||||
module.exports = { type: "cjs" };
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
export const type = "mjs";
|
||||
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./mod.mjs",
|
||||
"require": "./mod.cjs"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = require('dep');
|
||||
+1
@@ -0,0 +1 @@
|
||||
module.exports = { type: "cjs" };
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
export const type = "mjs";
|
||||
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./mod.mjs",
|
||||
"default": "./mod.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
module.exports = require('dep');
|
||||
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
export const type = 'mjs';
|
||||
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./mod.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = require('dep');
|
||||
+1
@@ -0,0 +1 @@
|
||||
module.exports = { type: "cjs" };
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"exports": {
|
||||
".": {
|
||||
"require": "./mod.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
import { hello } from './imported-esm.mjs';
|
||||
console.log(hello);
|
||||
export { hello };
|
||||
+1
@@ -0,0 +1 @@
|
||||
export const hello = 'world';
|
||||
+1
@@ -0,0 +1 @@
|
||||
import 'http://example.com/foo.js';
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = { entry: 'cjs' };
|
||||
@@ -0,0 +1 @@
|
||||
export const entry = 'mjs';
|
||||
@@ -0,0 +1,3 @@
|
||||
// Reference errors are not thrown until reference happens.
|
||||
console.log('executed');
|
||||
module.exports = { hello: 'world' };
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
import { createRequire } from "node:module";
|
||||
const require = createRequire(import.meta.url);
|
||||
const exports = require('./required-cjs');
|
||||
console.log(exports.hello);
|
||||
export default exports;
|
||||
@@ -0,0 +1,2 @@
|
||||
'use strict';
|
||||
require('./reference-error.mjs');
|
||||
@@ -0,0 +1,2 @@
|
||||
'use strict';
|
||||
require('./syntax-error.mjs');
|
||||
@@ -0,0 +1,2 @@
|
||||
'use strict';
|
||||
require('./throw-error.mjs');
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
hello: 'world',
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export const hello = 'world';
|
||||
+1
@@ -0,0 +1 @@
|
||||
var foo bar;
|
||||
+1
@@ -0,0 +1 @@
|
||||
throw new Error('test');
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
import process from 'node:process';
|
||||
process._rawDebug('I am executed');
|
||||
await Promise.resolve('hi');
|
||||
@@ -0,0 +1 @@
|
||||
require('./execution.mjs');
|
||||
+1
@@ -0,0 +1 @@
|
||||
await Promise.resolve('hello');
|
||||
Reference in new issue
Block a user