diff --git a/doc/api/cli.md b/doc/api/cli.md index bddd4a5c0a5f..af67e5478ea9 100644 --- a/doc/api/cli.md +++ b/doc/api/cli.md @@ -1509,33 +1509,6 @@ added: Enable experimental support for inspector network resources. -### `--experimental-loader=module` - - - -> This flag is discouraged and may be removed in a future version of Node.js. -> Please use -> [`--import` with `register()`][preloading asynchronous module customization hooks] instead. - -Specify the `module` containing exported [asynchronous module customization hooks][]. -`module` may be any string accepted as an [`import` specifier][]. - -This feature requires `--allow-worker` if used with the [Permission Model][]. - ### `--experimental-network-inspection` -Type: Runtime +Type: End-of-Life -[`module.register()`][] is deprecated. Use [`module.registerHooks()`][] -instead. +The `module.register()` API and the `--experimental-loader` and `--loader` +flags have been removed. Use [`module.registerHooks()`][] instead. -The `module.register()` API provides off-thread async hooks for customizing ES modules; -the `module.registerHooks()` API provides similar hooks that are synchronous, in-thread, and -work for all types of modules. -Supporting async hooks has proven to be complex, involving worker threads orchestration, and there are issues -that have proven unresolvable. See [caveats of asynchronous customization hooks][]. Please migrate to -`module.registerHooks()` as soon as possible as `module.register()` will be -removed in a future version of Node.js. +The `module.register()` API provided off-thread async module customization +hooks for customizing ES modules; the `module.registerHooks()` API provides +similar hooks that are synchronous and work for all types of modules. ### DEP0206: Calling `digest()` on an already-finalized `Hmac` instance @@ -4935,7 +4934,6 @@ Type: Documentation-only [`message.trailersDistinct`]: http.md#messagetrailersdistinct [`message.trailers`]: http.md#messagetrailers [`module.createRequire()`]: module.md#modulecreaterequirefilename -[`module.register()`]: module.md#moduleregisterspecifier-parenturl-options [`module.registerHooks()`]: module.md#moduleregisterhooksoptions [`os.networkInterfaces()`]: os.md#osnetworkinterfaces [`os.tmpdir()`]: os.md#ostmpdir @@ -4991,7 +4989,6 @@ Type: Documentation-only [`zlib.bytesWritten`]: zlib.md#zlibbyteswritten [alloc]: buffer.md#static-method-bufferallocsize-fill-encoding [alloc_unsafe_size]: buffer.md#static-method-bufferallocunsafesize-alignment -[caveats of asynchronous customization hooks]: module.md#caveats-of-asynchronous-customization-hooks [from_arraybuffer]: buffer.md#static-method-bufferfromarraybuffer-byteoffset-length [from_string_encoding]: buffer.md#static-method-bufferfromstring-encoding [legacy URL API]: url.md#legacy-url-api diff --git a/doc/api/errors.md b/doc/api/errors.md index 2a1e423d10dc..27004c6d5eb2 100644 --- a/doc/api/errors.md +++ b/doc/api/errors.md @@ -733,13 +733,6 @@ by the `node:assert` module. An attempt was made to register something that is not a function as an `AsyncHooks` callback. - - -### `ERR_ASYNC_LOADER_REQUEST_NEVER_SETTLED` - -An operation related to module loading is customized by an asynchronous loader -hook that never settled the promise before the loader thread exits. - ### `ERR_ASYNC_RESOURCE_DOMAIN_REMOVED` @@ -2354,19 +2347,6 @@ for more information. IP is blocked by `net.BlockList`. - - -### `ERR_LOADER_CHAIN_INCOMPLETE` - - - -An ESM loader hook returned without calling `next()` and without explicitly -signaling a short circuit. - ### `ERR_LOAD_SQLITE_EXTENSION` @@ -3915,6 +3895,17 @@ attempting a [`require()`][] operation or when loading the program entry point. > Stability: 0 - Deprecated. These error codes are either inconsistent, or have > been removed. + + +### `ERR_ASYNC_LOADER_REQUEST_NEVER_SETTLED` + + + +An operation related to module loading is customized by an asynchronous loader +hook that never settled the promise before the loader thread exits. + ### `ERR_CANNOT_TRANSFER_OBJECT` @@ -4153,6 +4144,20 @@ changes: An invalid transfer object was passed to `postMessage()`. + + +### `ERR_LOADER_CHAIN_INCOMPLETE` + + + +An ESM loader hook returned without calling `next()` and without explicitly +signaling a short circuit. + ### `ERR_MANIFEST_ASSERT_INTEGRITY` diff --git a/doc/api/esm.md b/doc/api/esm.md index ef15a51dcfe5..bc4af89045f7 100644 --- a/doc/api/esm.md +++ b/doc/api/esm.md @@ -199,8 +199,7 @@ ES modules are resolved and cached as URLs. This means that special characters must be [percent-encoded][], such as `#` with `%23` and `?` with `%3F`. `file:`, `node:`, and `data:` URL schemes are supported. A specifier like -`'https://example.com/app.js'` is not supported natively in Node.js unless using -a [custom HTTPS loader][]. +`'https://example.com/app.js'` is not supported natively in Node.js. #### `file:` URLs @@ -493,8 +492,6 @@ resolutions are subject to the permitted exports resolutions within the package. * This can result in synchronous file-system operations, which can impact performance similarly to `require.resolve`. -* This feature is not available within custom loaders (it would - create a deadlock). **Non-standard API**: @@ -1316,8 +1313,8 @@ _isImports_, _conditions_) ### Customizing ESM specifier resolution algorithm [Module customization hooks][] provide a mechanism for customizing the ESM -specifier resolution algorithm. An example that provides CommonJS-style -resolution for ESM specifiers is [commonjs-extension-resolution-loader][]. +specifier resolution algorithm, for example to provide CommonJS-style +resolution for ESM specifiers. @@ -1360,8 +1357,6 @@ resolution for ESM specifiers is [commonjs-extension-resolution-loader][]. [`process.dlopen`]: process.md#processdlopenmodule-filename-flags [`require(esm)`]: modules.md#loading-ecmascript-modules-using-require [`url.fileURLToPath()`]: url.md#urlfileurltopathurl-options -[commonjs-extension-resolution-loader]: https://github.com/nodejs/loaders-test/tree/main/commonjs-extension-resolution-loader -[custom https loader]: module.md#import-from-https [import.meta.resolve]: #importmetaresolvespecifier [merve]: https://github.com/anonrig/merve/tree/v1.0.0 [percent-encoded]: url.md#percent-encoding-in-urls diff --git a/doc/api/module.md b/doc/api/module.md index f86167e8d44f..309114f9468c 100644 --- a/doc/api/module.md +++ b/doc/api/module.md @@ -176,56 +176,6 @@ isBuiltin('fs'); // true isBuiltin('wss'); // false ``` -## `module.register(specifier[, parentURL][, options])` - - - -> Stability: 0 - Deprecated: Use [`module.registerHooks()`][] instead. - -* `specifier` {string|URL} Customization hooks to be registered; this should be - the same string that would be passed to `import()`, except that if it is - relative, it is resolved relative to `parentURL`. -* `parentURL` {string|URL} If you want to resolve `specifier` relative to a base - URL, such as `import.meta.url`, you can pass that URL here. **Default:** - `'data:'` -* `options` {Object} - * `parentURL` {string|URL} If you want to resolve `specifier` relative to a - base URL, such as `import.meta.url`, you can pass that URL here. This - property is ignored if the `parentURL` is supplied as the second argument. - **Default:** `'data:'` - * `data` {any} Any arbitrary, cloneable JavaScript value to pass into the - [`initialize`][] hook. - * `transferList` {Object\[]} [transferable objects][] to be passed into the - `initialize` hook. - -Register a module that exports [hooks][] that customize Node.js module -resolution and loading behavior. See [Customization hooks][]. - -This feature requires `--allow-worker` if used with the [Permission Model][]. - ## `module.registerHooks(options)` -Node.js currently supports two types of module customization hooks: - -1. [`module.registerHooks(options)`][`module.registerHooks()`]: takes synchronous hook - functions that are run directly on the thread where the modules are loaded. -2. [`module.register(specifier[, parentURL][, options])`][`register`]: takes specifier to a - module that exports asynchronous hook functions. The functions are run on a - separate loader thread. - -The asynchronous hooks incur extra overhead from inter-thread communication, -and have [several caveats][caveats of asynchronous customization hooks] especially -when customizing CommonJS modules in the module graph. -In most cases, it's recommended to use synchronous hooks via `module.registerHooks()` -for simplicity. - -### Synchronous customization hooks +Node.js supports module customization hooks registered with +[`module.registerHooks()`][]. > Stability: 1.2 - Release candidate -#### Registration of synchronous customization hooks +### Registration of customization hooks -To register synchronous customization hooks, use [`module.registerHooks()`][], which -takes [synchronous hook functions][] directly in-line. +To register customization hooks, use [`module.registerHooks()`][], which +takes [hook functions][] directly in-line. ```mjs // register-hooks.js @@ -711,7 +648,7 @@ registerHooks({ }); ``` -##### Registering hooks before application code runs with flags +#### Registering hooks before application code runs with flags The hooks can be registered before the application code is run by using the [`--import`][] or [`--require`][] flag: @@ -736,7 +673,7 @@ Using `--import` or `--require` ensures that the hooks are registered before any application code is loaded, including the entry point of the application and for any worker threads by default as well. -##### Registering hooks before application code runs programmatically +#### Registering hooks before application code runs programmatically Alternatively, `registerHooks()` can be called from the entry point. @@ -751,7 +688,7 @@ module. ```mjs import { registerHooks } from 'node:module'; -registerHooks({ /* implementation of synchronous hooks */ }); +registerHooks({ /* implementation of hooks */ }); // If loaded using static import, the hooks would not be applied when loading // my-app.mjs, because statically imported modules are all executed before its @@ -765,7 +702,7 @@ await import('./my-app.mjs'); ```cjs const { registerHooks } = require('node:module'); -registerHooks({ /* implementation of synchronous hooks */ }); +registerHooks({ /* implementation of hooks */ }); import('./my-app.mjs'); // Or, if my-app.mjs does not have top-level await or it's a CommonJS module, @@ -773,7 +710,7 @@ import('./my-app.mjs'); // require('./my-app.mjs'); ``` -##### Registering hooks before application code runs with a `data:` URL +#### Registering hooks before application code runs with a `data:` URL Alternatively, inline JavaScript code can be embedded in `data:` URLs to register the hooks before the application code runs. For example, @@ -782,7 +719,7 @@ the hooks before the application code runs. For example, node --import 'data:text/javascript,import {registerHooks} from "node:module"; registerHooks(/* hooks code */);' ./my-app.js ``` -#### Convention of hooks and chaining +### Convention of hooks and chaining Hooks are part of a chain, even if that chain consists of only one custom (user-provided) hook and the default hook, which is always present. @@ -833,14 +770,13 @@ hook to signal that the chain is intentionally ending at your hook. If a hook should be applied when loading other hook modules, the other hook modules should be loaded after the hook is registered. -#### Deregistration of synchronous customization hooks +### Deregistration of customization hooks The object returned by `registerHooks()` has a `deregister()` method that can be used to remove the hooks from the chain. Once `deregister()` is called, the hooks will no longer be invoked during module resolution or loading. -This is currently only available for synchronous hooks registered via `registerHooks()`, not for asynchronous -hooks registered via `module.register()`. +This is available for hooks registered via `registerHooks()`. ```mjs import { registerHooks } from 'node:module'; @@ -890,7 +826,7 @@ hooks.deregister(); require('./another-module.cjs'); ``` -#### Hook functions accepted by `module.registerHooks()` +### Hook functions accepted by `module.registerHooks()` -The `module.registerHooks()` method accepts the following synchronous hook functions. +The `module.registerHooks()` method accepts the following hook functions. ```mjs function resolve(specifier, context, nextResolve) { @@ -910,16 +846,16 @@ function load(url, context, nextLoad) { } ``` -Synchronous hooks are run in the same thread and the same [realm][] where the modules -are loaded, the code in the hook function can pass values to the modules being referenced -directly via global variables or other shared states. +Hooks are run in the same [realm][] as the modules being loaded, so the code in the +hook function can pass values to the modules being referenced directly via global +variables or other shared states. -Unlike the asynchronous hooks, the synchronous hooks are not inherited into child worker -threads by default, though if the hooks are registered using a file preloaded by -[`--import`][] or [`--require`][], child worker threads can inherit the preloaded scripts -via `process.execArgv` inheritance. See [the documentation of `Worker`][] for details. +Hooks are not inherited into child worker threads by default. If the hooks are +registered using a file preloaded by [`--import`][] or [`--require`][], child worker threads +can inherit the preloaded scripts via `process.execArgv` inheritance. See +[the documentation of `Worker`][] for details. -#### Synchronous `resolve(specifier, context, nextResolve)` +### `resolve(specifier, context, nextResolve)` - -The [`register`][] method can be used to register a module that exports a set of -hooks. The hooks are functions that are called by Node.js to customize the -module resolution and loading process. The exported functions must have specific -names and signatures, and they must be exported as named exports. - -```mjs -export async function initialize({ number, port }) { - // Receives data from `register`. -} - -export async function resolve(specifier, context, nextResolve) { - // Take an `import` or `require` specifier and resolve it to a URL. -} - -export async function load(url, context, nextLoad) { - // Take a resolved URL and return the source code to be evaluated. -} -``` - -Asynchronous hooks are run in a separate thread, isolated from the main thread where -application code runs. That means it is a different [realm][]. The hooks thread -may be terminated by the main thread at any time, so do not depend on -asynchronous operations (like `console.log`) to complete. They are inherited into -child workers by default. - -#### `initialize()` - - - -* `data` {any} The data from `register(loader, import.meta.url, { data })`. - -The `initialize` hook is only accepted by [`register`][]. `registerHooks()` does -not support nor need it since initialization done for synchronous hooks can be run -directly before the call to `registerHooks()`. - -The `initialize` hook provides a way to define a custom function that runs in -the hooks thread when the hooks module is initialized. Initialization happens -when the hooks module is registered via [`register`][]. - -This hook can receive data from a [`register`][] invocation, including -ports and other transferable objects. The return value of `initialize` can be a -{Promise}, in which case it will be awaited before the main application thread -execution resumes. - -Module customization code: - -```mjs -// path-to-my-hooks.js - -export async function initialize({ number, port }) { - port.postMessage(`increment: ${number + 1}`); -} -``` - -Caller code: - -```mjs -import assert from 'node:assert'; -import { register } from 'node:module'; -import { MessageChannel } from 'node:worker_threads'; - -// This example showcases how a message channel can be used to communicate -// between the main (application) thread and the hooks running on the hooks -// thread, by sending `port2` to the `initialize` hook. -const { port1, port2 } = new MessageChannel(); - -port1.on('message', (msg) => { - assert.strictEqual(msg, 'increment: 2'); -}); -port1.unref(); - -register('./path-to-my-hooks.js', { - parentURL: import.meta.url, - data: { number: 1, port: port2 }, - transferList: [port2], -}); -``` - -```cjs -const assert = require('node:assert'); -const { register } = require('node:module'); -const { pathToFileURL } = require('node:url'); -const { MessageChannel } = require('node:worker_threads'); - -// This example showcases how a message channel can be used to communicate -// between the main (application) thread and the hooks running on the hooks -// thread, by sending `port2` to the `initialize` hook. -const { port1, port2 } = new MessageChannel(); - -port1.on('message', (msg) => { - assert.strictEqual(msg, 'increment: 2'); -}); -port1.unref(); - -register('./path-to-my-hooks.js', { - parentURL: pathToFileURL(__filename), - data: { number: 1, port: port2 }, - transferList: [port2], -}); -``` - -#### Asynchronous `resolve(specifier, context, nextResolve)` - - - -* `specifier` {string} -* `context` {Object} - * `conditions` {string\[]} Export conditions of the relevant `package.json` - * `importAttributes` {Object} An object whose key-value pairs represent the - attributes for the module to import - * `parentURL` {string|undefined} The module importing this one, or undefined - if this is the Node.js entry point -* `nextResolve` {Function} The subsequent `resolve` hook in the chain, or the - Node.js default `resolve` hook after the last user-supplied `resolve` hook - * `specifier` {string} - * `context` {Object|undefined} When omitted, the defaults are provided. When provided, defaults - are merged in with preference to the provided properties. -* Returns: {Object|Promise} The asynchronous version takes either an object containing the - following properties, or a `Promise` that will resolve to such an object. - * `format` {string|null|undefined} A hint to the `load` hook (it might be ignored). It can be a - module format (such as `'commonjs'` or `'module'`) or an arbitrary value like `'css'` or - `'yaml'`. - * `importAttributes` {Object|undefined} The import attributes to use when - caching the module (optional; if excluded the input will be used) - * `shortCircuit` {undefined|boolean} A signal that this hook intends to - terminate the chain of `resolve` hooks. **Default:** `false` - * `url` {string} The absolute URL to which this input resolves - -The asynchronous version works similarly to the synchronous version, only that the -`nextResolve` function returns a `Promise`, and the `resolve` hook itself can return a `Promise`. - -> **Warning** In the case of the asynchronous version, despite support for returning -> promises and async functions, calls to `resolve` may still block the main thread which -> can impact performance. - -> **Warning** The `resolve` hook invoked for `require()` calls inside CommonJS modules -> customized by asynchronous hooks does not receive the original specifier passed to -> `require()`. Instead, it receives a URL already fully resolved using the default -> CommonJS resolution. - -> **Warning** In the CommonJS modules that are customized by the asynchronous customization hooks, -> `require.resolve()` and `require()` will use `"import"` export condition instead of -> `"require"`, which may cause unexpected behaviors when loading dual packages. - -```mjs -export async function resolve(specifier, context, nextResolve) { - // When calling `defaultResolve`, the arguments can be modified. For example, - // to change the specifier or add conditions. - if (specifier.includes('foo')) { - specifier = specifier.replace('foo', 'bar'); - return nextResolve(specifier, { - ...context, - conditions: [...context.conditions, 'another-condition'], - }); - } - - // The hook can also skips default resolution and provide a custom URL. - if (specifier === 'special-module') { - return { - url: 'file:///path/to/special-module.mjs', - format: 'module', - shortCircuit: true, // This is mandatory if not calling nextResolve(). - }; - } - - // If no customization is needed, defer to the next hook in the chain which would be the - // Node.js default resolve if this is the last user-specified loader. - return nextResolve(specifier); -} -``` - -#### Asynchronous `load(url, context, nextLoad)` - - - -* `url` {string} The URL returned by the `resolve` chain -* `context` {Object} - * `conditions` {string\[]} Export conditions of the relevant `package.json` - * `format` {string|null|undefined} The format optionally supplied by the - `resolve` hook chain. This can be any string value as an input; input values do not need to - conform to the list of acceptable return values described below. - * `importAttributes` {Object} -* `nextLoad` {Function} The subsequent `load` hook in the chain, or the - Node.js default `load` hook after the last user-supplied `load` hook - * `url` {string} - * `context` {Object|undefined} When omitted, defaults are provided. When provided, defaults are - merged in with preference to the provided properties. In the default `nextLoad`, if - the module pointed to by `url` does not have explicit module type information, - `context.format` is mandatory. - -* Returns: {Promise} The asynchronous version takes either an object containing the - following properties, or a `Promise` that will resolve to such an object. - * `format` {string} - * `shortCircuit` {undefined|boolean} A signal that this hook intends to - terminate the chain of `load` hooks. **Default:** `false` - * `source` {string|ArrayBuffer|TypedArray} The source for Node.js to evaluate - -> **Warning**: The asynchronous `load` hook and namespaced exports from CommonJS -> modules are incompatible. Attempting to use them together will result in an empty -> object from the import. This may be addressed in the future. This does not apply -> to the synchronous `load` hook, in which case exports can be used as usual. - -The asynchronous version works similarly to the synchronous version, though -when using the asynchronous `load` hook, omitting vs providing a `source` for -`'commonjs'` has very different effects: - -* When a `source` is provided, all `require` calls from this module will be - processed by the ESM loader with registered `resolve` and `load` hooks; all - `require.resolve` calls from this module will be processed by the ESM loader - with registered `resolve` hooks; only a subset of the CommonJS API will be - available (e.g. no `require.extensions`, no `require.cache`, no - `require.resolve.paths`) and monkey-patching on the CommonJS module loader - will not apply. -* If `source` is undefined or `null`, it will be handled by the CommonJS module - loader and `require`/`require.resolve` calls will not go through the - registered hooks. This behavior for nullish `source` is temporary — in the - future, nullish `source` will not be supported. - -These caveats do not apply to the synchronous `load` hook, in which case -the complete set of CommonJS APIs available to the customized CommonJS -modules, and `require`/`require.resolve` always go through the registered -hooks. - -The Node.js internal asynchronous `load` implementation, which is the value of `next` for the -last hook in the `load` chain, returns `null` for `source` when `format` is -`'commonjs'` for backward compatibility. Here is an example hook that would -opt-in to using the non-default behavior: - -```mjs -import { readFile } from 'node:fs/promises'; - -// Asynchronous version accepted by module.register(). This fix is not needed -// for the synchronous version accepted by module.registerHooks(). -export async function load(url, context, nextLoad) { - const result = await nextLoad(url, context); - if (result.format === 'commonjs') { - result.source ??= await readFile(new URL(result.responseURL ?? url)); - } - return result; -} -``` - -This doesn't apply to the synchronous `load` hook either, in which case the -`source` returned contains source code loaded by the next hook, regardless -of module format. - ### Examples The various module customization hooks can be used together to accomplish wide-ranging customizations of the Node.js code loading and evaluation behaviors. -#### Import from HTTPS - -The hook below registers hooks to enable rudimentary support for such -specifiers. While this may seem like a significant improvement to Node.js core -functionality, there are substantial downsides to actually using these hooks: -performance is much slower than loading files from disk, there is no caching, -and there is no security. - -```mjs -// https-hooks.mjs -import { get } from 'node:https'; - -export function load(url, context, nextLoad) { - // For JavaScript to be loaded over the network, we need to fetch and - // return it. - if (url.startsWith('https://')) { - return new Promise((resolve, reject) => { - get(url, (res) => { - let data = ''; - res.setEncoding('utf8'); - res.on('data', (chunk) => data += chunk); - res.on('end', () => resolve({ - // This example assumes all network-provided JavaScript is ES module - // code. - format: 'module', - shortCircuit: true, - source: data, - })); - }).on('error', (err) => reject(err)); - }); - } - - // Let Node.js handle all other URLs. - return nextLoad(url); -} -``` - -```mjs -// main.mjs -import { VERSION } from 'https://coffeescript.org/browser-compiler-modern/coffeescript.js'; - -console.log(VERSION); -``` - -With the preceding hooks module, running -`node --import 'data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register(pathToFileURL("./https-hooks.mjs"));' ./main.mjs` -prints the current version of CoffeeScript per the module at the URL in -`main.mjs`. - - - #### Transpilation Sources that are in formats Node.js doesn't understand can be converted into @@ -1685,58 +1052,6 @@ JavaScript using the [`load` hook][load hook]. This is less performant than transpiling source files before running Node.js; transpiler hooks should only be used for development and testing purposes. -##### Asynchronous version - -```mjs -// coffeescript-hooks.mjs -import { readFile } from 'node:fs/promises'; -import { findPackageJSON } from 'node:module'; -import coffeescript from 'coffeescript'; - -const extensionsRegex = /\.(coffee|litcoffee|coffee\.md)$/; - -export async function load(url, context, nextLoad) { - if (extensionsRegex.test(url)) { - // CoffeeScript files can be either CommonJS or ES modules. Use a custom format - // to tell Node.js not to detect its module type. - const { source: rawSource } = await nextLoad(url, { ...context, format: 'coffee' }); - // This hook converts CoffeeScript source code into JavaScript source code - // for all imported CoffeeScript files. - const transformedSource = coffeescript.compile(rawSource.toString(), url); - - // To determine how Node.js would interpret the transpilation result, - // search up the file system for the nearest parent package.json file - // and read its "type" field. - return { - format: await getPackageType(url), - shortCircuit: true, - source: transformedSource, - }; - } - - // Let Node.js handle all other URLs. - return nextLoad(url, context); -} - -async function getPackageType(url) { - // `url` is only a file path during the first iteration when passed the - // resolved url from the load() hook - // an actual file path from load() will contain a file extension as it's - // required by the spec - // this simple truthy check for whether `url` contains a file extension will - // work for most projects but does not cover some edge-cases (such as - // extensionless files or a url ending in a trailing space) - const pJson = findPackageJSON(url); - - return readFile(pJson, 'utf8') - .then(JSON.parse) - .then((json) => json?.type) - .catch(() => undefined); -} -``` - -##### Synchronous version - ```mjs // coffeescript-sync-hooks.mjs import { readFileSync } from 'node:fs'; @@ -1807,9 +1122,8 @@ able to return an `format` known to Node.js even in the absence of an explicit t (if undefined) or `ERR_UNKNOWN_MODULE_FORMAT` (if it's not a known format listed in the [load hook][] documentation). -With the preceding hooks modules, running -`node --import 'data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register(pathToFileURL("./coffeescript-hooks.mjs"));' ./main.coffee` -or `node --import ./coffeescript-sync-hooks.mjs ./main.coffee` +With the preceding hooks module, running +`node --import ./coffeescript-sync-hooks.mjs ./main.coffee` causes `main.coffee` to be turned into JavaScript after its source code is loaded from disk but before Node.js executes it; and so on for any `.coffee`, `.litcoffee` or `.coffee.md` files referenced via `import` statements of any @@ -1822,25 +1136,6 @@ The previous two examples defined `load` hooks. This is an example of a which specifiers to override to other URLs (this is a very simplistic implementation of a small subset of the "import maps" specification). -##### Asynchronous version - -```mjs -// import-map-hooks.js -import fs from 'node:fs/promises'; - -const { imports } = JSON.parse(await fs.readFile('import-map.json')); - -export async function resolve(specifier, context, nextResolve) { - if (Object.hasOwn(imports, specifier)) { - return nextResolve(imports[specifier], context); - } - - return nextResolve(specifier, context); -} -``` - -##### Synchronous version - ```mjs // import-map-sync-hooks.js import fs from 'node:fs/promises'; @@ -1882,8 +1177,7 @@ import 'a-module'; console.log('some module!'); ``` -Running `node --import 'data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register(pathToFileURL("./import-map-hooks.js"));' main.js` -or `node --import ./import-map-sync-hooks.js main.js` +Running `node --import ./import-map-sync-hooks.js main.js` should print `some module!`. ## Source Map Support @@ -2104,7 +1398,6 @@ returned object contains the following keys: [Conditional exports]: packages.md#conditional-exports [Customization hooks]: #customization-hooks [ES Modules]: esm.md -[Permission Model]: permissions.md#permission-model [Source Map]: https://tc39.es/ecma426/ [Source map format]: https://tc39.es/ecma426/#sec-source-map-format [V8 JavaScript code coverage]: https://v8project.blogspot.com/2017/12/javascript-code-coverage.html @@ -2120,7 +1413,6 @@ returned object contains the following keys: [`NODE_V8_COVERAGE=dir`]: cli.md#node_v8_coveragedir [`Object.freeze()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze [`SourceMap`]: #class-modulesourcemap -[`initialize`]: #initialize [`module.constants.compileCacheStatus`]: #moduleconstantscompilecachestatus [`module.enableCompileCache()`]: #moduleenablecompilecacheoptions [`module.flushCompileCache()`]: #moduleflushcompilecache @@ -2129,22 +1421,16 @@ returned object contains the following keys: [`module.registerHooks()`]: #moduleregisterhooksoptions [`module.setSourceMapsSupport()`]: #modulesetsourcemapssupportenabled-options [`os.tmpdir()`]: os.md#ostmpdir -[`register`]: #moduleregisterspecifier-parenturl-options [`util.TextDecoder`]: util.md#class-utiltextdecoder [accepted final formats]: #accepted-final-formats-returned-by-load -[asynchronous `load` hook]: #asynchronous-loadurl-context-nextload -[asynchronous `resolve` hook]: #asynchronous-resolvespecifier-context-nextresolve -[asynchronous hook functions]: #asynchronous-hooks-accepted-by-moduleregister -[caveats of asynchronous customization hooks]: #caveats-of-asynchronous-customization-hooks -[deregistration of synchronous customization hooks]: #deregistration-of-synchronous-customization-hooks +[deregistration of customization hooks]: #deregistration-of-customization-hooks +[hook functions]: #hook-functions-accepted-by-moduleregisterhooks [hooks]: #customization-hooks -[load hook]: #synchronous-loadurl-context-nextload +[load hook]: #loadurl-context-nextload [module compile cache]: #module-compile-cache [realm]: https://tc39.es/ecma262/#realm -[resolve hook]: #synchronous-resolvespecifier-context-nextresolve +[resolve hook]: #resolvespecifier-context-nextresolve [source map include directives]: https://tc39.es/ecma426/#sec-linking-generated-code -[synchronous hook functions]: #hook-functions-accepted-by-moduleregisterhooks [the `module` object]: modules.md#the-module-object [the documentation of `Worker`]: worker_threads.md#new-workerfilename-options -[transferable objects]: worker_threads.md#portpostmessagevalue-transferlist [type-stripping]: typescript.md#type-stripping diff --git a/doc/api/test.md b/doc/api/test.md index b80bdb372dc2..fdc709196a75 100644 --- a/doc/api/test.md +++ b/doc/api/test.md @@ -2883,10 +2883,8 @@ Node.js builtin modules. Any references to the original module prior to mocking order to enable module mocking, Node.js must be started with the [`--experimental-test-module-mocks`][] command-line flag. -**Note**: [module customization hooks][] registered via the **synchronous** API effect resolution of -the `specifier` provided to `mock.module`. Customization hooks registered via the **asynchronous** -API are currently ignored (because the test runner's loader is synchronous, and node does not -support multi-chain / cross-chain loading). +**Note**: [module customization hooks][] registered with `module.registerHooks()` affect resolution of +the `specifier` provided to `mock.module`. The following example demonstrates how a mock is created for a module. diff --git a/doc/api/worker_threads.md b/doc/api/worker_threads.md index 08da7b35f152..af572039c8ac 100644 --- a/doc/api/worker_threads.md +++ b/doc/api/worker_threads.md @@ -169,27 +169,16 @@ if (isMainThread) { added: - v23.7.0 - v22.14.0 +changes: + - version: REPLACEME + pr-url: https://github.com/nodejs/node/pull/67090 + description: Always `false`, as Node.js no longer runs code in internal threads. --> * Type: {boolean} -Is `true` if this code is running inside of an internal [`Worker`][] thread (e.g the loader thread). - -```bash -node --experimental-loader ./loader.js main.js -``` - -```mjs -// loader.js -import { isInternalThread } from 'node:worker_threads'; -console.log(isInternalThread); // true -``` - -```cjs -// loader.js -const { isInternalThread } = require('node:worker_threads'); -console.log(isInternalThread); // true -``` +Always `false`. Node.js no longer runs user-visible code in internal [`Worker`][] +threads, so this is retained only for backward compatibility. ```mjs // main.js diff --git a/doc/node-config-schema.json b/doc/node-config-schema.json index 0d2cb83b517d..6787d1d8dbce 100644 --- a/doc/node-config-schema.json +++ b/doc/node-config-schema.json @@ -359,21 +359,6 @@ "type": "boolean", "description": "experimental support for importing source as text with import attributes" }, - "experimental-loader": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "array", - "minItems": 1, - "items": { - "type": "string" - } - } - ], - "description": "use the specified module as a custom loader" - }, "experimental-package-map": { "type": "string", "description": "use the specified file for package map resolution" diff --git a/doc/node.1 b/doc/node.1 index 7a32ece46d21..bc7225a4856e 100644 --- a/doc/node.1 +++ b/doc/node.1 @@ -868,11 +868,6 @@ Enable experimental support for importing modules with .It Fl -experimental-inspector-network-resource Enable experimental support for inspector network resources. . -.It Fl -experimental-loader Ns = Ns Ar module -Specify the \fBmodule\fR containing exported asynchronous module customization hooks. -\fBmodule\fR may be any string accepted as an \fBimport\fR specifier. -This feature requires \fB--allow-worker\fR if used with the Permission Model. -. .It Fl -experimental-network-inspection Enable experimental support for the network inspection with Chrome DevTools. . @@ -2270,8 +2265,6 @@ one is included in the list below. .It \fB--experimental-json-modules\fR .It -\fB--experimental-loader\fR -.It \fB--experimental-modules\fR .It \fB--experimental-package-map\fR diff --git a/lib/internal/bench_runner/cli.js b/lib/internal/bench_runner/cli.js index 5884046e146a..63757edd386b 100644 --- a/lib/internal/bench_runner/cli.js +++ b/lib/internal/bench_runner/cli.js @@ -711,7 +711,6 @@ function runFileOptionRequiresValue(arg) { } const kOptionAliases = new SafeMap([ - ['--loader', '--experimental-loader'], ['-C', '--conditions'], ['-r', '--require'], ]); diff --git a/lib/internal/errors.js b/lib/internal/errors.js index 88c4102eee4f..3c2990111fa3 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -1143,8 +1143,6 @@ E('ERR_AMBIGUOUS_ARGUMENT', 'The "%s" argument is ambiguous. %s', TypeError); E('ERR_ARG_NOT_ITERABLE', '%s must be iterable', TypeError); E('ERR_ASSERTION', '%s', Error); E('ERR_ASYNC_CALLBACK', '%s must be a function', TypeError); -E('ERR_ASYNC_LOADER_REQUEST_NEVER_SETTLED', - 'Async loader request never settled', Error); E('ERR_ASYNC_RESOURCE_DOMAIN_REMOVED', 'The domain property on AsyncResource has been removed. ' + 'Use AsyncLocalStorage instead.', Error); @@ -1603,13 +1601,6 @@ E('ERR_IPC_SYNC_FORK', 'IPC cannot be used with synchronous forks', Error); E('ERR_IP_BLOCKED', function(ip) { return `IP(${ip}) is blocked by net.BlockList`; }, Error); -E( - 'ERR_LOADER_CHAIN_INCOMPLETE', - '"%s" did not call the next hook in its chain and did not' + - ' explicitly signal a short circuit. If this is intentional, include' + - ' `shortCircuit: true` in the hook\'s return.', - Error, -); E('ERR_METHOD_NOT_IMPLEMENTED', 'The %s method is not implemented', Error); E('ERR_MISSING_ARGS', (...args) => { @@ -1787,13 +1778,14 @@ E('ERR_REQUIRE_ESM', 'all ES modules instead).\n'; return msg; }, Error); -E('ERR_REQUIRE_ESM_RACE_CONDITION', (filename, parentFilename, isForAsyncLoaderHookWorker) => { +E('ERR_REQUIRE_ESM_RACE_CONDITION', (filename, parentFilename) => { let raceMessage = `Cannot require() ES Module ${filename} because it is not yet fully loaded.\n`; raceMessage += 'This may be caused by a race condition if the module is simultaneously dynamically '; raceMessage += 'import()-ed via Promise.all().\n'; - raceMessage += 'Try await-ing the import() sequentially in a loop instead.\n'; - raceMessage += ` (From ${parentFilename ? `${parentFilename} in ` : ' '}`; - raceMessage += `${isForAsyncLoaderHookWorker ? 'loader hook worker thread' : 'non-loader-hook thread'})`; + raceMessage += 'Try await-ing the import() sequentially in a loop instead.'; + if (parentFilename) { + raceMessage += `\n (From ${parentFilename})`; + } return raceMessage; }, Error); E('ERR_SCRIPT_EXECUTION_INTERRUPTED', diff --git a/lib/internal/main/eval_string.js b/lib/internal/main/eval_string.js index 998c3fb4ada5..c662b8c2c267 100644 --- a/lib/internal/main/eval_string.js +++ b/lib/internal/main/eval_string.js @@ -30,7 +30,7 @@ markBootstrapComplete(); const code = getOptionValue('--eval'); const print = getOptionValue('--print'); -const shouldLoadESM = getOptionValue('--import').length > 0 || getOptionValue('--experimental-loader').length > 0; +const shouldLoadESM = getOptionValue('--import').length > 0; const inputType = getOptionValue('--input-type'); const tsEnabled = getOptionValue('--strip-types'); if (inputType === 'module') { diff --git a/lib/internal/main/worker_thread.js b/lib/internal/main/worker_thread.js index 1f5fa8f8212b..29bb783a1306 100644 --- a/lib/internal/main/worker_thread.js +++ b/lib/internal/main/worker_thread.js @@ -105,16 +105,14 @@ port.on('message', (message) => { mainThreadPort, } = message; - if (doEval !== 'internal') { - if (argv !== undefined) { - ArrayPrototypePushApply(process.argv, argv); - } - - const publicWorker = require('worker_threads'); - publicWorker.parentPort = publicPort; - publicWorker.workerData = workerData; + if (argv !== undefined) { + ArrayPrototypePushApply(process.argv, argv); } + const publicWorker = require('worker_threads'); + publicWorker.parentPort = publicPort; + publicWorker.workerData = workerData; + require('internal/worker').assignEnvironmentData(environmentData); setupMainThreadPort(mainThreadPort); @@ -140,24 +138,18 @@ port.on('message', (message) => { }; workerIo.sharedCwdCounter = cwdCounter; - const isLoaderHookWorker = (filename === 'internal/modules/esm/worker' && doEval === 'internal'); - if (!isLoaderHookWorker) { - // If we are in the loader hook worker, delay the module loader initializations until - // initializeAsyncLoaderHooksOnLoaderHookWorker() which needs to run preloads - // after the asynchronous loader hooks are registered. - initializeModuleLoaders({ shouldSpawnLoaderHookWorker: true, shouldPreloadModules: true }); - // Re-mount the inherited --vfs-load source so its reserved path (which a - // worker filename may point into) resolves in this thread too. With - // --import, mounting is deferred to after that loop in run_main, matching - // the main thread; finishVfsMounts() is idempotent so it runs once. - if (getOptionValue('--import').length === 0) { - finishVfsMounts(); - } - // Unlike the --vfs-load source, a SEA archive needs no provider that a - // preload could register, so its mount is never deferred. - if (getOptionValue('[sea_vfs_archive]')) { - require('internal/vfs/sea').initSeaVfsInWorker(); - } + initializeModuleLoaders({ shouldPreloadModules: true }); + // Re-mount the inherited --vfs-load source so its reserved path (which a + // worker filename may point into) resolves in this thread too. With + // --import, mounting is deferred to after that loop in run_main, matching + // the main thread; finishVfsMounts() is idempotent so it runs once. + if (getOptionValue('--import').length === 0) { + finishVfsMounts(); + } + // Unlike the --vfs-load source, a SEA archive needs no provider that a + // preload could register, so its mount is never deferred. + if (getOptionValue('[sea_vfs_archive]')) { + require('internal/vfs/sea').initSeaVfsInWorker(); } if (!hasStdin) @@ -167,14 +159,6 @@ port.on('message', (message) => { `(eval = ${doEval}) at cwd = ${process.cwd()}`); port.postMessage({ type: UP_AND_RUNNING }); switch (doEval) { - case 'internal': { - // Currently the only user of internal eval is the async loader hook thread. - assert(isLoaderHookWorker, `Unexpected internal eval ${filename}`); - const setupModuleWorker = require('internal/modules/esm/worker'); - setupModuleWorker(workerData, publicPort); - break; - } - case 'classic': if (webWorkerData?.source !== undefined) { // The source of a web Worker script loaded from a blob: or data: // URL. diff --git a/lib/internal/modules/customization_hooks.js b/lib/internal/modules/customization_hooks.js index c3d1970243fd..aed35529f8c6 100644 --- a/lib/internal/modules/customization_hooks.js +++ b/lib/internal/modules/customization_hooks.js @@ -282,8 +282,8 @@ function validateLoadSloppy(url, context, result) { function validateSourceStrict(url, context, result) { const { source, format } = result; - // To align with module.register(), the load hooks are still invoked for - // the builtins even though the default load step only provides null as source, + // Load hooks are still invoked for builtins even though the default load + // step only provides null as source, // and any source content for builtins provided by the user hooks are ignored. if (!StringPrototypeStartsWith(url, 'node:') && typeof result.source !== 'string' && @@ -302,8 +302,7 @@ function validateSourceStrict(url, context, result) { function validateSourcePermissive(url, context, result) { const { source, format } = result; if (format === 'commonjs' && source == null) { - // Accommodate the quirk in defaultLoad used by asynchronous loader hooks - // which sets source to null for commonjs. + // A hook may return a nullish source for CommonJS, in which case the CJS loader loads it itself. // See: https://github.com/nodejs/node/issues/57327#issuecomment-2701382020 return; } diff --git a/lib/internal/modules/esm/hooks.js b/lib/internal/modules/esm/hooks.js deleted file mode 100644 index 220dcd33754e..000000000000 --- a/lib/internal/modules/esm/hooks.js +++ /dev/null @@ -1,870 +0,0 @@ -'use strict'; - -const { - ArrayPrototypePush, - ArrayPrototypePushApply, - AtomicsLoad, - AtomicsWait, - AtomicsWaitAsync, - Int32Array, - ObjectAssign, - ObjectDefineProperty, - ObjectSetPrototypeOf, - Promise, - ReflectSet, - SafeSet, - StringPrototypeSlice, - StringPrototypeToUpperCase, -} = primordials; - -const { - ERR_ASYNC_LOADER_REQUEST_NEVER_SETTLED, - ERR_INTERNAL_ASSERTION, - ERR_INVALID_ARG_TYPE, - ERR_INVALID_ARG_VALUE, - ERR_INVALID_RETURN_PROPERTY_VALUE, - ERR_INVALID_RETURN_VALUE, - ERR_LOADER_CHAIN_INCOMPLETE, - ERR_WORKER_UNSERIALIZABLE_ERROR, -} = require('internal/errors').codes; -const { URLParse } = require('internal/url'); -const { canParse: URLCanParse } = internalBinding('url'); -const { receiveMessageOnPort } = require('worker_threads'); -const { - isAnyArrayBuffer, - isArrayBufferView, -} = require('internal/util/types'); -const { - validateObject, - validateString, -} = require('internal/validators'); -const { - constructSharedArrayBuffer, - kEmptyObject, -} = require('internal/util'); - -const { - defaultResolve, - throwIfInvalidParentURL, -} = require('internal/modules/esm/resolve'); -const { - getDefaultConditions, -} = require('internal/modules/esm/utils'); -const { hasWorkerCondition } = require('internal/modules/helpers'); -const { deserializeError } = require('internal/error_serdes'); -const { - SHARED_MEMORY_BYTE_LENGTH, - WORKER_TO_MAIN_THREAD_NOTIFICATION, -} = require('internal/modules/esm/shared_constants'); -let debug = require('internal/util/debuglog').debuglog('async_loader_worker', (fn) => { - debug = fn; -}); - -let importAssertionAlreadyWarned = false; - -function emitImportAssertionWarning() { - if (!importAssertionAlreadyWarned) { - importAssertionAlreadyWarned = true; - process.emitWarning('Use `importAttributes` instead of `importAssertions`', 'ExperimentalWarning'); - } -} - -function defineImportAssertionAlias(context) { - return ObjectDefineProperty(context, 'importAssertions', { - __proto__: null, - configurable: true, - get() { - emitImportAssertionWarning(); - return this.importAttributes; - }, - set(value) { - emitImportAssertionWarning(); - return ReflectSet(this, 'importAttributes', value); - }, - }); -} - -/** - * @typedef {object} ExportedHooks - * @property {Function} resolve Resolve hook. - * @property {Function} load Load hook. - */ - -/** - * @typedef {object} KeyedHook - * @property {Function} fn The hook function. - * @property {URL['href']} url The URL of the module. - * @property {KeyedHook?} next The next hook in the chain. - */ - -// [2] `validate...()`s throw the wrong error - -/** - * @typedef {{ format: ModuleFormat, source: ModuleSource }} LoadResult - */ - -/** - * @typedef {{ format: ModuleFormat, url: string, importAttributes: Record }} ResolveResult - */ - -/** - * Interface for classes that implement asynchronous loader hooks that can be attached to the ModuleLoader - * via `ModuleLoader.#setAsyncLoaderHooks()`. - * @typedef {object} AsyncLoaderHooks - * @property {boolean} isForAsyncLoaderHookWorker Whether the instance is running on the loader hook worker thread. - * @property {(url: string, context: object, defaultLoad: Function) => Promise} load - * Calling the asynchronous `load` hook asynchronously. - * @property {(url: string, context: object, defaultLoad: Function) => LoadResult} [loadSync] - * Calling the asynchronous `load` hook synchronously. - * @property {(originalSpecifier: string, parentURL: string, - * importAttributes: Record) => Promise} resolve - * Calling the asynchronous `resolve` hook asynchronously. - * @property {(originalSpecifier: string, parentURL: string, - * importAttributes: Record) => ResolveResult} [resolveSync] - * Calling the asynchronous `resolve` hook synchronously. - * @property {(specifier: string, parentURL: string) => any} register Register asynchronous loader hooks - * @property {() => void} waitForLoaderHookInitialization Force loading of hooks. - */ - -/** - * @implements {AsyncLoaderHooks} - * Instances of this class run directly on the loader hook worker thread and customize the module - * loading of the hooks worker itself. - */ -class AsyncLoaderHooksOnLoaderHookWorker { - #chains = { - /** - * Phase 1 of 2 in ESM loading. - * The output of the `resolve` chain of hooks is passed into the `load` chain of hooks. - * @private - * @property {KeyedHook[]} resolve Last-in-first-out collection of resolve hooks. - */ - resolve: [ - { - fn: defaultResolve, - url: 'node:internal/modules/esm/resolve', - }, - ], - - /** - * Phase 2 of 2 in ESM loading. - * @private - * @property {KeyedHook[]} load Last-in-first-out collection of loader hooks. - */ - load: [ - { - fn: require('internal/modules/esm/load').defaultLoad, - url: 'node:internal/modules/esm/load', - }, - ], - }; - - // Cache URLs we've already validated to avoid repeated validation - #validatedUrls = new SafeSet(); - - isForAsyncLoaderHookWorker = true; - - /** - * Import and register custom/user-defined module loader hook(s). - * @param {string} urlOrSpecifier - * @param {string} parentURL - * @param {any} [data] Arbitrary data to be passed from the custom - * loader (user-land) to the worker. - */ - async register(urlOrSpecifier, parentURL, data, isInternal) { - const cascadedLoader = require('internal/modules/esm/loader').getOrInitializeCascadedLoader(); - const keyedExports = isInternal ? - require(urlOrSpecifier) : - await cascadedLoader.import( - urlOrSpecifier, - parentURL, - kEmptyObject, - ); - await this.addCustomLoader(urlOrSpecifier, keyedExports, data); - } - - /** - * Collect custom/user-defined module loader hook(s). - * @param {string} url Custom loader specifier - * @param {Record} exports - * @param {any} [data] Arbitrary data to be passed from the custom loader (user-land) - * to the worker. - * @returns {any | Promise} User data, ignored unless it's a promise, in which case it will be awaited. - */ - addCustomLoader(url, exports, data) { - const { - initialize, - resolve, - load, - } = pluckHooks(exports); - - if (resolve) { - const next = this.#chains.resolve[this.#chains.resolve.length - 1]; - ArrayPrototypePush(this.#chains.resolve, { __proto__: null, fn: resolve, url, next }); - } - if (load) { - const next = this.#chains.load[this.#chains.load.length - 1]; - ArrayPrototypePush(this.#chains.load, { __proto__: null, fn: load, url, next }); - } - return initialize?.(data); - } - - /** - * Resolve the location of the module. - * - * Internally, this behaves like a backwards iterator, wherein the stack of - * hooks starts at the top and each call to `nextResolve()` moves down 1 step - * until it reaches the bottom or short-circuits. - * @param {string} originalSpecifier The specified URL path of the module to - * be resolved. - * @param {string} [parentURL] The URL path of the module's parent. - * @param {ImportAttributes} [importAttributes] Attributes from the import - * statement or expression. - * @returns {Promise<{ format: string, url: URL['href'] }>} - */ - async resolve( - originalSpecifier, - parentURL, - importAttributes = { __proto__: null }, - ) { - throwIfInvalidParentURL(parentURL); - - const chain = this.#chains.resolve; - const context = { - conditions: getDefaultConditions(), - importAttributes, - parentURL, - }; - const meta = { - chainFinished: null, - context, - hookErrIdentifier: '', - hookName: 'resolve', - shortCircuited: false, - }; - - const validateArgs = (hookErrIdentifier, suppliedSpecifier, ctx) => { - validateString( - suppliedSpecifier, - `${hookErrIdentifier} specifier`, - ); // non-strings can be coerced to a URL string - - if (ctx) { validateObject(ctx, `${hookErrIdentifier} context`); } - }; - const validateOutput = (hookErrIdentifier, output) => { - if (typeof output !== 'object' || output === null) { // [2] - throw new ERR_INVALID_RETURN_VALUE( - 'an object', - hookErrIdentifier, - output, - ); - } - }; - - const nextResolve = nextHookFactory(chain[chain.length - 1], meta, { validateArgs, validateOutput }); - - const resolution = await nextResolve(originalSpecifier, defineImportAssertionAlias(context)); - const { hookErrIdentifier } = meta; // Retrieve the value after all settled - - validateOutput(hookErrIdentifier, resolution); - - if (resolution?.shortCircuit === true) { meta.shortCircuited = true; } - - if (!meta.chainFinished && !meta.shortCircuited) { - throw new ERR_LOADER_CHAIN_INCOMPLETE(hookErrIdentifier); - } - - let resolvedImportAttributes; - const { - format, - url, - } = resolution; - - if (typeof url !== 'string') { - // non-strings can be coerced to a URL string - // validateString() throws a less-specific error - throw new ERR_INVALID_RETURN_PROPERTY_VALUE( - 'a URL string', - hookErrIdentifier, - 'url', - url, - ); - } - - // Avoid expensive URL instantiation for known-good URLs - if (!this.#validatedUrls.has(url)) { - // No need to convert to string, since the type is already validated - if (!URLCanParse(url)) { - throw new ERR_INVALID_RETURN_PROPERTY_VALUE( - 'a URL string', - hookErrIdentifier, - 'url', - url, - ); - } - - this.#validatedUrls.add(url); - } - - if (!('importAttributes' in resolution) && ('importAssertions' in resolution)) { - emitImportAssertionWarning(); - resolvedImportAttributes = resolution.importAssertions; - } else { - resolvedImportAttributes = resolution.importAttributes; - } - - if ( - resolvedImportAttributes != null && - typeof resolvedImportAttributes !== 'object' - ) { - throw new ERR_INVALID_RETURN_PROPERTY_VALUE( - 'an object', - hookErrIdentifier, - 'importAttributes', - resolvedImportAttributes, - ); - } - - if ( - format != null && - typeof format !== 'string' // [2] - ) { - throw new ERR_INVALID_RETURN_PROPERTY_VALUE( - 'a string', - hookErrIdentifier, - 'format', - format, - ); - } - - return { - __proto__: null, - format, - importAttributes: resolvedImportAttributes, - url, - }; - } - - /** - * Provide source that is understood by one of Node's translators. - * - * Internally, this behaves like a backwards iterator, wherein the stack of - * hooks starts at the top and each call to `nextLoad()` moves down 1 step - * until it reaches the bottom or short-circuits. - * @param {URL['href']} url The URL/path of the module to be loaded - * @param {object} context Metadata about the module - * @returns {Promise<{ format: ModuleFormat, source: ModuleSource }>} - */ - async load(url, context = kEmptyObject) { - const chain = this.#chains.load; - const meta = { - chainFinished: null, - context, - hookErrIdentifier: '', - hookName: 'load', - shortCircuited: false, - }; - - const validateArgs = (hookErrIdentifier, nextUrl, ctx) => { - if (typeof nextUrl !== 'string') { - // Non-strings can be coerced to a URL string - // validateString() throws a less-specific error - throw new ERR_INVALID_ARG_TYPE( - `${hookErrIdentifier} url`, - 'a URL string', - nextUrl, - ); - } - - // Avoid expensive URL instantiation for known-good URLs - if (!this.#validatedUrls.has(nextUrl)) { - // No need to convert to string, since the type is already validated - if (!URLCanParse(nextUrl)) { - throw new ERR_INVALID_ARG_VALUE( - `${hookErrIdentifier} url`, - nextUrl, - 'should be a URL string', - ); - } - - this.#validatedUrls.add(nextUrl); - } - - if (ctx) { validateObject(ctx, `${hookErrIdentifier} context`); } - }; - const validateOutput = (hookErrIdentifier, output) => { - if (typeof output !== 'object' || output === null) { // [2] - throw new ERR_INVALID_RETURN_VALUE( - 'an object', - hookErrIdentifier, - output, - ); - } - }; - - const nextLoad = nextHookFactory(chain[chain.length - 1], meta, { validateArgs, validateOutput }); - - const loaded = await nextLoad(url, defineImportAssertionAlias(context)); - const { hookErrIdentifier } = meta; // Retrieve the value after all settled - - validateOutput(hookErrIdentifier, loaded); - - if (loaded?.shortCircuit === true) { meta.shortCircuited = true; } - - if (!meta.chainFinished && !meta.shortCircuited) { - throw new ERR_LOADER_CHAIN_INCOMPLETE(hookErrIdentifier); - } - - const { - format, - source, - } = loaded; - let responseURL = loaded.responseURL; - - if (responseURL === undefined) { - responseURL = url; - } - - let responseURLObj; - if (typeof responseURL === 'string') { - responseURLObj = URLParse(responseURL); - } - - if (responseURLObj?.href !== responseURL) { - throw new ERR_INVALID_RETURN_PROPERTY_VALUE( - 'undefined or a fully resolved URL string', - hookErrIdentifier, - 'responseURL', - responseURL, - ); - } - - if (format == null) { - require('internal/modules/esm/load').throwUnknownModuleFormat(url, format); - } - - if (typeof format !== 'string') { // [2] - throw new ERR_INVALID_RETURN_PROPERTY_VALUE( - 'a string', - hookErrIdentifier, - 'format', - format, - ); - } - - if ( - source != null && - typeof source !== 'string' && - !isAnyArrayBuffer(source) && - !isArrayBufferView(source) - ) { - throw new ERR_INVALID_RETURN_PROPERTY_VALUE( - 'a string, an ArrayBuffer, or a TypedArray', - hookErrIdentifier, - 'source', - source, - ); - } - - return { - __proto__: null, - format, - responseURL, - source, - }; - } - - waitForLoaderHookInitialization() { - // No-op - } -} -ObjectSetPrototypeOf(AsyncLoaderHooksOnLoaderHookWorker.prototype, null); - -/** - * There is only one loader hook thread for each non-loader-hook worker thread - * (i.e. the non-loader-hook thread and any worker threads that are not loader hook workers themselves), - * so there is only 1 MessageChannel. - */ -let MessageChannel; - -/** - * Abstraction over a worker thread that runs the asynchronous module loader hooks. - * Instances of this class run on the non-loader-hook thread and communicate with the loader hooks worker thread. - */ -class AsyncLoaderHookWorker { - /** - * Shared memory. Always use Atomics method to read or write to it. - * @type {Int32Array} - */ - #lock; - /** - * The InternalWorker instance, which lets us communicate with the loader thread. - */ - #worker; - - /** - * The last notification ID received from the worker. This is used to detect - * if the worker has already sent a notification before putting the main - * thread to sleep, to avoid a race condition. - * @type {number} - */ - #workerNotificationLastId = 0; - - /** - * Track how many async responses the main thread should expect. - * @type {number} - */ - #numberOfPendingAsyncResponses = 0; - - #isReady = false; - - constructor() { - const { InternalWorker } = require('internal/worker'); - MessageChannel ??= require('internal/worker/io').MessageChannel; - - const lock = constructSharedArrayBuffer(SHARED_MEMORY_BYTE_LENGTH); - this.#lock = new Int32Array(lock); - - this.#worker = new InternalWorker('internal/modules/esm/worker', { - stderr: false, - stdin: false, - stdout: false, - trackUnmanagedFds: false, - workerData: { - lock, - workerCondition: hasWorkerCondition(), - }, - }); - this.#worker.unref(); // ! Allows the process to eventually exit. - this.#worker.on('exit', process.exit); - } - - waitForWorker() { - if (!this.#isReady) { - const { kIsOnline } = require('internal/worker'); - if (!this.#worker[kIsOnline]) { - debug('wait for signal from worker'); - AtomicsWait(this.#lock, WORKER_TO_MAIN_THREAD_NOTIFICATION, 0); - const response = this.#worker.receiveMessageSync(); - if (response == null) { return; } - if (response.message.status === 'exit') { - process.exit(response.message.body); - } - - // ! This line catches initialization errors in the worker thread. - this.#unwrapMessage(response); - } - - this.#isReady = true; - } - } - - /** - * Invoke a remote method asynchronously. - * @param {string} method Method to invoke - * @param {any[]} [transferList] Objects in `args` to be transferred - * @param {any[]} args Arguments to pass to `method` - * @returns {Promise} - */ - async makeAsyncRequest(method, transferList, ...args) { - this.waitForWorker(); - - MessageChannel ??= require('internal/worker/io').MessageChannel; - const asyncCommChannel = new MessageChannel(); - - // Pass work to the worker. - debug('post async message to worker', { method, args, transferList }); - const finalTransferList = [asyncCommChannel.port2]; - if (transferList) { - ArrayPrototypePushApply(finalTransferList, transferList); - } - this.#worker.postMessage({ - __proto__: null, - method, args, - port: asyncCommChannel.port2, - }, finalTransferList); - - if (this.#numberOfPendingAsyncResponses++ === 0) { - // On the next lines, the main thread will await a response from the worker thread that might - // come AFTER the last task in the event loop has run its course and there would be nothing - // left keeping the thread alive (and once the main thread dies, the whole process stops). - // However we want to keep the process alive until the worker thread responds (or until the - // event loop of the worker thread is also empty), so we ref the worker until we get all the - // responses back. - this.#worker.ref(); - } - - let response; - do { - debug('wait for async response from worker', { method, args }); - await AtomicsWaitAsync(this.#lock, WORKER_TO_MAIN_THREAD_NOTIFICATION, this.#workerNotificationLastId).value; - this.#workerNotificationLastId = AtomicsLoad(this.#lock, WORKER_TO_MAIN_THREAD_NOTIFICATION); - - response = receiveMessageOnPort(asyncCommChannel.port1); - } while (response == null); - debug('got async response from worker', { method, args }, this.#lock); - - if (--this.#numberOfPendingAsyncResponses === 0) { - // We got all the responses from the worker, its job is done (until next time). - this.#worker.unref(); - } - - const body = this.#unwrapMessage(response); - asyncCommChannel.port1.close(); - return body; - } - - /** - * Invoke a remote method synchronously. - * @param {string} method Method to invoke - * @param {any[]} [transferList] Objects in `args` to be transferred - * @param {any[]} args Arguments to pass to `method` - * @returns {any} - */ - makeSyncRequest(method, transferList, ...args) { - this.waitForWorker(); - - // Pass work to the worker. - debug('post sync message to worker', { method, args, transferList }); - this.#worker.postMessage({ __proto__: null, method, args }, transferList); - - let response; - do { - debug('wait for sync response from worker', { method, args }); - // Sleep until worker responds. - AtomicsWait(this.#lock, WORKER_TO_MAIN_THREAD_NOTIFICATION, this.#workerNotificationLastId); - this.#workerNotificationLastId = AtomicsLoad(this.#lock, WORKER_TO_MAIN_THREAD_NOTIFICATION); - - response = this.#worker.receiveMessageSync(); - debug('got sync message from worker', { method, args, response }); - } while (response == null); - - if (response.message.status === 'never-settle') { - const error = new ERR_ASYNC_LOADER_REQUEST_NEVER_SETTLED(); - error.details = { method, args }; - throw error; - } else if (response.message.status === 'exit') { - process.exit(response.message.body); - } - return this.#unwrapMessage(response); - } - - #unwrapMessage(response) { - if (response.message.status === 'never-settle') { - return new Promise(() => {}); - } - const { status, body } = response.message; - if (status === 'error') { - if (body == null || typeof body !== 'object') { throw body; } - if (body.serializationFailed || body.serialized == null) { - throw new ERR_WORKER_UNSERIALIZABLE_ERROR(); - } - - // eslint-disable-next-line no-restricted-syntax - throw deserializeError(body.serialized); - } else { - return body; - } - } -} -ObjectSetPrototypeOf(AsyncLoaderHookWorker.prototype, null); - -// TODO(JakobJingleheimer): Remove this when loaders go "stable". -let globalPreloadWarningWasEmitted = false; - -/** - * A utility function to pluck the hooks from a user-defined loader. - * @param {import('./loader.js').ModuleExports} exports - * @returns {ExportedHooks} - */ -function pluckHooks({ - globalPreload, - initialize, - resolve, - load, -}) { - const acceptedHooks = { __proto__: null }; - - if (resolve) { - acceptedHooks.resolve = resolve; - } - if (load) { - acceptedHooks.load = load; - } - - if (initialize) { - acceptedHooks.initialize = initialize; - } else if (globalPreload && !globalPreloadWarningWasEmitted) { - process.emitWarning( - '`globalPreload` has been removed; use `initialize` instead.', - 'UnsupportedWarning', - ); - globalPreloadWarningWasEmitted = true; - } - - return acceptedHooks; -} - - -/** - * A utility function to iterate through a hook chain, track advancement in the - * chain, and generate and supply the `next` argument to the custom - * hook. - * @param {KeyedHook} current The (currently) first hook in the chain (this shifts - * on every call). - * @param {object} meta Properties that change as the current hook advances - * along the chain. - * @param {boolean} meta.chainFinished Whether the end of the chain has been - * reached AND invoked. - * @param {string} meta.hookErrIdentifier A user-facing identifier to help - * pinpoint where an error occurred. Ex "file:///foo.mjs 'resolve'". - * @param {string} meta.hookName The kind of hook the chain is (ex 'resolve') - * @param {boolean} meta.shortCircuited Whether a hook signaled a short-circuit. - * @param {function(string, unknown): void} validate A wrapper function - * containing all validation of a custom loader hook's intermediary output. Any - * validation within MUST throw. - * @returns {Function} The next hook in the chain. - */ -function nextHookFactory(current, meta, { validateArgs, validateOutput }) { - // First, prepare the current - const { hookName } = meta; - const { - fn: hook, - url: hookFilePath, - next, - } = current; - - // ex 'nextResolve' - const nextHookName = `next${ - StringPrototypeToUpperCase(hookName[0]) + - StringPrototypeSlice(hookName, 1) - }`; - - let nextNextHook; - if (next) { - nextNextHook = nextHookFactory(next, meta, { validateArgs, validateOutput }); - } else { - // eslint-disable-next-line node-core/func-name-matching - nextNextHook = function chainAdvancedTooFar() { - throw new ERR_INTERNAL_ASSERTION( - `ESM custom loader '${hookName}' advanced beyond the end of the chain.`, - ); - }; - } - - return ObjectDefineProperty( - async (arg0 = undefined, context) => { - // Update only when hook is invoked to avoid fingering the wrong filePath - meta.hookErrIdentifier = `${hookFilePath} '${hookName}'`; - - validateArgs(`${meta.hookErrIdentifier} hook's ${nextHookName}()`, arg0, context); - - const outputErrIdentifier = `${hookFilePath} '${hookName}' hook's ${nextHookName}()`; - - // Set when next is actually called, not just generated. - if (!next) { meta.chainFinished = true; } - - if (context) { // `context` has already been validated, so no fancy check needed. - ObjectAssign(meta.context, context); - } - - const output = await hook(arg0, meta.context, nextNextHook); - validateOutput(outputErrIdentifier, output); - - if (output?.shortCircuit === true) { meta.shortCircuited = true; } - - return output; - }, - 'name', - { __proto__: null, value: nextHookName }, - ); -} - -/** - * @type {AsyncLoaderHookWorker} - * Worker instance used to run async loader hooks in a separate thread. This is a singleton for each - * non-loader-hook worker thread (i.e. the main thread and any worker threads that are not - * loader hook workers themselves). - */ -let asyncLoaderHookWorker; -/** - * Get the AsyncLoaderHookWorker instance. If it is not defined, then create a new one. - * @returns {AsyncLoaderHookWorker} - */ -function getAsyncLoaderHookWorker() { - asyncLoaderHookWorker ??= new AsyncLoaderHookWorker(); - return asyncLoaderHookWorker; -} - -/** - * @implements {AsyncLoaderHooks} - * Instances of this class are created in the non-loader-hook thread and communicate with the worker thread - * spawned to run the async loader hooks. - */ -class AsyncLoaderHooksProxiedToLoaderHookWorker { - - isForAsyncLoaderHookWorker = false; - - /** - * Instantiate a module loader that uses user-provided custom loader hooks. - */ - constructor() { - getAsyncLoaderHookWorker(); - } - - /** - * Register some loader specifier. - * @param {string} originalSpecifier The specified URL path of the loader to - * be registered. - * @param {string} parentURL The parent URL from where the loader will be - * registered if using it package name as specifier - * @param {any} [data] Arbitrary data to be passed from the custom loader - * (user-land) to the worker. - * @param {any[]} [transferList] Objects in `data` that are changing ownership - * @param {boolean} [isInternal] For internal loaders that should not be publicly exposed. - * @returns {{ format: string, url: URL['href'] }} - */ - register(originalSpecifier, parentURL, data, transferList, isInternal) { - return asyncLoaderHookWorker.makeSyncRequest('register', transferList, originalSpecifier, parentURL, - data, isInternal); - } - - /** - * Resolve the location of the module. - * @param {string} originalSpecifier The specified URL path of the module to - * be resolved. - * @param {string} [parentURL] The URL path of the module's parent. - * @param {ImportAttributes} importAttributes Attributes from the import - * statement or expression. - * @returns {{ format: string, url: URL['href'] }} - */ - resolve(originalSpecifier, parentURL, importAttributes) { - return asyncLoaderHookWorker.makeAsyncRequest('resolve', undefined, originalSpecifier, parentURL, importAttributes); - } - - resolveSync(originalSpecifier, parentURL, importAttributes) { - // This happens only as a result of `import.meta.resolve` calls, which must be sync per spec. - return asyncLoaderHookWorker.makeSyncRequest('resolve', undefined, originalSpecifier, parentURL, importAttributes); - } - - /** - * Provide source that is understood by one of Node's translators. - * @param {URL['href']} url The URL/path of the module to be loaded - * @param {object} [context] Metadata about the module - * @returns {Promise<{ format: ModuleFormat, source: ModuleSource }>} - */ - load(url, context) { - return asyncLoaderHookWorker.makeAsyncRequest('load', undefined, url, context); - } - loadSync(url, context) { - return asyncLoaderHookWorker.makeSyncRequest('load', undefined, url, context); - } - - waitForLoaderHookInitialization() { - asyncLoaderHookWorker.waitForWorker(); - } -} - -exports.AsyncLoaderHooksProxiedToLoaderHookWorker = AsyncLoaderHooksProxiedToLoaderHookWorker; -exports.AsyncLoaderHooksOnLoaderHookWorker = AsyncLoaderHooksOnLoaderHookWorker; -exports.AsyncLoaderHookWorker = AsyncLoaderHookWorker; diff --git a/lib/internal/modules/esm/load.js b/lib/internal/modules/esm/load.js index 06f60f9f6725..66e7eb6d5ecf 100644 --- a/lib/internal/modules/esm/load.js +++ b/lib/internal/modules/esm/load.js @@ -8,7 +8,7 @@ const { } = require('internal/util'); const { defaultGetFormat } = require('internal/modules/esm/get_format'); -const { validateAttributes, emitImportAssertionWarning } = require('internal/modules/esm/assert'); +const { validateAttributes } = require('internal/modules/esm/assert'); const { loaderMethods } = require('internal/modules/helpers'); const { Buffer: { from: BufferFrom } } = require('buffer'); @@ -51,67 +51,6 @@ function getSourceSync(url, context) { } -/** - * Node.js default load hook. - * @param {string} url - * @param {LoadContext} context - * @returns {LoadReturn} - */ -function defaultLoad(url, context = kEmptyObject) { - let responseURL = url; - let { - importAttributes, - format, - source, - } = context; - - if (importAttributes == null && !('importAttributes' in context) && 'importAssertions' in context) { - emitImportAssertionWarning(); - importAttributes = context.importAssertions; - // Alias `importAssertions` to `importAttributes` - context = { - ...context, - importAttributes, - }; - } - - const urlInstance = new URL(url); - - throwIfUnsupportedURLScheme(urlInstance); - - if (urlInstance.protocol === 'node:') { - source = null; - format ??= 'builtin'; - } else if (format === 'addon') { - // Skip loading addon file content. It must be loaded with dlopen from file system. - source = null; - } else if (format !== 'commonjs') { - if (source == null) { - ({ responseURL, source } = getSourceSync(urlInstance, context)); - context = { __proto__: context, source }; - } - - if (format == null) { - // Now that we have the source for the module, run `defaultGetFormat` to detect its format. - format = defaultGetFormat(urlInstance, context); - - if (format === 'commonjs') { - // For backward compatibility reasons, we need to discard the source in - // order for the CJS loader to re-fetch it. - source = null; - } - } - } - - validateAttributes(url, format, importAttributes); - - return { - __proto__: null, - format, - responseURL, - source, - }; -} /** * @typedef LoadContext * @property {string} [format] A hint (possibly returned from `resolve`) @@ -212,7 +151,6 @@ function throwUnknownModuleFormat(url, format) { module.exports = { - defaultLoad, defaultLoadSync, getSourceSync, throwUnknownModuleFormat, diff --git a/lib/internal/modules/esm/loader.js b/lib/internal/modules/esm/loader.js index 2e0b0fef8e9d..f8e68e7996cb 100644 --- a/lib/internal/modules/esm/loader.js +++ b/lib/internal/modules/esm/loader.js @@ -1,18 +1,9 @@ 'use strict'; const { - ArrayPrototypeJoin, - ArrayPrototypeMap, - ArrayPrototypeReduce, FunctionPrototypeCall, - JSONStringify, ObjectSetPrototypeOf, - Promise, - PromisePrototypeThen, - RegExpPrototypeSymbolReplace, - encodeURIComponent, globalThis, - hardenRegExp, } = primordials; const { LoadCache, ResolveCache } = require('internal/modules/esm/module_map'); @@ -37,17 +28,15 @@ const { ERR_UNKNOWN_MODULE_FORMAT, } = require('internal/errors').codes; const { getOptionValue } = require('internal/options'); -const { isURL, pathToFileURL } = require('internal/url'); +const { pathToFileURL } = require('internal/url'); const { - getDeprecationWarningEmitter, kEmptyObject, } = require('internal/util'); const { compileSourceTextModule, SourceTextModuleTypes: { kUser }, getDefaultConditions, - shouldSpawnLoaderHookWorker, - requestTypes: { kImportInRequiredESM, kRequireInImportedCJS, kImportInImportedESM }, + requestTypes: { kImportInRequiredESM, kImportInImportedESM }, } = require('internal/modules/esm/utils'); /** * @typedef {import('./utils.js').ModuleRequestType} ModuleRequestType @@ -62,7 +51,6 @@ const { kUninstantiated, kErrored, kSourcePhase, - throwIfPromiseRejected, setImportMetaResolveInitializer, } = internalBinding('module_wrap'); const { @@ -83,10 +71,7 @@ let debug = require('internal/util/debuglog').debuglog('esm', (fn) => { debug = fn; }); -const { isPromise } = require('internal/util/types'); - /** - * @typedef {import('./hooks.js').AsyncLoaderHookWorker} AsyncLoaderHookWorker * @typedef {import('./module_job.js').ModuleJobBase} ModuleJobBase * @typedef {import('url').URL} URL */ @@ -123,22 +108,12 @@ const { defaultLoadSync, throwUnknownModuleFormat } = require('internal/modules/ * responseURL?: string, * translatorKey: string, * isResolvedBySyncHooks: boolean, - * isSourceLoadedSynchronously: boolean, * }} TranslateContext */ /** - * @typedef {import('./hooks.js').AsyncLoaderHooks} AsyncLoaderHooks - * @typedef {import('./hooks.js').AsyncLoaderHooksOnLoaderHookWorker} AsyncLoaderHooksOnLoaderHookWorker - * @typedef {import('./hooks.js').AsyncLoaderHooksProxiedToLoaderHookWorker} AsyncLoaderHooksProxiedToLoaderHookWorker - */ - -/** - * This class covers the base machinery of module loading. There are two types of loader hooks: - * 1. Asynchronous loader hooks, which are run in a separate loader hook worker thread. - * This is configured in #asyncLoaderHooks. - * 2. Synchronous loader hooks, which are run in-thread. This is shared with the CJS loader and is - * stored in the cross-module syncResolveHooks and syncLoadHooks arrays. + * This class covers the base machinery of module loading. The `module.registerHooks()` hooks are + * shared with the CJS loader and stored in the cross-module syncResolveHooks and syncLoadHooks arrays. */ class ModuleLoader { /** @@ -156,57 +131,6 @@ class ModuleLoader { */ loadCache = new LoadCache(); - /** - * @see {AsyncLoaderHooks.isForAsyncLoaderHookWorker} - * Shortcut to this.#asyncLoaderHooks.isForAsyncLoaderHookWorker. - */ - isForAsyncLoaderHookWorker = false; - - /** - * Asynchronous loader hooks to pass requests to. - * - * Note that this value _MUST_ be set with `#setAsyncLoaderHooks` - * because it needs to copy `#asyncLoaderHooks.isForAsyncLoaderHookWorker` - * to this property. - * TODO(joyeecheung): this was a legacy of the previous setup of import.meta.resolve - * configuration; put this information in the environment directly instead. - * - * When the ModuleLoader is created on a loader hook thread, this is - * {@link AsyncLoaderHooksOnLoaderHookWorker}, and its methods directly call out - * to loader methods. Otherwise, this is {@link AsyncLoaderHooksProxiedToLoaderHookWorker}, - * and its methods post messages to the loader thread and possibly block on it. - * @see {ModuleLoader.#setAsyncLoaderHooks} - * @type {AsyncLoaderHooks} - */ - #asyncLoaderHooks; - - constructor(asyncLoaderHooks) { - this.#setAsyncLoaderHooks(asyncLoaderHooks); - } - - /** - * Change the currently activate async loader hooks for this module - * loader to be the provided `AsyncLoaderHooks`. - * - * If present, this class customizes its core functionality to the - * `AsyncLoaderHooks` object, including registration, loading, and resolving. - * There are some responsibilities that this class _always_ takes - * care of, like validating outputs, so that the AsyncLoaderHooks object - * does not have to do so. - * - * Calling this function alters how modules are loaded and should be - * invoked with care. - * @param {AsyncLoaderHooks} asyncLoaderHooks - */ - #setAsyncLoaderHooks(asyncLoaderHooks) { - this.#asyncLoaderHooks = asyncLoaderHooks; - if (asyncLoaderHooks) { - this.isForAsyncLoaderHookWorker = asyncLoaderHooks.isForAsyncLoaderHookWorker; - } else { - this.isForAsyncLoaderHookWorker = false; - } - } - /** * * @param {string} source Source code of the module. @@ -290,10 +214,6 @@ class ModuleLoader { if (job !== undefined) { mod[kRequiredModuleSymbol] = job.module; const parentFilename = urlToFilename(parent?.filename); - // This race should only be possible on the loader hook thread. See https://github.com/nodejs/node/issues/59666 - if (!job.module) { - throw new ERR_REQUIRE_ESM_RACE_CONDITION(filename, parentFilename, this.isForAsyncLoaderHookWorker); - } const status = job.module.getStatus(); debug('Module status', job, status); // hasAsyncGraph is available after module been instantiated. @@ -303,13 +223,10 @@ class ModuleLoader { if (status === kEvaluated) { return { wrap: job.module, namespace: job.module.getNamespace() }; } else if (status === kInstantiated) { - // When it's an async job cached by another import request, - // which has finished linking but has not started its - // evaluation because the async run() task would be later - // in line. Then start the evaluation now with runSync(), which - // is guaranteed to finish by the time the other run() get to it, - // and the other task would just get the cached evaluation results, - // similar to what would happen when both are async. + // The job was created by an earlier import() that has finished linking but has + // not started its evaluation, which is deferred to a later microtask. Evaluate + // it now with runSync(); the pending run() will then just find it already + // evaluated, similar to what would happen when both are async. mod[kRequiredModuleSymbol] = job.module; const { namespace } = job.runSync(parent); return { wrap: job.module, namespace: namespace || job.module.getNamespace() }; @@ -317,21 +234,14 @@ class ModuleLoader { // If the module was previously imported and errored, throw the error. throw job.module.getError(); } - // When the cached async job have already encountered a linking - // error that gets wrapped into a rejection, but is still later - // in line to throw on it, just unwrap and throw the linking error - // from require(). - if (job.instantiated) { - throwIfPromiseRejected(job.instantiated); - } if (status !== kEvaluating) { assert(status === kUninstantiated, `Unexpected module status ${status}`); - // If we get here, either there's a race where the job is still being instantiated - // by an in-flight import(), or the cached module previously encountered an + // If we get here, either the job was created by an in-flight import() that has not + // instantiated it yet, or the cached module previously encountered an // instantiation error during a prior load (e.g. due to a mismatched import). // TODO(joyeecheung): the current check is too broad. We should attempt to // get the potential instantiation error and throw it. - throw new ERR_REQUIRE_ESM_RACE_CONDITION(filename, parentFilename, false); + throw new ERR_REQUIRE_ESM_RACE_CONDITION(filename, parentFilename); } let message = `Cannot require() ES Module ${filename} in a cycle.`; if (parentFilename) { @@ -397,10 +307,6 @@ class ModuleLoader { * @param {ModuleJobBase} job The cached module job. */ #checkCachedJobForRequireESM(specifier, url, parentURL, job) { - // This race should only be possible on the loader hook thread. See https://github.com/nodejs/node/issues/59666 - if (!job.module) { - throw new ERR_REQUIRE_ESM_RACE_CONDITION(url, parentURL, this.isForAsyncLoaderHookWorker); - } // This module is being evaluated, which means it's imported in a previous link // in a cycle. if (job.module.getStatus() === kEvaluating) { @@ -422,7 +328,7 @@ class ModuleLoader { * Load a module and translate it into a ModuleWrap for require(esm). * This is run synchronously, and the translator always return a ModuleWrap synchronously. * @param {ResolveResult} resolveResult Result from the resolve step. - * @param {object} loadContext See {@link load} + * @param {{ format: string, importAttributes: object }} loadContext Context for the load step. * @param {string|undefined} parentURL URL of the parent module. Undefined if it's the entry point. * @param {ModuleRequest} request Module request. * @returns {ModuleWrap} @@ -460,8 +366,7 @@ class ModuleLoader { } /** - * Translate a loaded module source into a ModuleWrap. This is run synchronously, - * but the translator may return the ModuleWrap in a Promise. + * Translate a loaded module source into a ModuleWrap synchronously. * @param {string} url URL of the module to be translated. * @param {TranslateContext} translateContext Context for the translator * @param {string|undefined} parentURL URL of the module initiating the module loading for the first time. @@ -479,98 +384,47 @@ class ModuleLoader { const result = FunctionPrototypeCall(translator, this, url, translateContext, parentURL); assert(result instanceof ModuleWrap, `The ${format} module returned is not a ModuleWrap`); - if (format === 'commonjs' || format === 'commonjs-sync' || format === 'require-commonjs') { + if (format === 'commonjs' || format === 'commonjs-sync') { result.isCommonJS = true; } return result; } /** - * Load a module and translate it into a ModuleWrap for require() in imported CJS. - * This is run synchronously, and the translator always return a ModuleWrap synchronously. + * Load a module and translate it into a ModuleWrap for ordinary imported ESM. + * Loading and translation are synchronous; evaluation may be asynchronous. * @param {ResolveResult} resolveResult Result from the resolve step. - * @param {object} loadContext See {@link load} + * @param {{ format: string, importAttributes: object }} loadContext Context for the load step. * @param {string|undefined} parentURL URL of the parent module. Undefined if it's the entry point. * @returns {ModuleWrap} */ - loadAndTranslateForRequireInImportedCJS(resolveResult, loadContext, parentURL) { + loadAndTranslate(resolveResult, loadContext, parentURL) { const { url } = resolveResult; const loadResult = this.#loadSync(url, loadContext); - const formatFromLoad = loadResult.format; - - if (formatFromLoad === 'wasm') { // require(wasm) is not supported. - throw new ERR_UNKNOWN_MODULE_FORMAT(formatFromLoad, url); - } - - if (formatFromLoad === 'module' || formatFromLoad === 'module-typescript') { - if (!getOptionValue('--require-module')) { - throw new ERR_REQUIRE_ESM(url, true); - } - } - - let translatorKey = formatFromLoad; - if (formatFromLoad === 'commonjs') { - translatorKey = 'require-commonjs'; - } - if (formatFromLoad === 'commonjs-typescript') { - translatorKey = 'require-commonjs-typescript'; - } - const translateContext = { ...resolveResult, ...loadResult, - translatorKey, + translatorKey: loadResult.format, __proto__: null, }; - const wrap = this.#translate(url, translateContext, parentURL); - assert(wrap instanceof ModuleWrap, `Translator used for require(${url}) should not be async`); - return wrap; - } - - /** - * Load a module and translate it into a ModuleWrap for ordinary imported ESM. - * This may be run asynchronously if there are asynchronous module loader hooks registered. - * @param {ResolveResult} resolveResult Result from the resolve step. - * @param {object} loadContext See {@link load} - * @param {string|undefined} parentURL URL of the parent module. Undefined if it's the entry point. - * @returns {Promise|ModuleWrap} - */ - loadAndTranslate(resolveResult, loadContext, parentURL) { - const { url } = resolveResult; - const maybePromise = this.load(url, loadContext); - const afterLoad = (loadResult) => { - const translateContext = { - ...resolveResult, - ...loadResult, - translatorKey: loadResult.format, - __proto__: null, - }; - return this.#translate(url, translateContext, parentURL); - }; - if (isPromise(maybePromise)) { - return PromisePrototypeThen(maybePromise, afterLoad); - } - return afterLoad(maybePromise); + return this.#translate(url, translateContext, parentURL); } /** * Given a resolved module request, obtain a ModuleJobBase from it - if it's already cached, * return the cached ModuleJobBase. Otherwise, load its source and translate it into a ModuleWrap first. - * This runs synchronously. On any thread that is not an async loader hook worker thread, - * the module should be linked by the time this returns. Otherwise it may still have - * pending module requests. + * This runs synchronously, so the module should be linked by the time this returns. * @param {string} parentURL See {@link getOrCreateModuleJob} * @param {ResolveResult} resolveResult * @param {ModuleRequest} request Module request. * @param {ModuleRequestType} requestType Type of the module request. - * @returns {ModuleJobBase} The (possibly pending) module job + * @returns {ModuleJobBase} The module job */ #getOrCreateModuleJobAfterResolve(parentURL, resolveResult, request, requestType) { const { url, format } = resolveResult; if (process.env.WATCH_REPORT_DEPENDENCIES && process.send) { - const type = requestType === kRequireInImportedCJS ? 'require' : 'import'; - process.send({ [`watch:${type}`]: [url] }); + process.send({ 'watch:import': [url] }); } // Relay Events from worker to main thread if (process.env.WATCH_REPORT_DEPENDENCIES && !process.send) { @@ -597,34 +451,19 @@ class ModuleLoader { const context = { format, importAttributes, __proto__: null }; - let moduleOrModulePromise; - if (requestType === kRequireInImportedCJS) { - moduleOrModulePromise = this.loadAndTranslateForRequireInImportedCJS(resolveResult, context, parentURL); - } else if (requestType === kImportInRequiredESM) { - moduleOrModulePromise = this.loadAndTranslateForImportInRequiredESM(resolveResult, context, parentURL, request); - } else { - moduleOrModulePromise = this.loadAndTranslate(resolveResult, context, parentURL); - } + const module = requestType === kImportInRequiredESM ? + this.loadAndTranslateForImportInRequiredESM(resolveResult, context, parentURL, request) : + this.loadAndTranslate(resolveResult, context, parentURL); - if (requestType === kImportInRequiredESM || requestType === kRequireInImportedCJS || - !this.isForAsyncLoaderHookWorker) { - assert(moduleOrModulePromise instanceof ModuleWrap, `Expected ModuleWrap for loading ${url}`); - } + assert(module instanceof ModuleWrap, `Expected ModuleWrap for loading ${url}`); const isMain = (parentURL === undefined); const inspectBrk = (isMain && getOptionValue('--inspect-brk')); - // Use ModuleJobSync for kImportInRequiredESM, which must always be linked synchronously, - // and whenever we're on the main thread (not the async loader hook worker), - // except for kRequireInImportedCJS (TODO: consolidate that case too). - // TODO(joyeecheung): use ModuleJobSync for kRequireInImportedCJS too. - const ModuleJobCtor = (requestType === kImportInRequiredESM || - (!this.isForAsyncLoaderHookWorker && requestType !== kRequireInImportedCJS)) ? - ModuleJobSync : ModuleJob; - job = new ModuleJobCtor( + job = new ModuleJobSync( this, url, importAttributes, - moduleOrModulePromise, + module, request.phase, isMain, inspectBrk, @@ -637,35 +476,16 @@ class ModuleLoader { } /** - * Get a (possibly not yet fully linked) module job from the cache, or create one and return its Promise. + * Get a module job from the cache, or create one. Loading and linking are synchronous. * @param {string} [parentURL] The URL of the module where the module request is initiated. * It's undefined if it's from the root module. * @param {ModuleRequest} request Module request. * @param {ModuleRequestType} requestType Type of the module request. - * @returns {Promise|ModuleJobBase} + * @returns {ModuleJobBase} */ getOrCreateModuleJob(parentURL, request, requestType) { - let maybePromise; - if (!this.isForAsyncLoaderHookWorker || - requestType === kRequireInImportedCJS || requestType === kImportInRequiredESM) { - // On the main thread, always resolve synchronously; - // `resolveSync` coordinates with the async loader hook worker if needed. - // On the async loader hook worker, require(esm) and require() in imported CJS - // must still be resolved synchronously. - maybePromise = this.resolveSync(parentURL, request); - assert(!isPromise(maybePromise)); - } else { - maybePromise = this.#resolve(parentURL, request); - } - - const afterResolve = (resolveResult) => { - return this.#getOrCreateModuleJobAfterResolve(parentURL, resolveResult, request, requestType); - }; - - if (isPromise(maybePromise)) { - return PromisePrototypeThen(maybePromise, afterResolve); - } - return afterResolve(maybePromise); + const resolveResult = this.resolveSync(parentURL, request); + return this.#getOrCreateModuleJobAfterResolve(parentURL, resolveResult, request, requestType); } /** @@ -682,15 +502,10 @@ class ModuleLoader { async import(specifier, parentURL, importAttributes, phase = kEvaluationPhase, isEntryPoint = false) { return onImport.tracePromise(async () => { const request = { specifier, phase, attributes: importAttributes, __proto__: null }; - let moduleJob; - try { - moduleJob = await this.getOrCreateModuleJob(parentURL, request); - } catch (e) { - if (e?.code === 'ERR_ASYNC_LOADER_REQUEST_NEVER_SETTLED') { - return new Promise(() => {}); - } - throw e; - } + const moduleJob = this.getOrCreateModuleJob(parentURL, request); + // Loading and linking are synchronous, but import() must still never evaluate the + // module before returning to its caller, so yield once before running the job. + await null; if (phase === kSourcePhase) { const module = await moduleJob.modulePromise; return module.getModuleSourceObject(); @@ -704,44 +519,10 @@ class ModuleLoader { }); } - /** - * @see {@link AsyncLoaderHooks.register} - * @returns {any} - */ - register(specifier, parentURL, data, transferList, isInternal) { - if (!this.#asyncLoaderHooks) { - // On the loader hook worker thread, the #asyncLoaderHooks must already have been initialized - // to be an instance of AsyncLoaderHooksOnLoaderHookWorker, so this branch can only ever - // be hit on a non-loader-hook thread that will talk to the loader hook worker thread. - const { AsyncLoaderHooksProxiedToLoaderHookWorker } = require('internal/modules/esm/hooks'); - this.#setAsyncLoaderHooks(new AsyncLoaderHooksProxiedToLoaderHookWorker()); - } - return this.#asyncLoaderHooks.register(`${specifier}`, `${parentURL}`, data, transferList, isInternal); - } - - /** - * Resolve a module request to a URL identifying the location of the module. Handles customization hooks, - * if any. - * @param {string} [parentURL] The URL of the module where the module request is initiated. - * It's undefined if it's from the root module. - * @param {ModuleRequest} request Module request. - * @returns {Promise|ResolveResult} - */ - #resolve(parentURL, request) { - if (this.isForAsyncLoaderHookWorker) { - const specifier = `${request.specifier}`; - const importAttributes = request.attributes ?? kEmptyObject; - // TODO(joyeecheung): invoke the synchronous hooks in the default step on loader thread. - return this.#asyncLoaderHooks.resolve(specifier, parentURL, importAttributes); - } - - return this.resolveSync(parentURL, request); - } - /** * Either return a cached resolution, or perform the default resolution which is synchronous, and * cache the result. - * @param {string} specifier See {@link resolve}. + * @param {string} specifier The module specifier. * @param {{ parentURL?: string, importAttributes: ImportAttributes, conditions?: string[]}} context * @returns {{ format: string, url: string }} */ @@ -759,37 +540,28 @@ class ModuleLoader { } /** - * This is the default resolve step for module.registerHooks(), which incorporates asynchronous hooks - * from module.register() which are run in a blocking fashion for it to be synchronous. + * This is the default resolve step for module.registerHooks(). * @param {{isResolvedByDefaultResolve: boolean}} out Output object to track whether the default resolve was used * without polluting the user-visible resolve result. - * @param {string|URL} specifier See {@link resolveSync}. + * @param {string|URL} specifier The module specifier. * @param {{ parentURL?: string, importAttributes: ImportAttributes, conditions?: string[]}} context - * See {@link resolveSync}. + * Resolution context. * @returns {{ format: string, url: string }} */ - #resolveAndMaybeBlockOnLoaderThread(out, specifier, context) { - if (this.#asyncLoaderHooks?.resolveSync) { - return this.#asyncLoaderHooks.resolveSync(specifier, context.parentURL, context.importAttributes); - } + #defaultResolve(out, specifier, context) { out.isResolvedByDefaultResolve = true; return this.#cachedDefaultResolve(specifier, context); } /** - * Similar to {@link resolve}, but the results are always synchronously returned. If there are any - * asynchronous resolve hooks from module.register(), it will block until the results are returned - * from the loader thread for this to be synchronous. - * This is here to support `import.meta.resolve()`, `require()` in imported CJS, and - * `module.registerHooks()` hooks. - * @param {string} [parentURL] See {@link resolve}. - * @param {ModuleRequest} request See {@link resolve}. - * @param {boolean} [shouldSkipSyncHooks] Whether to skip the synchronous hooks registered by module.registerHooks(). - * This is used to maintain compatibility for the re-invented require.resolve (in imported CJS customized - * by module.register()`) which invokes the CJS resolution separately from the hook chain. + * Resolve a module request to a URL identifying the location of the module. Handles + * `module.registerHooks()` hooks, if any. + * @param {string} [parentURL] The URL of the module where the module request is initiated. + * It's undefined if it's from the root module. + * @param {ModuleRequest} request Module request. * @returns {ResolveResult} */ - resolveSync(parentURL, request, shouldSkipSyncHooks = false) { + resolveSync(parentURL, request) { const specifier = `${request.specifier}`; const importAttributes = request.attributes ?? kEmptyObject; // Use an output parameter to track the state and avoid polluting the user-visible resolve results. @@ -797,10 +569,10 @@ class ModuleLoader { let result; let isResolvedBySyncHooks = false; - if (!shouldSkipSyncHooks && syncResolveHooks.length) { - // Has module.registerHooks() hooks, chain the asynchronous hooks in the default step. + if (syncResolveHooks.length) { + // Has module.registerHooks() hooks, chain the default resolver in the default step. result = resolveWithSyncHooks(specifier, parentURL, importAttributes, this.#defaultConditions, - this.#resolveAndMaybeBlockOnLoaderThread.bind(this, out)); + this.#defaultResolve.bind(this, out)); // If the default step ran, sync hooks did not short-circuit the resolution. isResolvedBySyncHooks = !out.isResolvedByDefaultResolve; } else { @@ -811,85 +583,34 @@ class ModuleLoader { importAttributes, __proto__: null, }; - result = this.#resolveAndMaybeBlockOnLoaderThread(out, specifier, context); + result = this.#defaultResolve(out, specifier, context); } result.isResolvedBySyncHooks = isResolvedBySyncHooks; return result; } /** - * Provide source that is understood by one of Node's translators. Handles customization hooks, - * if any. - * @typedef {{ - * format: ModuleFormat, - * source: ModuleSource, - * responseURL?: string, - * isSourceLoadedSynchronously: boolean, - * }} LoadResult + * Provide source that is understood by one of Node's translators. Handles + * `module.registerHooks()` hooks, if any. * @param {string} url The URL of the module to be loaded. - * @param {object} context Metadata about the module - * @returns {Promise | LoadResult}} - */ - load(url, context) { - if (this.isForAsyncLoaderHookWorker) { - // TODO(joyeecheung): invoke the synchronous hooks in the default step on loader thread. - return this.#asyncLoaderHooks.load(url, context); - } - return this.#loadSync(url, context); - } - - /** - * This is the default load step for module.registerHooks(), which incorporates asynchronous hooks - * from module.register() which are run in a blocking fashion for it to be synchronous. - * @param {{isSourceLoadedSynchronously: boolean}} out - * Output object to track whether the source was loaded synchronously without polluting - * the user-visible load result. - * @param {string} url See {@link load} - * @param {object} context See {@link load} - * @returns {{ format: ModuleFormat, source: ModuleSource }} - */ - #loadAndMaybeBlockOnLoaderThread(out, url, context) { - if (this.#asyncLoaderHooks?.loadSync) { - out.isSourceLoadedSynchronously = false; - return this.#asyncLoaderHooks.loadSync(url, context); - } - out.isSourceLoadedSynchronously = true; - return defaultLoadSync(url, context); - } - - /** - * Similar to {@link load} but this is always run synchronously. If there are asynchronous hooks - * from module.register(), this blocks on the loader thread for it to return synchronously. - * - * This is here to support `require()` in imported CJS and `module.registerHooks()` hooks. - * @param {string} url See {@link load} - * @param {object} [context] See {@link load} - * @returns {LoadResult} + * @param {{ format: string, importAttributes: object }} context Metadata about the module + * @returns {{ format: ModuleFormat, source: ModuleSource, responseURL?: string }} */ #loadSync(url, context) { - // Use an output parameter to track the state and avoid polluting the user-visible resolve results. - const out = { - isSourceLoadedSynchronously: true, - __proto__: null, - }; - let result; if (syncLoadHooks.length) { - // Has module.registerHooks() hooks, chain the asynchronous hooks in the default step. + // Has module.registerHooks() hooks, chain the default loader in the default step. // TODO(joyeecheung): construct the ModuleLoadContext in the loaders directly instead // of converting them from plain objects in the hooks. - result = loadWithSyncHooks( + return loadWithSyncHooks( url, context.format, context.importAttributes, this.#defaultConditions, - this.#loadAndMaybeBlockOnLoaderThread.bind(this, out), + defaultLoadSync, validateLoadSloppy, ); - } else { - result = this.#loadAndMaybeBlockOnLoaderThread(out, url, context); } - result.isSourceLoadedSynchronously = out.isSourceLoadedSynchronously; - return result; + return defaultLoadSync(url, context); } validateLoadResult(url, format) { @@ -897,60 +618,9 @@ class ModuleLoader { throwUnknownModuleFormat(url, format); } } - - /** - * Block until the async loader hooks have been initialized. - * - * No-op when no hooks have been supplied. - */ - waitForAsyncLoaderHookInitialization() { - this.#asyncLoaderHooks?.waitForLoaderHookInitialization(); - } } ObjectSetPrototypeOf(ModuleLoader.prototype, null); -let emittedLoaderFlagWarning = false; -/** - * A loader instance is used as the main entry point for loading ES modules. Currently, this is a singleton; there is - * only one used for loading the main module and everything in its dependency graph, though separate instances of this - * class might be instantiated as part of bootstrap for other purposes. - * @param {AsyncLoaderHooksOnLoaderHookWorker|undefined} [asyncLoaderHooks] - * Only provided when run on the loader hook thread. - * @returns {ModuleLoader} - */ -function createModuleLoader(asyncLoaderHooks) { - // Don't spawn a new loader hook worker if we are already in a loader hook worker to avoid infinite recursion. - if (shouldSpawnLoaderHookWorker()) { - assert(asyncLoaderHooks === undefined, 'asyncLoaderHooks should only be provided on the loader hook thread itself'); - const userLoaderPaths = getOptionValue('--experimental-loader'); - if (userLoaderPaths.length > 0) { - if (!emittedLoaderFlagWarning) { - const readableURIEncode = (string) => ArrayPrototypeReduce( - [ - [/'/g, '%27'], // We need to URL-encode the single quote as it's the delimiter for the --import flag. - [/%22/g, '"'], // We can decode the double quotes to improve readability. - [/%2F/ig, '/'], // We can decode the slashes to improve readability. - ], - (str, { 0: regex, 1: replacement }) => RegExpPrototypeSymbolReplace(hardenRegExp(regex), str, replacement), - encodeURIComponent(string)); - process.emitWarning( - '`--experimental-loader` may be removed in the future; instead use `register()`:\n' + - `--import 'data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; ${ArrayPrototypeJoin( - ArrayPrototypeMap(userLoaderPaths, (loader) => `register(${readableURIEncode(JSONStringify(loader))}, pathToFileURL("./"))`), - '; ', - )};'`, - 'ExperimentalWarning', - ); - emittedLoaderFlagWarning = true; - } - const { AsyncLoaderHooksProxiedToLoaderHookWorker } = require('internal/modules/esm/hooks'); - asyncLoaderHooks = new AsyncLoaderHooksProxiedToLoaderHookWorker(); - } - } - - return new ModuleLoader(asyncLoaderHooks); -} - let allowImportMetaResolveParentURL; /** * This is only called from the native ImportMetaObjectInitialize function to set up import.meta.resolve @@ -1000,20 +670,12 @@ let cascadedLoader; * require('internal/module/esm/loader').getOrInitializeCascadedLoader() * lazily only right before the loader is actually needed, and don't do it * in the top-level, to avoid circular dependencies. - * @param {AsyncLoaderHooksOnLoaderHookWorker|undefined} [asyncLoaderHooks] - * Only provided when run on the loader hook thread. * @returns {ModuleLoader} */ -function getOrInitializeCascadedLoader(asyncLoaderHooks) { +function getOrInitializeCascadedLoader() { if (!cascadedLoader) { - cascadedLoader = createModuleLoader(asyncLoaderHooks); - // import.meta.resolve is not allowed in the async loader hook worker thread. - // So only set up the import.meta.resolve initializer when we are initializing - // the non-loader-hook-thread cascaded loader. When the native land doesn't see it, - // it knows the loader is running on the loader hook thread. - if (!(asyncLoaderHooks?.isForAsyncLoaderHookWorker)) { - setImportMetaResolveInitializer(createImportMetaResolve.bind(null, cascadedLoader)); - } + cascadedLoader = new ModuleLoader(); + setImportMetaResolveInitializer(createImportMetaResolve.bind(null, cascadedLoader)); } return cascadedLoader; } @@ -1022,60 +684,8 @@ function isCascadedLoaderInitialized() { return cascadedLoader !== undefined; } -/** - * Register a single loader programmatically. - * @param {string|URL} specifier - * @param {string|URL} [parentURL] Base to use when resolving `specifier`; optional if - * `specifier` is absolute. Same as `options.parentUrl`, just inline - * @param {object} [options] Additional options to apply, described below. - * @param {string|URL} [options.parentURL] Base to use when resolving `specifier` - * @param {any} [options.data] Arbitrary data passed to the loader's `initialize` hook - * @param {any[]} [options.transferList] Objects in `data` that are changing ownership - * @returns {void} We want to reserve the return value for potential future extension of the API. - * @example - * ```js - * register('./myLoader.js'); - * register('ts-node/esm', { parentURL: import.meta.url }); - * register('./myLoader.js', { parentURL: import.meta.url }); - * register('ts-node/esm', import.meta.url); - * register('./myLoader.js', import.meta.url); - * register(new URL('./myLoader.js', import.meta.url)); - * register('./myLoader.js', { - * parentURL: import.meta.url, - * data: { banana: 'tasty' }, - * }); - * register('./myLoader.js', { - * parentURL: import.meta.url, - * data: someArrayBuffer, - * transferList: [someArrayBuffer], - * }); - * ``` - */ -const emitRegisterDeprecation = getDeprecationWarningEmitter( - 'DEP0205', - '`module.register()` is deprecated. Use `module.registerHooks()` instead.', - undefined, - false, -); - -function register(specifier, parentURL = undefined, options) { - emitRegisterDeprecation(); - - if (parentURL != null && typeof parentURL === 'object' && !isURL(parentURL)) { - options = parentURL; - parentURL = options.parentURL; - } - getOrInitializeCascadedLoader().register( - specifier, - parentURL ?? 'data:', - options?.data, - options?.transferList, - ); -} - module.exports = { - createModuleLoader, + ModuleLoader, getOrInitializeCascadedLoader, isCascadedLoaderInitialized, - register, }; diff --git a/lib/internal/modules/esm/module_job.js b/lib/internal/modules/esm/module_job.js index c8506b42ff3f..ba300785c9eb 100644 --- a/lib/internal/modules/esm/module_job.js +++ b/lib/internal/modules/esm/module_job.js @@ -11,12 +11,10 @@ const { FunctionPrototype, ObjectAssign, ObjectSetPrototypeOf, - PromisePrototypeThen, PromiseResolve, RegExpPrototypeExec, RegExpPrototypeSymbolReplace, RegExpPrototypeSymbolSplit, - SafePromiseAllReturnArrayLike, SafePromiseAllReturnVoid, SafeSet, StringPrototypeIncludes, @@ -31,15 +29,11 @@ let debug = require('internal/util/debuglog').debuglog('esm', (fn) => { const { ModuleWrap, kErrored, - kEvaluated, - kEvaluating, kDeferPhase, kEvaluationPhase, kInstantiated, - kUninstantiated, } = internalBinding('module_wrap'); const { - getPromiseDetails, privateSymbols: { entry_point_module_private_symbol, module_source_private_symbol: kModuleSource, @@ -49,7 +43,6 @@ const { * @typedef {import('./utils.js').ModuleRequestType} ModuleRequestType */ const { decorateErrorStack } = require('internal/util'); -const { isPromise } = require('internal/util/types'); const { getSourceMapsSupport, } = require('internal/source_map/source_map_cache'); @@ -63,7 +56,6 @@ const { getOptionValue } = require('internal/options'); const noop = FunctionPrototype; const { ERR_REQUIRE_ASYNC_MODULE, - ERR_REQUIRE_ESM_RACE_CONDITION, } = require('internal/errors').codes; const CJSGlobalLike = [ @@ -193,14 +185,7 @@ function findModulesWithTopLevelAwait(root) { if (job.module?.hasTopLevelAwait) { ArrayPrototypePush(found, job.module); } - let linked = job.linked; - if (isPromise(linked)) { - linked = getPromiseDetails(linked)?.[1]; - } - // If `require(esm)` comes from the deprecated async loader hook worker thread, - // linked may be pending at this point. In that case, this branch would be skipped - - // we just allow lossy reporting of TLA locations in an edge case when a deprecated - // feature is used in combination with another experimental flag. + const { linked } = job; if (ArrayIsArray(linked)) { for (let i = 0; i < linked.length; i++) { ArrayPrototypePush(stack, linked[i]); @@ -309,7 +294,7 @@ class ModuleJobBase { * @param {ModuleRequestType} requestType Type of the module request. * @returns {ModuleJobBase[]} */ - syncLink(requestType) { + link(requestType) { // Store itself into the cache first before linking in case there are circular // references in the linking. Track whether we're overwriting an existing entry // so we know whether to remove the temporary entry in the finally block. @@ -323,11 +308,8 @@ class ModuleJobBase { try { for (let idx = 0; idx < moduleRequests.length; idx++) { const request = moduleRequests[idx]; - // TODO(joyeecheung): split this into two iterators, one for resolving and one for loading so - // that hooks can pre-fetch sources off-thread. const job = this.loader.getOrCreateModuleJob(this.url, request, requestType); - debug(`ModuleJobBase.syncLink() ${this.url} -> ${request.specifier}`, job); - assert(!isPromise(job)); + debug(`ModuleJobBase.link() ${this.url} -> ${request.specifier}`, job); assert(job.module instanceof ModuleWrap); if (this.shouldRunModule(request.phase)) { ArrayPrototypePush(evaluationDepJobs, job); @@ -374,9 +356,6 @@ class ModuleJobBase { if (this.phase < phase) { this.phase = phase; this.linked = this.link(requestType); - if (isPromise(this.linked)) { - PromisePrototypeThen(this.linked, undefined, noop); - } } } @@ -395,33 +374,23 @@ class ModuleJob extends ModuleJobBase { * @param {ModuleLoader} loader The ESM loader. * @param {string} url URL of the module to be wrapped in ModuleJob. * @param {ImportAttributes} importAttributes Import attributes from the import statement. - * @param {ModuleWrap|Promise} moduleOrModulePromise Translated ModuleWrap for the module. + * @param {ModuleWrap} module Translated ModuleWrap for the module. * @param {number} phase The phase to load the module to. * @param {boolean} isMain Whether the module is the entry point. * @param {boolean} inspectBrk Whether this module should be evaluated with the * first line paused in the debugger (because --inspect-brk is passed). * @param {ModuleRequestType} requestType Type of the module request. */ - constructor(loader, url, importAttributes = { __proto__: null }, moduleOrModulePromise, + constructor(loader, url, importAttributes = { __proto__: null }, module, phase = kEvaluationPhase, isMain, inspectBrk, requestType) { super(loader, url, importAttributes, phase, isMain, inspectBrk); - // Expose the promise to the ModuleWrap directly for linking below. - if (isPromise(moduleOrModulePromise)) { - this.modulePromise = moduleOrModulePromise; - } else { - this.module = moduleOrModulePromise; - this.modulePromise = PromiseResolve(moduleOrModulePromise); - } + this.module = module; + this.modulePromise = PromiseResolve(module); if (this.shouldLinkModule(this.phase)) { - // Promise for the list of all dependencyJobs. + // The list of all dependencyJobs. this.linked = this.link(requestType); - // This promise is awaited later anyway, so silence - // 'unhandled rejection' warnings. - if (isPromise(this.linked)) { - PromisePrototypeThen(this.linked, undefined, noop); - } } // instantiated == deep dependency jobs wrappers are instantiated, @@ -429,59 +398,6 @@ class ModuleJob extends ModuleJobBase { this.instantiated = undefined; } - /** - * @param {ModuleRequestType} requestType Type of the module request. - * @returns {ModuleJobBase[]|Promise} - */ - link(requestType) { - if (this.loader.isForAsyncLoaderHookWorker) { - return this.#asyncLink(requestType); - } - return this.syncLink(requestType); - } - - /** - * @param {ModuleRequestType} requestType Type of the module request. - * @returns {Promise} - */ - async #asyncLink(requestType) { - assert(this.loader.isForAsyncLoaderHookWorker); - this.module = await this.modulePromise; - assert(this.module instanceof ModuleWrap); - const moduleRequests = this.module.getModuleRequests(); - // Create an ArrayLike to avoid calling into userspace with `.then` - // when returned from the async function. - // Modules should be aligned with the moduleRequests array in order. - const modulePromises = Array(moduleRequests.length); - const evaluationDepJobs = []; - this.commonJsDeps = Array(moduleRequests.length); - for (let idx = 0; idx < moduleRequests.length; idx++) { - const request = moduleRequests[idx]; - // Explicitly keeping track of dependency jobs is needed in order - // to flatten out the dependency graph below in `asyncInstantiate()`, - // so that circular dependencies can't cause a deadlock by two of - // these `link` callbacks depending on each other. - // TODO(joyeecheung): split this into two iterators, one for resolving and one for loading so - // that hooks can pre-fetch sources off-thread. - const dependencyJobPromise = this.loader.getOrCreateModuleJob(this.url, request, requestType); - const modulePromise = PromisePrototypeThen(dependencyJobPromise, (job) => { - debug(`ModuleJob.asyncLink() ${this.url} -> ${request.specifier}`, job); - if (this.shouldRunModule(request.phase)) { - ArrayPrototypePush(evaluationDepJobs, job); - } - return job.modulePromise; - }); - modulePromises[idx] = modulePromise; - } - const modules = await SafePromiseAllReturnArrayLike(modulePromises); - for (let idx = 0; idx < moduleRequests.length; idx++) { - this.commonJsDeps[idx] = modules[idx].isCommonJS; - } - - this.module.link(modules); - return evaluationDepJobs; - } - #instantiate() { if (this.instantiated === undefined) { this.instantiated = this.#asyncInstantiate(); @@ -491,8 +407,6 @@ class ModuleJob extends ModuleJobBase { async #asyncInstantiate() { const jobsInGraph = new SafeSet(); - // TODO(joyeecheung): if it's not on the async loader thread, consider this already - // linked. const addJobsToDependencyGraph = async (moduleJob) => { debug(`async addJobsToDependencyGraph() ${this.url}`, moduleJob); @@ -500,8 +414,7 @@ class ModuleJob extends ModuleJobBase { return; } jobsInGraph.add(moduleJob); - const dependencyJobs = isPromise(moduleJob.linked) ? await moduleJob.linked : moduleJob.linked; - return SafePromiseAllReturnVoid(dependencyJobs, addJobsToDependencyGraph); + return SafePromiseAllReturnVoid(moduleJob.linked, addJobsToDependencyGraph); }; await addJobsToDependencyGraph(this); @@ -520,46 +433,6 @@ class ModuleJob extends ModuleJobBase { } } - runSync(parent) { - assert(this.shouldRunModule(this.phase)); - assert(this.module instanceof ModuleWrap); - let status = this.module.getStatus(); - - debug('ModuleJob.runSync()', status, this.module); - if (status === kUninstantiated) { - // TODO(joyeecheung): Reject graphs with top-level await _before_ instantiation, so that - // the async graph error supersedes instantiation (mismatch export) errors in the graph. - // FIXME(joyeecheung): this cannot fully handle < kInstantiated. Make the linking - // fully synchronous instead. - if (this.module.getModuleRequests().length === 0) { - this.module.link([]); - } - this.module.instantiate(); - status = this.module.getStatus(); - } - if (status === kInstantiated || status === kErrored) { - this.throwIfAsyncGraph(parent); - if (status === kInstantiated) { - setHasStartedUserESMExecution(); - const namespace = this.module.evaluateSync(); - return { __proto__: null, module: this.module, namespace }; - } - throw this.module.getError(); - } else if (status === kEvaluating || status === kEvaluated) { - this.throwIfAsyncGraph(parent); - // kEvaluating can show up when this is being used to deal with CJS <-> CJS cycles. - // Allow it for now, since we only need to ban ESM <-> CJS cycles which would be - // detected earlier during the linking phase, though the CJS handling in the ESM - // loader won't be able to emit warnings on pending circular exports like what - // the CJS loader does. - // TODO(joyeecheung): remove the re-invented require() in the ESM loader and - // always handle CJS using the CJS loader to eliminate the quirks. - return { __proto__: null, module: this.module, namespace: this.module.getNamespace() }; - } - assert(status === kUninstantiated, `Unexpected module status ${status}.`); - throw new ERR_REQUIRE_ESM_RACE_CONDITION(); - } - async run(isEntryPoint = false) { debug('ModuleJob.run()', this.module); assert(this.shouldRunModule(this.phase)); @@ -581,16 +454,12 @@ class ModuleJob extends ModuleJobBase { } /** - * This is a fully synchronous job and does not spawn additional threads in any way. * Loading and linking are always synchronous. Evaluation via runSync() throws on an * asynchronous graph; evaluation via run() falls back to async for top-level await. * It also disallows CJS <-> ESM cycles. * - * Used for all ES module imports on the main thread, regardless of how the import was - * triggered (entry point, import(), require(esm), --import, etc.). - * Modules loaded by require() in imported CJS are handled by ModuleJob with the - * isForRequireInImportedCJS set to true instead. The two currently have different caching behaviors. - * TODO(joyeecheung): consolidate this with the isForRequireInImportedCJS variant of ModuleJob. + * Used for all ES module imports regardless of how the import was triggered + * (entry point, import(), require(esm), --import, etc.). */ class ModuleJobSync extends ModuleJobBase { /** @@ -617,15 +486,6 @@ class ModuleJobSync extends ModuleJobBase { } } - /** - * @param {ModuleRequestType} requestType Type of the module request. - * @returns {ModuleJobBase[]} - */ - link(requestType) { - // Synchronous linking is always used for ModuleJobSync. - return this.syncLink(requestType); - } - get modulePromise() { return PromiseResolve(this.module); } @@ -708,9 +568,6 @@ class ModuleJobSync extends ModuleJobBase { // TODO(joyeecheung): Reject graphs with top-level await _before_ instantiation, so that the // async graph error supersedes instantiation (mismatch export) errors in the graph. this.instantiateSync(); - // On the deprecated async loader hook worker thread, dependencies linked by an - // earlier import may not be walkable synchronously, so double-check with - // V8 now that the graph is instantiated. this.throwIfAsyncGraph(parent); setHasStartedUserESMExecution(); try { diff --git a/lib/internal/modules/esm/module_map.js b/lib/internal/modules/esm/module_map.js index 207515573c79..05a6c3082d28 100644 --- a/lib/internal/modules/esm/module_map.js +++ b/lib/internal/modules/esm/module_map.js @@ -20,7 +20,8 @@ const { validateString } = require('internal/validators'); * Future resolutions of the same input (specifier, parent URL and import attributes) * must return the same result if the first attempt was successful, per * https://tc39.es/ecma262/#sec-HostLoadImportedModule. - * This cache is *not* used when custom loaders are registered. + * Only results of the default resolver are cached; results returned by + * `module.registerHooks()` resolve hooks are not. */ class ResolveCache extends SafeMap { /** diff --git a/lib/internal/modules/esm/shared_constants.js b/lib/internal/modules/esm/shared_constants.js deleted file mode 100644 index 4200bc87367d..000000000000 --- a/lib/internal/modules/esm/shared_constants.js +++ /dev/null @@ -1,25 +0,0 @@ -// This file contains the definition for the constant values that must be -// available to both the main thread and the loader thread. - -'use strict'; - -/* -The shared memory area is divided in 1 32-bit long section. It has to be 32-bit long as -`Atomics.notify` only works with `Int32Array` objects. - ---32-bits-- - ^ - | - | -WORKER_TO_MAIN_THREAD_NOTIFICATION - -WORKER_TO_MAIN_THREAD_NOTIFICATION is only used to send notifications, its value is going to -increase every time the worker sends a notification to the main thread. - -*/ - -module.exports = { - WORKER_TO_MAIN_THREAD_NOTIFICATION: 0, - - SHARED_MEMORY_BYTE_LENGTH: 1 * 4, -}; diff --git a/lib/internal/modules/esm/translators.js b/lib/internal/modules/esm/translators.js index 583b6d1fe91d..100f791b98ee 100644 --- a/lib/internal/modules/esm/translators.js +++ b/lib/internal/modules/esm/translators.js @@ -17,13 +17,9 @@ const { globalThis, } = primordials; -const { - compileFunctionForCJSLoader, -} = internalBinding('contextify'); - const { BuiltinModule } = require('internal/bootstrap/realm'); const assert = require('internal/assert'); -const { dirname, extname } = require('path'); +const { dirname } = require('path'); const { assertBufferSource, loadBuiltinModule, @@ -45,22 +41,20 @@ const { findLongestRegisteredExtension, resolveForCJSWithHooks, loadSourceForCJSWithHooks, - populateCJSExportsFromESM, } = require('internal/modules/cjs/loader'); -const { fileURLToPath, pathToFileURL, URL } = require('internal/url'); +const { fileURLToPath, URL } = require('internal/url'); const { isAnyArrayBuffer } = require('internal/util/types'); let debug = require('internal/util/debuglog').debuglog('esm', (fn) => { debug = fn; }); -const { emitExperimentalWarning, kEmptyObject, setOwnProperty, isWindows } = require('internal/util'); +const { emitExperimentalWarning, setOwnProperty, isWindows } = require('internal/util'); const { ERR_INVALID_RETURN_PROPERTY_VALUE, ERR_UNKNOWN_BUILTIN_MODULE, ERR_WEBASSEMBLY_NOT_SUPPORTED, } = require('internal/errors').codes; -const { maybeCacheSourceMap } = require('internal/source_map/source_map_cache'); const moduleWrap = internalBinding('module_wrap'); -const { ModuleWrap, kEvaluationPhase } = moduleWrap; +const { ModuleWrap } = moduleWrap; const { getSourceSync } = require('internal/modules/esm/load'); @@ -101,108 +95,9 @@ translators.set('module', function moduleStrategy(url, translateContext, parentU return module; }); -const { requestTypes: { kRequireInImportedCJS } } = require('internal/modules/esm/utils'); const kShouldSkipModuleHooks = { __proto__: null, shouldSkipModuleHooks: true }; const kShouldNotSkipModuleHooks = { __proto__: null, shouldSkipModuleHooks: false }; -/** - * This may be eventually removed when module.register() reaches end-of-life. - * - * Loads a CommonJS module via the ESM Loader sync CommonJS translator. - * This translator creates its own version of the `require` function passed into CommonJS modules. - * Any monkey patches applied to the CommonJS Loader will not affect this module. - * Any `require` calls in this module will load all children in the same way. - * @param {import('internal/modules/cjs/loader').Module} module - The module to load. - * @param {string} source - The source code of the module. - * @param {string} url - The URL of the module. - * @param {string} filename - The filename of the module. - * @param {boolean} isMain - Whether the module is the entrypoint - * @param {TranslateContext} translateContext Context for the translator - */ -function loadCJSModuleWithSpecialRequire(module, source, url, filename, isMain, translateContext) { - // Use the full URL as the V8 resource name so that any search params - // (e.g. ?node-test-mock) are preserved in coverage reports. - const compileResult = compileFunctionForCJSLoader(source, url, false /* is_sea_main */, false); - - const { function: compiledWrapper, sourceMapURL, sourceURL } = compileResult; - // Cache the source map for the cjs module if present. - if (sourceMapURL) { - maybeCacheSourceMap(url, source, module, false, sourceURL, sourceMapURL); - } - const cascadedLoader = require('internal/modules/esm/loader').getOrInitializeCascadedLoader(); - const __dirname = dirname(filename); - // eslint-disable-next-line node-core/func-name-matching,func-style - const requireFn = function require(specifier) { - let importAttributes = kEmptyObject; - if (!StringPrototypeStartsWith(specifier, 'node:') && !BuiltinModule.normalizeRequirableId(specifier)) { - // TODO: do not depend on the monkey-patchable CJS loader here. - const path = CJSModule._resolveFilename(specifier, module); - switch (extname(path)) { - case '.json': - importAttributes = { __proto__: null, type: 'json' }; - break; - case '.node': - // If it gets here in the translators, the hooks must have already been invoked - // in the loader. Skip them in the synthetic module evaluation step. - return wrapModuleLoad(specifier, module, false, kShouldSkipModuleHooks); - default: - // fall through - } - specifier = `${pathToFileURL(path)}`; - } - - // NOTE: This re-invented require() is only used on the loader-hook worker thread. - // On the main thread, the authentic require() is used instead (fixed by #60380). - const request = { specifier, attributes: importAttributes, phase: kEvaluationPhase, __proto__: null }; - const job = cascadedLoader.getOrCreateModuleJob(url, request, kRequireInImportedCJS); - job.runSync(module); - let mod = cjsCache.get(job.url); - assert(job.module, `Imported CJS module ${url} failed to load module ${job.url} using require() due to race condition`); - - if (job.module.synthetic) { - assert(mod, `Imported CJS module ${url} failed to load module ${job.url} using require() due to missed cache`); - return mod.exports; - } - - // The module being required is a source text module. - if (!mod) { - mod = cjsEmplaceModuleCacheEntry(job.url); - cjsCache.set(job.url, mod); - } - // The module has been cached by the re-invented require. Update the exports object - // from the namespace object and return the evaluated exports. - if (!mod.loaded) { - debug('populateCJSExportsFromESM from require(esm) in imported CJS', url, mod, job.module); - populateCJSExportsFromESM(mod, job.module, job.module.getNamespace()); - mod.loaded = true; - } - return mod.exports; - }; - setOwnProperty(requireFn, 'resolve', function resolve(specifier) { - if (!StringPrototypeStartsWith(specifier, 'node:')) { - const { - filename, url: resolvedURL, - } = resolveForCJSWithHooks(specifier, module, false, kShouldNotSkipModuleHooks); - if (specifier !== filename) { - specifier = resolvedURL ?? `${pathToFileURL(filename)}`; - } - } - - const request = { specifier, __proto__: null, attributes: kEmptyObject }; - // Skip sync hooks in resolveSync since resolveForCJSWithHooks already ran them above. - const { url: resolvedURL } = cascadedLoader.resolveSync(url, request, /* shouldSkipSyncHooks */ true); - return urlToFilename(resolvedURL); - }); - setOwnProperty(requireFn, 'main', process.mainModule); - - FunctionPrototypeCall(compiledWrapper, module.exports, - module.exports, requireFn, module, filename, __dirname); - setOwnProperty(module, 'loaded', true); -} - -// TODO: can we use a weak map instead? -const cjsCache = new SafeMap(); - /** * Creates a ModuleWrap object for a CommonJS module. * @param {string} url - The URL of the module. @@ -226,7 +121,6 @@ function createCJSModuleWrap(url, translateContext, parentURL) { } const { exportNames, module } = cjsPreparseModuleExports(filename, source, sourceFormat); - cjsCache.set(url, module); const wrapperNames = [...exportNames]; if (!exportNames.has('default')) { @@ -244,19 +138,7 @@ function createCJSModuleWrap(url, translateContext, parentURL) { debug(`Loading CJSModule ${url}`); if (!module.loaded) { - // For backward-compatibility, it's possible for async hooks to return a nullish value for - // CJS source associated with a `file:` URL - that usually means the source is not - // customized (is loaded by default load) or the hook author wants it to be reloaded - // through CJS routine. In this case, the source is obtained by calling the - // Module._load(). - if (translateContext.translatorKey === 'commonjs-sync' || - translateContext.isSourceLoadedSynchronously || - translateContext.source == null) { - loadCJSModuleWithModuleLoad(module, source, url, filename, !!isMain, translateContext); - } else { // CommonJS with source customized by async hooks - // This may be eventually removed when module.register() reaches end-of-life. - loadCJSModuleWithSpecialRequire(module, source, url, filename, !!isMain, translateContext); - } + loadCJSModuleWithModuleLoad(module, source, url, filename, !!isMain, translateContext); } let exports; @@ -298,7 +180,6 @@ function createCJSNoSourceModuleWrap(url, parentURL) { const filename = urlToFilename(url); const module = cjsEmplaceModuleCacheEntry(filename); - cjsCache.set(url, module); if (isMain) { setOwnProperty(process, 'mainModule', module); @@ -333,25 +214,10 @@ translators.set('commonjs-sync', function requireCommonJS(url, translateContext, return createCJSModuleWrap(url, translateContext, parentURL); }); -// Handle CommonJS modules referenced by `require` calls using re-invented require. -// This path is only used by require() from imported CJS customized by the *async* -// loader hooks. -translators.set('require-commonjs', (url, translateContext, parentURL) => { - return createCJSModuleWrap(url, translateContext, parentURL); -}); - -// Handle TypeScript CommonJS modules referenced by `require` calls using re-invented require. -// This path is only used by require() from imported CJS customized by the *async* -// loader hooks. -translators.set('require-commonjs-typescript', (url, translateContext, parentURL) => { - translateContext.source = stripTypeScriptModuleTypes(stringify(translateContext.source), url); - return createCJSModuleWrap(url, translateContext, parentURL); -}); - // This goes through Module._load to accommodate monkey-patchers. /** * Loads a CommonJS module through Module._load to accommodate monkey-patchers. - * If the module was resolved by synchronous hooks (i.e. not by the default resolver), + * If the module was resolved by hooks (i.e. not by the default resolver), * passes the pre-resolved information and source to Module._load to avoid * re-resolving and re-loading. * @param {import('internal/modules/cjs/loader').Module} module - The module to load. @@ -483,7 +349,6 @@ translators.set('builtin', function builtinStrategy(url, translateContext) { // Slice 'node:' scheme const id = StringPrototypeSlice(url, 5); const module = loadBuiltinModule(id, url); - cjsCache.set(url, module); if (!StringPrototypeStartsWith(url, 'node:') || !module) { throw new ERR_UNKNOWN_BUILTIN_MODULE(url); } @@ -545,7 +410,6 @@ translators.set('json', function jsonStrategy(url, translateContext) { if (shouldCheckAndPopulateCJSModuleCache) { CJSModule._cache[modulePath] = module; } - cjsCache.set(url, module); return new ModuleWrap(url, undefined, ['default'], function() { debug(`Parsing JSONModule ${url}`); this.setExport('default', module.exports); diff --git a/lib/internal/modules/esm/utils.js b/lib/internal/modules/esm/utils.js index 8937b8a61298..b8c8b10d3867 100644 --- a/lib/internal/modules/esm/utils.js +++ b/lib/internal/modules/esm/utils.js @@ -296,14 +296,10 @@ async function importModuleDynamicallyCallback(referrerSymbol, specifier, phase, throw new ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING(); } -let _shouldSpawnLoaderHookWorker = true; /** * Initializes handling of ES modules. - * @param {boolean} [shouldSpawnLoaderHookWorker] Whether the custom loader worker - * should be spawned later. */ -function initializeESM(shouldSpawnLoaderHookWorker = true) { - _shouldSpawnLoaderHookWorker = shouldSpawnLoaderHookWorker; +function initializeESM() { initializeDefaultConditions(); // Setup per-realm callbacks that locate data or callbacks that we keep // track of for different ESM modules. @@ -311,15 +307,6 @@ function initializeESM(shouldSpawnLoaderHookWorker = true) { setImportModuleDynamicallyCallback(importModuleDynamicallyCallback); } -/** - * Determine whether the custom loader worker should be spawned when initializing - * the singleton ESM loader. - * @returns {boolean} - */ -function shouldSpawnLoaderHookWorker() { - return _shouldSpawnLoaderHookWorker; -} - const SourceTextModuleTypes = { kInternal: 'internal', // TODO(joyeecheung): support internal ESM. kEmbedder: 'embedder', // Embedder ESM, also used by SEA @@ -391,12 +378,11 @@ function compileSourceTextModule(url, source, type, context = kEmptyObject) { const kImportInImportedESM = Symbol('kImportInImportedESM'); const kImportInRequiredESM = Symbol('kImportInRequiredESM'); -const kRequireInImportedCJS = Symbol('kRequireInImportedCJS'); /** - * @typedef {kImportInImportedESM | kImportInRequiredESM | kRequireInImportedCJS} ModuleRequestType + * @typedef {kImportInImportedESM | kImportInRequiredESM} ModuleRequestType */ -const requestTypes = { kImportInImportedESM, kImportInRequiredESM, kRequireInImportedCJS }; +const requestTypes = { kImportInImportedESM, kImportInRequiredESM }; module.exports = { embedder_module_hdo, @@ -404,7 +390,6 @@ module.exports = { initializeESM, getDefaultConditions, getConditionsSet, - shouldSpawnLoaderHookWorker, compileSourceTextModule, SourceTextModuleTypes, requestTypes, diff --git a/lib/internal/modules/esm/worker.js b/lib/internal/modules/esm/worker.js deleted file mode 100644 index 7d023059a4b0..000000000000 --- a/lib/internal/modules/esm/worker.js +++ /dev/null @@ -1,324 +0,0 @@ -'use strict'; - -const { - AtomicsAdd, - AtomicsNotify, - DataViewPrototypeGetBuffer, - Int32Array, - PromisePrototypeThen, - ReflectApply, - SafeSet, - TypedArrayPrototypeGetBuffer, -} = primordials; -const assert = require('internal/assert'); -const { clearImmediate, setImmediate } = require('timers'); -const { - hasUncaughtExceptionCaptureCallback, -} = require('internal/process/execution'); -const { - isArrayBuffer, - isDataView, - isTypedArray, -} = require('util/types'); - -const { getOptionValue } = require('internal/options'); -const { - loadPreloadModules, - initializeModuleLoaders, - initializeFrozenIntrinsics, -} = require('internal/process/pre_execution'); -const { receiveMessageOnPort } = require('internal/worker/io'); -const { - WORKER_TO_MAIN_THREAD_NOTIFICATION, -} = require('internal/modules/esm/shared_constants'); -const { isMarkedAsUntransferable } = require('internal/buffer'); -const { getCWDURL } = require('internal/util'); -const { isCascadedLoaderInitialized, getOrInitializeCascadedLoader } = require('internal/modules/esm/loader'); -const { AsyncLoaderHooksOnLoaderHookWorker } = require('internal/modules/esm/hooks'); -const { enableWorkerCondition } = require('internal/modules/helpers'); - -/** - * Register asynchronous module loader customization hooks. This should only be run in the loader - * hooks worker. In a non-loader-hooks thread, if any asynchronous loader hook is registered, the - * ModuleLoader#asyncLoaderHooks are initialized to be AsyncLoaderHooksProxiedToLoaderHookWorker - * which posts the messages to the async loader hook worker thread. - * When no asynchronous loader hook is registered, the loader hook worker is not spawned and module - * loading is entiredly done in-thread. - * @returns {Promise} - */ -async function initializeAsyncLoaderHooksOnLoaderHookWorker() { - const customLoaderURLs = getOptionValue('--experimental-loader'); - - // The worker thread spawned for handling asynchronous loader hooks should not - // further spawn other hook threads or there will be an infinite recursion. - const shouldSpawnLoaderHookWorker = false; - // The worker thread for async loader hooks will preload user modules itself in - // initializeAsyncLoaderHooksOnLoaderHookWorker(). - const shouldPreloadModules = false; - initializeModuleLoaders({ shouldSpawnLoaderHookWorker, shouldPreloadModules }); - - assert(!isCascadedLoaderInitialized(), - 'ModuleLoader should be initialized in initializeAsyncLoaderHooksOnLoaderHookWorker()'); - const asyncLoaderHooks = new AsyncLoaderHooksOnLoaderHookWorker(); - getOrInitializeCascadedLoader(asyncLoaderHooks); - - // We need the async loader hooks to be set _before_ we start invoking - // `--require`, otherwise loops can happen because a `--require` script - // might call `register(...)` before we've installed ourselves. These - // global values are magically set in `initializeModuleLoaders` just for us and - // we call them in the correct order. - // N.B. This block appears here specifically in order to ensure that - // `--require` calls occur before `--loader` ones do. - loadPreloadModules(); - initializeFrozenIntrinsics(); - - const parentURL = getCWDURL().href; - for (let i = 0; i < customLoaderURLs.length; i++) { - await asyncLoaderHooks.register(customLoaderURLs[i], parentURL); - } - - return asyncLoaderHooks; -} - -/** - * Transfers an ArrayBuffer, TypedArray, or DataView to a worker thread. - * @param {boolean} hasError - Whether an error occurred during transfer. - * @param {ArrayBuffer[] | TypedArray | DataView} source - The data to transfer. - * @returns {ArrayBuffer[]|undefined} - */ -function transferArrayBuffer(hasError, source) { - if (hasError || source == null) { return; } - let arrayBuffer; - if (isArrayBuffer(source)) { - arrayBuffer = source; - } else if (isTypedArray(source)) { - arrayBuffer = TypedArrayPrototypeGetBuffer(source); - } else if (isDataView(source)) { - arrayBuffer = DataViewPrototypeGetBuffer(source); - } - if (arrayBuffer && !isMarkedAsUntransferable(arrayBuffer)) { - return [arrayBuffer]; - } -} - -/** - * Wraps a message with a status and body, and serializes the body if necessary. - * @param {string} status - The status of the message. - * @param {any} body - The body of the message. - * @returns {{status: string, body: any}} - */ -function wrapMessage(status, body) { - if (status === 'success' || body === null || - (typeof body !== 'object' && - typeof body !== 'function' && - typeof body !== 'symbol')) { - return { status, body }; - } - - let serialized; - let serializationFailed; - try { - const { serializeError } = require('internal/error_serdes'); - serialized = serializeError(body); - } catch { - serializationFailed = true; - } - - return { - status, - body: { - serialized, - serializationFailed, - }, - }; -} - -/** - * Initializes the loader hooks worker thread with customized asynchronous module loading hooks. - * @param {SharedArrayBuffer} lock - The lock used to synchronize communication between the worker and the main thread. - * @param {MessagePort} syncCommPort - The message port used for synchronous communication between the worker and the - * main thread. - * @param {(err: Error, origin?: string) => void} errorHandler - The function to use for uncaught exceptions. - * @returns {Promise} A promise that resolves when the worker thread has been initialized. - */ -async function customizedModuleWorker(lock, syncCommPort, errorHandler) { - let asyncLoaderHooks; - let initializationError; - let hasInitializationError = false; - - { - // If a custom hook is calling `process.exit`, we should wake up the main thread - // so it can detect the exit event. - const { exit } = process; - process.exit = function(code) { - syncCommPort.postMessage(wrapMessage('exit', code ?? process.exitCode)); - AtomicsAdd(lock, WORKER_TO_MAIN_THREAD_NOTIFICATION, 1); - AtomicsNotify(lock, WORKER_TO_MAIN_THREAD_NOTIFICATION); - return ReflectApply(exit, this, arguments); - }; - } - - try { - asyncLoaderHooks = await initializeAsyncLoaderHooksOnLoaderHookWorker(); - } catch (exception) { - // If there was an error while parsing and executing a user loader, for example if because a - // loader contained a syntax error, then we need to send the error to the main thread so it can - // be thrown and printed. - hasInitializationError = true; - initializationError = exception; - } - - syncCommPort.on('message', handleMessage); - - if (hasInitializationError) { - syncCommPort.postMessage(wrapMessage('error', initializationError)); - } else { - syncCommPort.postMessage(wrapMessage('success')); - } - - // We're ready, so unlock the main thread. - AtomicsAdd(lock, WORKER_TO_MAIN_THREAD_NOTIFICATION, 1); - AtomicsNotify(lock, WORKER_TO_MAIN_THREAD_NOTIFICATION); - - let immediate; - /** - * Checks for messages on the syncCommPort and handles them asynchronously. - */ - function checkForMessages() { - immediate = setImmediate(checkForMessages).unref(); - // We need to let the event loop tick a few times to give the main thread a chance to send - // follow-up messages. - const response = receiveMessageOnPort(syncCommPort); - - if (response !== undefined) { - PromisePrototypeThen(handleMessage(response.message), undefined, errorHandler); - } - } - - const unsettledResponsePorts = new SafeSet(); - - process.on('beforeExit', () => { - for (const port of unsettledResponsePorts) { - port.postMessage(wrapMessage('never-settle')); - } - unsettledResponsePorts.clear(); - - AtomicsAdd(lock, WORKER_TO_MAIN_THREAD_NOTIFICATION, 1); - AtomicsNotify(lock, WORKER_TO_MAIN_THREAD_NOTIFICATION); - - // Attach back the event handler. - syncCommPort.on('message', handleMessage); - // Also check synchronously for a message, in case it's already there. - clearImmediate(immediate); - checkForMessages(); - // We don't need the sync check after this tick, as we already have added the event handler. - clearImmediate(immediate); - // Add some work for next tick so the worker cannot exit. - setImmediate(() => {}); - }); - - /** - * Handles incoming messages from the main thread or other workers. - * @param {object} options - The options object. - * @param {string} options.method - The name of the hook. - * @param {Array} options.args - The arguments to pass to the method. - * @param {MessagePort} options.port - The message port to use for communication. - */ - async function handleMessage({ method, args, port }) { - // Each potential exception needs to be caught individually so that the correct error is sent to - // the main thread. - let hasError = false; - let shouldRemoveGlobalErrorHandler = false; - assert(typeof asyncLoaderHooks[method] === 'function', `${method} is not implemented in the loader worker`); - if (port == null && !hasUncaughtExceptionCaptureCallback()) { - // When receiving sync messages, we want to unlock the main thread when there's an exception. - process.on('uncaughtException', errorHandler); - shouldRemoveGlobalErrorHandler = true; - } - - // We are about to yield the execution with `await ReflectApply` below. In case the code - // following the `await` never runs, we remove the message handler so the `beforeExit` event - // can be triggered. - syncCommPort.off('message', handleMessage); - - // We keep checking for new messages to not miss any. - clearImmediate(immediate); - immediate = setImmediate(checkForMessages).unref(); - - unsettledResponsePorts.add(port ?? syncCommPort); - - let response; - try { - response = await ReflectApply(asyncLoaderHooks[method], asyncLoaderHooks, args); - } catch (exception) { - hasError = true; - response = exception; - } - - unsettledResponsePorts.delete(port ?? syncCommPort); - - // Send the method response (or exception) to the main thread. - try { - (port ?? syncCommPort).postMessage( - wrapMessage(hasError ? 'error' : 'success', response), - transferArrayBuffer(hasError, response?.source), - ); - } catch (exception) { - // Or send the exception thrown when trying to send the response. - (port ?? syncCommPort).postMessage(wrapMessage('error', exception)); - } - - if (shouldRemoveGlobalErrorHandler) { - process.off('uncaughtException', errorHandler); - } - - syncCommPort.off('message', handleMessage); - // We keep checking for new messages to not miss any. - clearImmediate(immediate); - immediate = setImmediate(checkForMessages).unref(); - // To prevent the main thread from terminating before this function completes after unlocking, - // the following process is executed at the end of the function. - AtomicsAdd(lock, WORKER_TO_MAIN_THREAD_NOTIFICATION, 1); - AtomicsNotify(lock, WORKER_TO_MAIN_THREAD_NOTIFICATION); - } -} - -/** - * Initializes a worker thread for a module with customized hooks. - * ! Run everything possible within this function so errors get reported. - * @param {{lock: SharedArrayBuffer, workerCondition: boolean}} workerData - The lock used to - * synchronize with the main thread, and whether the spawning thread is a Web Worker. - * @param {MessagePort} syncCommPort - The communication port used to communicate with the main thread. - * @returns {object} - */ -module.exports = function setupModuleWorker(workerData, syncCommPort) { - const lock = new Int32Array(workerData.lock); - if (workerData.workerCondition) { - enableWorkerCondition(); - } - - /** - * Handles errors that occur in the worker thread. - * @param {Error} err - The error that occurred. - * @param {string} [origin] - The origin of the error. - */ - function errorHandler(err, origin = 'unhandledRejection') { - AtomicsAdd(lock, WORKER_TO_MAIN_THREAD_NOTIFICATION, 1); - AtomicsNotify(lock, WORKER_TO_MAIN_THREAD_NOTIFICATION); - process.off('uncaughtException', errorHandler); - if (hasUncaughtExceptionCaptureCallback()) { - process._fatalException(err); - return; - } - internalBinding('errors').triggerUncaughtException( - err, - origin === 'unhandledRejection', - ); - } - - return PromisePrototypeThen( - customizedModuleWorker(lock, syncCommPort, errorHandler), - undefined, - errorHandler, - ); -}; diff --git a/lib/internal/modules/package_json_reader.js b/lib/internal/modules/package_json_reader.js index 2ef5d1ebed39..f5fa643ac6c3 100644 --- a/lib/internal/modules/package_json_reader.js +++ b/lib/internal/modules/package_json_reader.js @@ -412,11 +412,9 @@ function findPackageJSON(specifier, base = 'data:') { defaultResolve ??= require('internal/modules/esm/resolve').defaultResolve; try { - // TODO(@JakobJingleheimer): Detect whether findPackageJSON is being used within a loader - // (possibly piggyback on `isForAsyncLoaderHookWorker` from the loader?) and if so: - // - When inside, use the default resolve - // - (I think it's impossible to use the chain because of re-entry & a deadlock from atomics). - // - When outside, use cascadedLoader.resolveSync (not implemented yet, but the pieces exist). + // TODO(@JakobJingleheimer): This always uses the default resolver and so ignores any + // module.registerHooks() resolve hooks. Using cascadedLoader.resolveSync would apply them, + // but that needs to handle re-entry when findPackageJSON is called from inside a hook. resolvedTarget = defaultResolve(specifier, { parentURL: `${parentURL}` }).url; } catch (err) { if (err.code === 'ERR_UNSUPPORTED_DIR_IMPORT') { diff --git a/lib/internal/modules/run_main.js b/lib/internal/modules/run_main.js index b0d7d4845eaf..ec97f46f5a0f 100644 --- a/lib/internal/modules/run_main.js +++ b/lib/internal/modules/run_main.js @@ -51,17 +51,12 @@ function resolveMainPath(main) { * @returns {boolean} */ function shouldUseESMLoader(mainPath) { - /** - * @type {string[]} userLoaders A list of custom loaders registered by the user - * (or an empty list when none have been registered). - */ - const userLoaders = getOptionValue('--experimental-loader'); /** * @type {string[]} userImports A list of preloaded modules registered by the user * (or an empty list when none have been registered). */ const userImports = getOptionValue('--import'); - if (userLoaders.length > 0 || userImports.length > 0) { return true; } + if (userImports.length > 0) { return true; } // Determine the module format of the entry point. if (mainPath && StringPrototypeEndsWith(mainPath, '.mjs')) { return true; } @@ -96,8 +91,6 @@ async function asyncRunEntryPointWithESMLoader(callback) { for (let i = 0; i < userImports.length; i++) { await cascadedLoader.import(userImports[i], parentURL, kEmptyObject); } - } else { - cascadedLoader.waitForAsyncLoaderHookInitialization(); } // Any --import above may have registered a VFS provider, so mount now (no-op // if prepareExecution already mounted in the no-import case). @@ -175,11 +168,10 @@ function executeUserEntryPoint(main = process.argv[1]) { const mainPath = resolvedMain || main; const mainURL = getOptionValue('--entry-url') ? new URL(mainPath, getCWDURL()) : pathToFileURL(mainPath); - // When no async loader hooks or --import are registered, load the entry point + // When no --import preloads are registered, load the entry point // directly. The graph is loaded and linked synchronously, and evaluated // synchronously too unless it contains top-level await. - if (getOptionValue('--experimental-loader').length === 0 && - getOptionValue('--import').length === 0) { + if (getOptionValue('--import').length === 0) { const cascadedLoader = require('internal/modules/esm/loader').getOrInitializeCascadedLoader(); const job = cascadedLoader.importForEntryPoint(mainURL.href); if (job === undefined) { diff --git a/lib/internal/process/pre_execution.js b/lib/internal/process/pre_execution.js index 0f5aaf857aeb..b5933f4e9d61 100644 --- a/lib/internal/process/pre_execution.js +++ b/lib/internal/process/pre_execution.js @@ -51,7 +51,6 @@ function prepareMainThreadExecution(expandArgv1 = false, initializeModules = tru expandArgv1, initializeModules, isMainThread: true, - shouldSpawnLoaderHookWorker: initializeModules, shouldPreloadModules: initializeModules, }); } @@ -61,7 +60,6 @@ function prepareTestRunnerMainExecution(loadUserModules = true) { expandArgv1: false, initializeModules: true, isMainThread: true, - shouldSpawnLoaderHookWorker: loadUserModules, shouldPreloadModules: loadUserModules, }); } @@ -73,7 +71,6 @@ function prepareWorkerThreadExecution() { // Module loader initialization in workers are delayed until the worker thread // is ready for execution. initializeModules: false, - shouldSpawnLoaderHookWorker: false, shouldPreloadModules: false, }); } @@ -84,8 +81,8 @@ function prepareShadowRealmExecution() { patchProcessObject(false); setupDebugEnv(); - // Disable custom loaders in ShadowRealm. - initializeModuleLoaders({ shouldSpawnLoaderHookWorker: false, shouldPreloadModules: false }); + // Preload modules are not supported in ShadowRealm. + initializeModuleLoaders({ shouldPreloadModules: false }); const { privateSymbols: { host_defined_option_symbol, @@ -107,7 +104,7 @@ function prepareShadowRealmExecution() { } function prepareExecution(options) { - const { expandArgv1, initializeModules, isMainThread, shouldSpawnLoaderHookWorker, shouldPreloadModules } = options; + const { expandArgv1, initializeModules, isMainThread, shouldPreloadModules } = options; refreshRuntimeOptions(); @@ -172,7 +169,7 @@ function prepareExecution(options) { setupVmModules(); if (initializeModules) { - initializeModuleLoaders({ shouldSpawnLoaderHookWorker, shouldPreloadModules }); + initializeModuleLoaders({ shouldPreloadModules }); } // Mount here only when there is no --import: those preloads run later (inside @@ -319,15 +316,13 @@ function warnAboutRemovedProxyEnvVars() { } function initializeModuleLoaders(options) { - const { shouldSpawnLoaderHookWorker, shouldPreloadModules } = options; + const { shouldPreloadModules } = options; // Initialize certain special module.Module properties and the CJS conditions. const { initializeCJS } = require('internal/modules/cjs/loader'); initializeCJS(); // Initialize the ESM loader and a few module callbacks. - // If shouldSpawnLoaderHookWorker is true, later when the ESM loader is instantiated on-demand, - // it will spawn a loader worker thread to handle async custom loader hooks. const { initializeESM } = require('internal/modules/esm/utils'); - initializeESM(shouldSpawnLoaderHookWorker); + initializeESM(); const { hasStartedUserCJSExecution, @@ -340,9 +335,7 @@ function initializeModuleLoaders(options) { if (getEmbedderOptions().hasEmbedderPreload) { runEmbedderPreload(); } - // Do not enable preload modules if custom loaders are disabled. - // For example, loader workers are responsible for doing this themselves. - // And preload modules are not supported in ShadowRealm as well. + // Preload modules are not supported in ShadowRealm. if (shouldPreloadModules) { loadPreloadModules(); } diff --git a/lib/internal/test_runner/mock/loader.js b/lib/internal/test_runner/mock/loader.js index d64ffdfa0965..266525c8d52a 100644 --- a/lib/internal/test_runner/mock/loader.js +++ b/lib/internal/test_runner/mock/loader.js @@ -60,12 +60,11 @@ function load(url, context, nextLoad) { // Treat builtins as commonjs because customization hooks do not allow a // core module to be replaced. - // Also collapse 'commonjs-sync' and 'require-commonjs' to 'commonjs'. + // Also collapse 'commonjs-sync' to 'commonjs'. let format = original.format; switch (original.format) { case 'builtin': // Deliberate fallthrough - case 'commonjs-sync': // Deliberate fallthrough - case 'require-commonjs': + case 'commonjs-sync': format = 'commonjs'; break; case 'json': diff --git a/lib/internal/worker.js b/lib/internal/worker.js index 728132f779e3..00b0293e85dc 100644 --- a/lib/internal/worker.js +++ b/lib/internal/worker.js @@ -44,7 +44,6 @@ const { const workerIo = require('internal/worker/io'); const { drainMessagePort, - receiveMessageOnPort, MessageChannel, messageTypes, kPort, @@ -73,7 +72,6 @@ const { const { ownsProcessState, isMainThread, - isInternalThread, resourceLimits: resourceLimitsRaw, threadId, threadName, @@ -94,7 +92,6 @@ const kOnCouldNotSerializeErr = Symbol('kOnCouldNotSerializeErr'); const kOnErrorMessage = Symbol('kOnErrorMessage'); const kParentSideStdio = Symbol('kParentSideStdio'); const kLoopStartTime = Symbol('kLoopStartTime'); -const kIsInternal = Symbol('kIsInternal'); const kIsOnline = Symbol('kIsOnline'); const kWebWorkerData = Symbol('kWebWorkerData'); @@ -203,13 +200,7 @@ class Worker extends EventEmitter { constructor(filename, options = kEmptyObject) { throwIfBuildingSnapshot('Creating workers'); super(); - const isInternal = arguments[2] === kIsInternal; - debug( - `[${threadId}] create new worker`, - filename, - options, - `isInternal: ${isInternal}`, - ); + debug(`[${threadId}] create new worker`, filename, options); if (options.execArgv) validateArray(options.execArgv, 'options.execArgv'); @@ -220,10 +211,7 @@ class Worker extends EventEmitter { } let url, doEval; - if (isInternal) { - doEval = 'internal'; - url = `node:${filename}`; - } else if (options.eval) { + if (options.eval) { if (typeof filename !== 'string') { throw new ERR_INVALID_ARG_VALUE( 'options.eval', @@ -286,7 +274,6 @@ class Worker extends EventEmitter { options.execArgv, parseResourceLimits(options.resourceLimits), !!(options.trackUnmanagedFds ?? true), - isInternal, name); if (this[kHandle].invalidExecArgv) { throw new ERR_WORKER_INVALID_EXEC_ARGV(this[kHandle].invalidExecArgv); @@ -361,7 +348,6 @@ class Worker extends EventEmitter { type: messageTypes.LOAD_SCRIPT, filename, doEval, - isInternal, cwdCounter: cwdCounter || workerIo.sharedCwdCounter, workerData: options.workerData, webWorkerData: options[kWebWorkerData], @@ -642,20 +628,6 @@ class Worker extends EventEmitter { } } -/** - * A worker which has an internal module for entry point (e.g. internal/module/esm/worker). - * Internal workers bypass the permission model. - */ -class InternalWorker extends Worker { - constructor(filename, options) { - super(filename, options, kIsInternal); - } - - receiveMessageSync() { - return receiveMessageOnPort(this[kPublicPort]); - } -} - function pipeWithoutWarning(source, dest) { const sourceMaxListeners = source._maxListeners; const destMaxListeners = dest._maxListeners; @@ -723,7 +695,6 @@ module.exports = { ownsProcessState, kIsOnline, isMainThread, - isInternalThread, SHARE_ENV, resourceLimits: !isMainThread ? makeResourceLimits(resourceLimitsRaw) : {}, @@ -732,7 +703,6 @@ module.exports = { assignEnvironmentData, threadId, threadName, - InternalWorker, Worker, kPublicPort, kWebWorkerData, diff --git a/lib/module.js b/lib/module.js index 1217172afb3c..60ba0803dc55 100644 --- a/lib/module.js +++ b/lib/module.js @@ -6,7 +6,6 @@ const { setSourceMapsSupport, } = require('internal/source_map/source_map_cache'); const { Module } = require('internal/modules/cjs/loader'); -const { register } = require('internal/modules/esm/loader'); const { SourceMap, } = require('internal/source_map/source_map'); @@ -21,7 +20,6 @@ const { } = require('internal/modules/package_json_reader'); const { stripTypeScriptTypes } = require('internal/modules/typescript'); -Module.register = register; Module.constants = constants; Module.enableCompileCache = enableCompileCache; Module.findPackageJSON = findPackageJSON; diff --git a/lib/worker_threads.js b/lib/worker_threads.js index 622bef3f55c1..b6a0887784d1 100644 --- a/lib/worker_threads.js +++ b/lib/worker_threads.js @@ -3,7 +3,6 @@ const { defineLazyProperties } = require('internal/util'); const { - isInternalThread, isMainThread, SHARE_ENV, resourceLimits, @@ -33,7 +32,7 @@ const { } = require('internal/buffer'); module.exports = { - isInternalThread, + isInternalThread: false, isMainThread, MessagePort, MessageChannel, diff --git a/src/module_wrap.cc b/src/module_wrap.cc index 9892f38d96bd..444d31337531 100644 --- a/src/module_wrap.cc +++ b/src/module_wrap.cc @@ -1362,7 +1362,7 @@ static Maybe DefaultImportMetaObjectInitializer(Realm* realm, } // Set a lazy getter of import.meta.resolve - only if the initializer is set, - // which is only the case when run on a non-loader-hook thread. + // which happens once the cascaded loader has been initialized. Local import_meta_resolve_initializer = realm->host_import_meta_resolve_initializer(); if (!import_meta_resolve_initializer.IsEmpty() && diff --git a/src/node_options.cc b/src/node_options.cc index 4ba4801ad1dc..3f86cce88595 100644 --- a/src/node_options.cc +++ b/src/node_options.cc @@ -854,11 +854,6 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() { true); AddOption("--experimental-global-webcrypto", "", NoOp{}, kAllowedInEnvvar); AddOption("--experimental-json-modules", "", NoOp{}, kAllowedInEnvvar); - AddOption("--experimental-loader", - "use the specified module as a custom loader", - &EnvironmentOptions::userland_loaders, - kAllowedInEnvvar); - AddAlias("--loader", "--experimental-loader"); AddOption("--experimental-modules", "", NoOp{}, kAllowedInEnvvar); AddOption("--experimental-wasm-modules", "", NoOp{}, kAllowedInEnvvar); AddOption("--experimental-import-text", diff --git a/src/node_options.h b/src/node_options.h index f8aa58253862..eb8255f70e26 100644 --- a/src/node_options.h +++ b/src/node_options.h @@ -99,11 +99,6 @@ class DebugOptions : public Options { break_first_line = true; } - void DisableWaitOrBreakFirstLine() { - inspect_wait = false; - break_first_line = false; - } - bool wait_for_connect() const { return break_first_line || break_node_first_line || inspect_wait; } @@ -176,7 +171,6 @@ class EnvironmentOptions : public Options { std::vector experimental_test_tag_filter; std::vector coverage_include_pattern; std::vector coverage_exclude_pattern; - std::vector userland_loaders; std::vector watch_mode_paths; std::vector preload_cjs_modules; std::vector preload_esm_modules; diff --git a/src/node_watchdog.cc b/src/node_watchdog.cc index c403f8cb0715..0dca0085e0d3 100644 --- a/src/node_watchdog.cc +++ b/src/node_watchdog.cc @@ -305,7 +305,7 @@ std::string FormatProcessTimeoutDiagnostics(Environment* env) { // Running Workers keep the event loop alive through the Environment's own // task queue handle rather than through a HandleWrap. env->ForEachWorker([&](worker::Worker* worker) { - if (worker->is_internal() || !worker->has_ref() || worker->is_stopped()) { + if (!worker->has_ref() || worker->is_stopped()) { return; } std::string details = SPrintF("thread %d", worker->thread_id()); diff --git a/src/node_worker.cc b/src/node_worker.cc index 308d987680d7..9c8f215e094e 100644 --- a/src/node_worker.cc +++ b/src/node_worker.cc @@ -64,8 +64,7 @@ Worker::Worker(Environment* env, std::shared_ptr per_isolate_opts, std::vector&& exec_argv, std::shared_ptr env_vars, - const SnapshotData* snapshot_data, - const bool is_internal) + const SnapshotData* snapshot_data) : AsyncWrap(env, wrap, AsyncWrap::PROVIDER_WORKER), per_isolate_opts_(per_isolate_opts), exec_argv_(exec_argv), @@ -74,8 +73,7 @@ Worker::Worker(Environment* env, name_(name), env_vars_(env_vars), embedder_preload_(env->embedder_preload()), - snapshot_data_(snapshot_data), - is_internal_(is_internal) { + snapshot_data_(snapshot_data) { Debug(this, "Creating new worker instance with thread id %llu", thread_id_.id); @@ -524,7 +522,6 @@ void Worker::New(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); THROW_IF_INSUFFICIENT_PERMISSIONS( env, permission::PermissionScope::kWorkerThreads, ""); - bool is_internal = args[5]->IsTrue(); Isolate* isolate = args.GetIsolate(); CHECK(args.IsConstructCall()); @@ -549,9 +546,9 @@ void Worker::New(const FunctionCallbackInfo& args) { url.append(value.out(), value.length()); } - if (!args[6]->IsNullOrUndefined()) { + if (!args[5]->IsNullOrUndefined()) { Utf8Value value( - isolate, args[6]->ToString(env->context()).FromMaybe(Local())); + isolate, args[5]->ToString(env->context()).FromMaybe(Local())); name.append(value.out(), value.length()); } @@ -709,16 +706,6 @@ void Worker::New(const FunctionCallbackInfo& args) { // is inherited but the load behavior must not be. per_isolate_opts->per_env->vfs_load = false; - // Internal workers should not wait for inspector frontend to connect or - // break on the first line of internal scripts. Module loader threads are - // essential to load user codes and must not be blocked by the inspector - // for internal scripts. - // Still, `--inspect-node` can break on the first line of internal scripts. - if (is_internal) { - per_isolate_opts->per_env->get_debug_options() - ->DisableWaitOrBreakFirstLine(); - } - const SnapshotData* snapshot_data = env->isolate_data()->snapshot_data(); Worker* worker = new Worker(env, @@ -728,8 +715,7 @@ void Worker::New(const FunctionCallbackInfo& args) { per_isolate_opts, std::move(exec_argv_out), env_vars, - snapshot_data, - is_internal); + snapshot_data); CHECK(args[3]->IsFloat64Array()); Local limit_info = args[3].As(); @@ -1474,13 +1460,6 @@ void IsMainThreadGetter(Local, info.GetReturnValue().Set(Environment::GetCurrent(info)->is_main_thread()); } -void IsInternalThreadGetter(Local, - const v8::PropertyCallbackInfo& info) { - Worker* worker = - Environment::GetCurrent(info)->isolate_data()->worker_context(); - info.GetReturnValue().Set(worker != nullptr && worker->is_internal()); -} - void OwnsProcessStateGetter(Local, const v8::PropertyCallbackInfo& info) { info.GetReturnValue().Set( @@ -1506,7 +1485,6 @@ void CreateWorkerPerIsolateProperties(IsolateData* isolate_data, lazy("threadId", ThreadIdGetter); lazy("threadName", ThreadNameGetter); lazy("isMainThread", IsMainThreadGetter); - lazy("isInternalThread", IsInternalThreadGetter); lazy("ownsProcessState", OwnsProcessStateGetter); lazy("resourceLimits", ResourceLimitsGetter); } @@ -1615,7 +1593,7 @@ void CreateWorkerPerContextProperties(Local target, Local unused, Local context, void* priv) { - // threadId, threadName, isMainThread, isInternalThread, ownsProcessState + // threadId, threadName, isMainThread, ownsProcessState // and resourceLimits are lazy properties of the per-isolate template (see // CreateWorkerPerIsolateProperties). NODE_DEFINE_CONSTANT(target, kMaxYoungGenerationSizeMb); @@ -1630,7 +1608,6 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) { registry->Register(ThreadIdGetter); registry->Register(ThreadNameGetter); registry->Register(IsMainThreadGetter); - registry->Register(IsInternalThreadGetter); registry->Register(OwnsProcessStateGetter); registry->Register(ResourceLimitsGetter); registry->Register(Worker::New); diff --git a/src/node_worker.h b/src/node_worker.h index 03e546534c1b..4261db0c18fc 100644 --- a/src/node_worker.h +++ b/src/node_worker.h @@ -36,8 +36,7 @@ class Worker : public AsyncWrap { std::shared_ptr per_isolate_opts, std::vector&& exec_argv, std::shared_ptr env_vars, - const SnapshotData* snapshot_data, - const bool is_internal); + const SnapshotData* snapshot_data); ~Worker() override; // Run the worker. This is only called from the worker thread. @@ -64,7 +63,6 @@ class Worker : public AsyncWrap { bool is_stopped() const; const SnapshotData* snapshot_data() const { return snapshot_data_; } - bool is_internal() const { return is_internal_; } std::string_view name() const { return name_; } uint64_t thread_id() const { return thread_id_.id; } // Whether the Worker keeps the parent's event loop alive, see @@ -146,7 +144,6 @@ class Worker : public AsyncWrap { Environment* env_ = nullptr; const SnapshotData* snapshot_data_ = nullptr; - const bool is_internal_; friend class WorkerThreadData; }; diff --git a/test/doctool/test-doc-api-json.mjs b/test/doctool/test-doc-api-json.mjs index ba0469da50a1..104ef026c762 100644 --- a/test/doctool/test-doc-api-json.mjs +++ b/test/doctool/test-doc-api-json.mjs @@ -158,5 +158,5 @@ for await (const dirent of await fs.opendir(new URL('../../out/doc/api/', import assert.partialDeepStrictEqual(allExpectedKeys, findAllKeys(json)); } -assert.strictEqual(numberOfDeprecatedSections, 48); // Increase this number every time a new API is deprecated. -assert.strictEqual(numberOfRemovedAPIs, 47); // Increase this number every time a section is marked as removed. +assert.strictEqual(numberOfDeprecatedSections, 47); // Increase this number every time a new API is deprecated. +assert.strictEqual(numberOfRemovedAPIs, 49); // Increase this number every time a section is marked as removed. diff --git a/test/es-module/test-esm-example-loader.mjs b/test/es-module/test-esm-example-loader.mjs deleted file mode 100644 index b85de4e64c4a..000000000000 --- a/test/es-module/test-esm-example-loader.mjs +++ /dev/null @@ -1,6 +0,0 @@ -// Flags: --experimental-loader ./test/fixtures/es-module-loaders/example-loader.mjs -/* eslint-disable node-core/require-common-first, node-core/required-modules */ -import assert from 'assert'; -import ok from '../fixtures/es-modules/test-esm-ok.mjs'; - -assert(ok); diff --git a/test/es-module/test-esm-experimental-warnings.mjs b/test/es-module/test-esm-experimental-warnings.mjs deleted file mode 100644 index 7b3ec4b942ee..000000000000 --- a/test/es-module/test-esm-experimental-warnings.mjs +++ /dev/null @@ -1,50 +0,0 @@ -import { spawnPromisified } from '../common/index.mjs'; -import * as fixtures from '../common/fixtures.mjs'; -import assert from 'node:assert'; -import { execPath } from 'node:process'; -import { describe, it } from 'node:test'; - - -describe('ESM: warn for obsolete hooks provided', { concurrency: !process.env.TEST_PARALLEL }, () => { - it('should not print warnings when no experimental features are enabled or used', async () => { - const { code, signal, stderr } = await spawnPromisified(execPath, [ - '--input-type=module', - '--eval', - `import ${JSON.stringify(fixtures.fileURL('es-module-loaders', 'module-named-exports.mjs'))}`, - ]); - - assert.doesNotMatch( - stderr, - /ExperimentalWarning/, - new Error('No experimental warning(s) should be emitted when no experimental feature is enabled') - ); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - - describe('experimental warnings for enabled experimental feature', () => { - for ( - const [experiment, ...args] of [ - [ - /`--experimental-loader` may be removed in the future/, - '--experimental-loader', - fixtures.fileURL('es-module-loaders', 'hooks-custom.mjs'), - ], - ] - ) { - it(`should print for ${experiment.toString().replaceAll('/', '')}`, async () => { - const { code, signal, stderr } = await spawnPromisified(execPath, [ - ...args, - '--input-type=module', - '--eval', - `import ${JSON.stringify(fixtures.fileURL('es-module-loaders', 'module-named-exports.mjs'))}`, - ]); - - assert.match(stderr, /ExperimentalWarning/); - assert.match(stderr, experiment); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - } - }); -}); diff --git a/test/es-module/test-esm-import-assertion-warning.mjs b/test/es-module/test-esm-import-assertion-warning.mjs deleted file mode 100644 index 2f9b7348a2d9..000000000000 --- a/test/es-module/test-esm-import-assertion-warning.mjs +++ /dev/null @@ -1,42 +0,0 @@ -import { spawnPromisified } from '../common/index.mjs'; -import assert from 'node:assert'; -import { execPath } from 'node:process'; - -await Promise.all([ - // Using importAssertions in the resolve hook should warn but still work. - `data:text/javascript,export ${encodeURIComponent(function resolve() { - return { shortCircuit: true, url: 'data:application/json,1', importAssertions: { type: 'json' } }; - })}`, - // Using importAssertions on the context object of the resolve hook should warn but still work. - `data:text/javascript,export ${encodeURIComponent(function resolve(s, c, n) { - const type = c.importAssertions.type; - return { shortCircuit: true, url: 'data:application/json,1', importAttributes: { type: type ?? 'json' } }; - })}`, - // Setting importAssertions on the context object of the load hook should warn but still work. - `data:text/javascript,export ${encodeURIComponent(function load(u, c, n) { - c.importAssertions = { type: 'json' }; - return n('data:application/json,1', c); - })}`, - // Creating a new context object with importAssertions in the load hook should warn but still work. - `data:text/javascript,export ${encodeURIComponent(function load(u, c, n) { - return n('data:application/json,1', { importAssertions: { type: 'json' } }); - })}`, -].map(async (loaderURL) => { - const { stdout, stderr, code } = await spawnPromisified(execPath, [ - '--input-type=module', - '--eval', ` - import assert from 'node:assert'; - import { register } from 'node:module'; - - register(${JSON.stringify(loaderURL)}); - - assert.deepStrictEqual( - { ...await import('data:') }, - { default: 1 } - );`, - ]); - - assert.match(stderr, /Use `importAttributes` instead of `importAssertions`/); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 0); -})); diff --git a/test/es-module/test-esm-import-defers-evaluation.js b/test/es-module/test-esm-import-defers-evaluation.js new file mode 100644 index 000000000000..0679493fe961 --- /dev/null +++ b/test/es-module/test-esm-import-defers-evaluation.js @@ -0,0 +1,16 @@ +'use strict'; +// Tests that import() never evaluates the module before returning to its +// caller, even though loading and linking are synchronous. +const common = require('../common'); +const assert = require('node:assert'); + +const key = '__esmImportDefersEvaluation'; +const url = `data:text/javascript,globalThis.${key} = true; export default 1;`; + +const promise = import(url); +assert.strictEqual(globalThis[key], undefined); +promise.then(common.mustCall((namespace) => { + assert.strictEqual(globalThis[key], true); + assert.strictEqual(namespace.default, 1); + delete globalThis[key]; +})); diff --git a/test/es-module/test-esm-initialization.mjs b/test/es-module/test-esm-initialization.mjs deleted file mode 100644 index c86d3b439496..000000000000 --- a/test/es-module/test-esm-initialization.mjs +++ /dev/null @@ -1,30 +0,0 @@ -import { spawnPromisified } from '../common/index.mjs'; -import * as fixtures from '../common/fixtures.mjs'; -import assert from 'node:assert'; -import { execPath } from 'node:process'; -import { describe, it } from 'node:test'; - - -describe('ESM: ensure initialization happens only once', { concurrency: !process.env.TEST_PARALLEL }, () => { - it(async () => { - const { code, stderr, stdout } = await spawnPromisified(execPath, [ - '--experimental-import-meta-resolve', - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-passthru.mjs'), - '--no-warnings', - fixtures.path('es-modules', 'runmain.mjs'), - ]); - - assert.strictEqual(stderr, ''); - /** - * resolveHookRunCount = 2: - * 1. fixtures/…/runmain.mjs - * 2. node:module (imported by fixtures/…/runmain.mjs) - * 3. doesnt-matter.mjs (first import.meta.resolve call) - * 4. fixtures/…/runmain.mjs (entry point) - * 5. doesnt-matter.mjs (second import.meta.resolve call) - */ - assert.strictEqual(stdout.match(/resolve passthru/g)?.length, 5); - assert.strictEqual(code, 0); - }); -}); diff --git a/test/es-module/test-esm-loader-chaining.mjs b/test/es-module/test-esm-loader-chaining.mjs deleted file mode 100644 index 6c266b9da3d8..000000000000 --- a/test/es-module/test-esm-loader-chaining.mjs +++ /dev/null @@ -1,507 +0,0 @@ -import { spawnPromisified } from '../common/index.mjs'; -import * as fixtures from '../common/fixtures.mjs'; -import assert from 'node:assert'; -import { execPath } from 'node:process'; -import { describe, it } from 'node:test'; - -const setupArgs = [ - '--no-warnings', - '--input-type=module', - '--eval', -]; -const commonInput = 'import os from "node:os"; console.log(os)'; -const commonArgs = [ - ...setupArgs, - commonInput, -]; - -describe('ESM: loader chaining', { concurrency: !process.env.TEST_PARALLEL }, () => { - it('should load unadulterated source when there are no loaders', async () => { - const { code, stderr, stdout } = await spawnPromisified( - execPath, - [ - ...setupArgs, - 'import fs from "node:fs"; console.log(typeof fs?.constants?.F_OK )', - ], - { encoding: 'utf8' }, - ); - - assert.strictEqual(stderr, ''); - assert.match(stdout, /number/); // node:fs is an object - assert.strictEqual(code, 0); - }); - - it('should load properly different source when only load changes something', async () => { - const { code, stderr, stdout } = await spawnPromisified( - execPath, - [ - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-passthru.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-load-foo-or-42.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-load-passthru.mjs'), - ...commonArgs, - ], - { encoding: 'utf8' }, - ); - - assert.strictEqual(stderr, ''); - assert.match(stdout, /load passthru/); - assert.match(stdout, /resolve passthru/); - assert.match(stdout, /foo/); - assert.strictEqual(code, 0); - }); - - it('should result in proper output from multiple changes in resolve hooks', async () => { - const { code, stderr, stdout } = await spawnPromisified( - execPath, - [ - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-shortcircuit.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-foo.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-42.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-load-foo-or-42.mjs'), - ...commonArgs, - ], - { encoding: 'utf8' }, - ); - - assert.strictEqual(stderr, ''); - assert.match(stdout, /resolve 42/); // It did go thru resolve-42 - assert.match(stdout, /foo/); // LIFO, so resolve-foo won - assert.strictEqual(code, 0); - }); - - it('should respect modified context within resolve chain', async () => { - const { code, stderr, stdout } = await spawnPromisified( - execPath, - [ - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-shortcircuit.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-42.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-load-foo-or-42.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-load-receiving-modified-context.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-load-passing-modified-context.mjs'), - ...commonArgs, - ], - { encoding: 'utf8' }, - ); - - assert.strictEqual(stderr, ''); - assert.match(stdout, /bar/); - assert.strictEqual(code, 0); - }); - - it('should accept only the correct arguments', async () => { - const { stdout } = await spawnPromisified( - execPath, - [ - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-log-args.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-with-too-many-args.mjs'), - ...commonArgs, - ], - { encoding: 'utf8' }, - ); - - assert.match(stdout, /^resolve arg count: 3$/m); - assert.match(stdout, /specifier: 'node:os'/); - assert.match(stdout, /next: \[AsyncFunction: nextResolve\]/); - - assert.match(stdout, /^load arg count: 3$/m); - assert.match(stdout, /url: 'node:os'/); - assert.match(stdout, /next: \[AsyncFunction: nextLoad\]/); - }); - - it('should result in proper output from multiple changes in resolve hooks', async () => { - const { code, stderr, stdout } = await spawnPromisified( - execPath, - [ - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-shortcircuit.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-42.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-foo.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-load-foo-or-42.mjs'), - ...commonArgs, - ], - { encoding: 'utf8' }, - ); - - assert.strictEqual(stderr, ''); - assert.match(stdout, /resolve foo/); // It did go thru resolve-foo - assert.match(stdout, /42/); // LIFO, so resolve-42 won - assert.strictEqual(code, 0); - }); - - it('should provide the correct "next" fn when multiple calls to next within same loader', async () => { - const { code, stderr, stdout } = await spawnPromisified( - execPath, - [ - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-shortcircuit.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-foo.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-multiple-next-calls.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-load-foo-or-42.mjs'), - ...commonArgs, - ], - { encoding: 'utf8' }, - ); - - const countFoos = stdout.match(/resolve foo/g)?.length; - - assert.strictEqual(stderr, ''); - assert.strictEqual(countFoos, 2); - assert.strictEqual(code, 0); - }); - - it('should use the correct `name` for next\'s function', async () => { - const { code, stderr, stdout } = await spawnPromisified( - execPath, - [ - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-shortcircuit.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-42.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-load-foo-or-42.mjs'), - ...commonArgs, - ], - { encoding: 'utf8' }, - ); - - assert.strictEqual(stderr, ''); - assert.match(stdout, /next: nextResolve/); - assert.strictEqual(code, 0); - }); - - it('should throw for incomplete resolve chain, citing errant loader & hook', async () => { - const { code, stderr, stdout } = await spawnPromisified( - execPath, - [ - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-incomplete.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-passthru.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-load-foo-or-42.mjs'), - ...commonArgs, - ], - { encoding: 'utf8' }, - ); - assert.match(stdout, /resolve passthru/); - assert.match(stderr, /ERR_LOADER_CHAIN_INCOMPLETE/); - assert.match(stderr, /loader-resolve-incomplete\.mjs/); - assert.match(stderr, /'resolve'/); - assert.strictEqual(code, 1); - }); - - it('should NOT throw when nested resolve hook signaled a short circuit', async () => { - const { code, stderr, stdout } = await spawnPromisified( - execPath, - [ - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-shortcircuit.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-next-modified.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-load-foo-or-42.mjs'), - ...commonArgs, - ], - { encoding: 'utf8' }, - ); - - assert.strictEqual(stderr, ''); - assert.strictEqual(stdout.trim(), 'foo'); - assert.strictEqual(code, 0); - }); - - it('should NOT throw when nested load hook signaled a short circuit', async () => { - const { code, stderr, stdout } = await spawnPromisified( - execPath, - [ - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-shortcircuit.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-42.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-load-foo-or-42.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-load-next-modified.mjs'), - ...commonArgs, - ], - { encoding: 'utf8' }, - ); - - assert.strictEqual(stderr, ''); - assert.match(stdout, /421/); - assert.strictEqual(code, 0); - }); - - it('should allow loaders to influence subsequent loader resolutions', async () => { - const { code, stderr } = await spawnPromisified( - execPath, - [ - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-strip-xxx.mjs'), - '--loader', - 'xxx/loader-resolve-strip-yyy.mjs', - ...commonArgs, - ], - { encoding: 'utf8', cwd: fixtures.path('es-module-loaders') }, - ); - - assert.strictEqual(stderr, ''); - assert.strictEqual(code, 0); - }); - - it('should throw when the resolve chain is broken', async () => { - const { code, stderr, stdout } = await spawnPromisified( - execPath, - [ - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-passthru.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-incomplete.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-load-foo-or-42.mjs'), - ...commonArgs, - ], - { encoding: 'utf8' }, - ); - - assert.doesNotMatch(stdout, /resolve passthru/); - assert.match(stderr, /ERR_LOADER_CHAIN_INCOMPLETE/); - assert.match(stderr, /loader-resolve-incomplete\.mjs/); - assert.match(stderr, /'resolve'/); - assert.strictEqual(code, 1); - }); - - it('should throw for incomplete load chain, citing errant loader & hook', async () => { - const { code, stderr, stdout } = await spawnPromisified( - execPath, - [ - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-passthru.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-load-incomplete.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-load-passthru.mjs'), - ...commonArgs, - ], - { encoding: 'utf8' }, - ); - - assert.match(stdout, /load passthru/); - assert.match(stderr, /ERR_LOADER_CHAIN_INCOMPLETE/); - assert.match(stderr, /loader-load-incomplete\.mjs/); - assert.match(stderr, /'load'/); - assert.strictEqual(code, 1); - }); - - it('should throw when the load chain is broken', async () => { - const { code, stderr, stdout } = await spawnPromisified( - execPath, - [ - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-passthru.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-load-passthru.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-load-incomplete.mjs'), - ...commonArgs, - ], - { encoding: 'utf8' }, - ); - - assert.doesNotMatch(stdout, /load passthru/); - assert.match(stderr, /ERR_LOADER_CHAIN_INCOMPLETE/); - assert.match(stderr, /loader-load-incomplete\.mjs/); - assert.match(stderr, /'load'/); - assert.strictEqual(code, 1); - }); - - it('should throw when invalid `specifier` argument passed to `nextResolve`', async () => { - const { code, stderr } = await spawnPromisified( - execPath, - [ - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-passthru.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-bad-next-specifier.mjs'), - ...commonArgs, - ], - { encoding: 'utf8' }, - ); - - assert.strictEqual(code, 1); - assert.match(stderr, /ERR_INVALID_ARG_TYPE/); - assert.match(stderr, /loader-resolve-bad-next-specifier\.mjs/); - assert.match(stderr, /'resolve' hook's nextResolve\(\) specifier/); - }); - - it('should throw when resolve hook is invalid', async () => { - const { code, stderr } = await spawnPromisified( - execPath, - [ - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-passthru.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-null-return.mjs'), - ...commonArgs, - ], - { encoding: 'utf8' }, - ); - - assert.strictEqual(code, 1); - assert.match(stderr, /ERR_INVALID_RETURN_VALUE/); - assert.match(stderr, /loader-resolve-null-return\.mjs/); - assert.match(stderr, /'resolve' hook's nextResolve\(\)/); - assert.match(stderr, /an object/); - assert.match(stderr, /got null/); - }); - - it('should throw when invalid `context` argument passed to `nextResolve`', async () => { - const { code, stderr } = await spawnPromisified( - execPath, - [ - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-passthru.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-bad-next-context.mjs'), - ...commonArgs, - ], - { encoding: 'utf8' }, - ); - - assert.match(stderr, /ERR_INVALID_ARG_TYPE/); - assert.match(stderr, /loader-resolve-bad-next-context\.mjs/); - assert.match(stderr, /'resolve' hook's nextResolve\(\) context/); - assert.strictEqual(code, 1); - }); - - it('should throw when load hook is invalid', async () => { - const { code, stderr } = await spawnPromisified( - execPath, - [ - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-load-passthru.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-load-null-return.mjs'), - ...commonArgs, - ], - { encoding: 'utf8' }, - ); - - assert.strictEqual(code, 1); - assert.match(stderr, /ERR_INVALID_RETURN_VALUE/); - assert.match(stderr, /loader-load-null-return\.mjs/); - assert.match(stderr, /'load' hook's nextLoad\(\)/); - assert.match(stderr, /an object/); - assert.match(stderr, /got null/); - }); - - it('should throw when invalid `url` argument passed to `nextLoad`', async () => { - const { code, stderr } = await spawnPromisified( - execPath, - [ - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-load-passthru.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-load-bad-next-url.mjs'), - ...commonArgs, - ], - { encoding: 'utf8' }, - ); - - assert.match(stderr, /ERR_INVALID_ARG_TYPE/); - assert.match(stderr, /loader-load-bad-next-url\.mjs/); - assert.match(stderr, /'load' hook's nextLoad\(\) url/); - assert.strictEqual(code, 1); - }); - - it('should throw when invalid `url` argument passed to `nextLoad`', async () => { - const { code, stderr } = await spawnPromisified( - execPath, - [ - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-load-passthru.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-load-impersonating-next-url.mjs'), - ...commonArgs, - ], - { encoding: 'utf8' }, - ); - - assert.match(stderr, /ERR_INVALID_ARG_VALUE/); - assert.match(stderr, /loader-load-impersonating-next-url\.mjs/); - assert.match(stderr, /'load' hook's nextLoad\(\) url/); - assert.strictEqual(code, 1); - }); - - it('should throw when invalid `context` argument passed to `nextLoad`', async () => { - const { code, stderr } = await spawnPromisified( - execPath, - [ - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-load-passthru.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-load-bad-next-context.mjs'), - ...commonArgs, - ], - { encoding: 'utf8' }, - ); - assert.match(stderr, /ERR_INVALID_ARG_TYPE/); - assert.match(stderr, /loader-load-bad-next-context\.mjs/); - assert.match(stderr, /'load' hook's nextLoad\(\) context/); - assert.strictEqual(code, 1); - }); - - it('should allow loaders to influence subsequent loader `import()` calls in `resolve`', async () => { - const { code, stderr, stdout } = await spawnPromisified( - execPath, - [ - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-strip-xxx.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-dynamic-import.mjs'), - ...commonArgs, - ], - { encoding: 'utf8' }, - ); - assert.strictEqual(stderr, ''); - assert.match(stdout, /resolve dynamic import/); // It did go thru resolve-dynamic - assert.strictEqual(code, 0); - }); - - it('should allow loaders to influence subsequent loader `import()` calls in `load`', async () => { - const { code, stderr, stdout } = await spawnPromisified( - execPath, - [ - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-strip-xxx.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-load-dynamic-import.mjs'), - ...commonArgs, - ], - { encoding: 'utf8' }, - ); - assert.strictEqual(stderr, ''); - assert.match(stdout, /load dynamic import/); // It did go thru load-dynamic - assert.strictEqual(code, 0); - }); -}); diff --git a/test/es-module/test-esm-loader-custom-condition.mjs b/test/es-module/test-esm-loader-custom-condition.mjs deleted file mode 100644 index 075e4fe2b660..000000000000 --- a/test/es-module/test-esm-loader-custom-condition.mjs +++ /dev/null @@ -1,21 +0,0 @@ -// Flags: --experimental-loader ./test/fixtures/es-module-loaders/loader-with-custom-condition.mjs -import '../common/index.mjs'; -import assert from 'assert'; -import util from 'util'; - -import * as ns from '../fixtures/es-modules/conditional-exports.mjs'; - -assert.deepStrictEqual({ ...ns }, { default: 'from custom condition' }); - -assert.strictEqual( - util.inspect(ns, { showHidden: false }), - "[Module: null prototype] { default: 'from custom condition' }" -); - -assert.strictEqual( - util.inspect(ns, { showHidden: true }), - '[Module: null prototype] {\n' + - " default: 'from custom condition',\n" + - " [Symbol(Symbol.toStringTag)]: 'Module'\n" + - '}' -); diff --git a/test/es-module/test-esm-loader-default-resolver.mjs b/test/es-module/test-esm-loader-default-resolver.mjs index 2a69010e0504..9f836aa56e0f 100644 --- a/test/es-module/test-esm-loader-default-resolver.mjs +++ b/test/es-module/test-esm-loader-default-resolver.mjs @@ -5,51 +5,6 @@ import { execPath } from 'node:process'; import { describe, it } from 'node:test'; describe('default resolver', () => { - // In these tests `byop` is an acronym for "bring your own protocol", and is the - // protocol our byop-dummy-loader.mjs can load - it('should accept foreign schemas without exception (e.g. byop://something/or-other)', async () => { - const { code, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('/es-module-loaders/byop-dummy-loader.mjs'), - '--input-type=module', - '--eval', - "import 'byop://1/index.mjs'", - ]); - assert.strictEqual(code, 0); - assert.strictEqual(stdout.trim(), 'index.mjs!'); - assert.strictEqual(stderr, ''); - }); - - it('should resolve foreign schemas by doing regular url absolutization', async () => { - const { code, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('/es-module-loaders/byop-dummy-loader.mjs'), - '--input-type=module', - '--eval', - "import 'byop://1/index2.mjs'", - ]); - assert.strictEqual(code, 0); - assert.strictEqual(stdout.trim(), '42'); - assert.strictEqual(stderr, ''); - }); - - // In this test, `byoe` is an acronym for "bring your own extension" - it('should accept foreign extensions without exception (e.g. ..//something.byoe)', async () => { - const { code, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('/es-module-loaders/byop-dummy-loader.mjs'), - '--input-type=module', - '--eval', - "import 'byop://1/index.byoe'", - ]); - assert.strictEqual(code, 0); - assert.strictEqual(stdout.trim(), 'index.byoe!'); - assert.strictEqual(stderr, ''); - }); - it('should identify the parent module of an invalid URL host in import specifier', async () => { if (process.platform === 'win32') return; diff --git a/test/es-module/test-esm-loader-dependency.mjs b/test/es-module/test-esm-loader-dependency.mjs deleted file mode 100644 index 54327b478bf4..000000000000 --- a/test/es-module/test-esm-loader-dependency.mjs +++ /dev/null @@ -1,5 +0,0 @@ -// Flags: --experimental-loader ./test/fixtures/es-module-loaders/loader-with-dep.mjs -/* eslint-disable node-core/require-common-first, node-core/required-modules */ -import '../fixtures/es-modules/test-esm-ok.mjs'; - -// We just test that this module doesn't fail loading diff --git a/test/es-module/test-esm-loader-event-loop.mjs b/test/es-module/test-esm-loader-event-loop.mjs deleted file mode 100644 index d94e55148d85..000000000000 --- a/test/es-module/test-esm-loader-event-loop.mjs +++ /dev/null @@ -1,12 +0,0 @@ -// Flags: --experimental-loader ./test/fixtures/es-module-loaders/hooks-custom.mjs -import { mustCall } from '../common/index.mjs'; - -const done = mustCall(); - - -// Test that the process doesn't exit because of a caught exception thrown as part of dynamic import(). -for (let i = 0; i < 10; i++) { - await import('nonexistent/file.mjs').catch(() => {}); -} - -done(); diff --git a/test/es-module/test-esm-loader-http-imports.mjs b/test/es-module/test-esm-loader-http-imports.mjs deleted file mode 100644 index 96bfffe5b13f..000000000000 --- a/test/es-module/test-esm-loader-http-imports.mjs +++ /dev/null @@ -1,91 +0,0 @@ -import { spawnPromisified } from '../common/index.mjs'; -import fixtures from '../common/fixtures.js'; -import assert from 'node:assert'; -import http from 'node:http'; -import path from 'node:path'; -import { execPath } from 'node:process'; -import { promisify } from 'node:util'; -import { describe, it, after } from 'node:test'; - - -const files = { - 'main.mjs': 'export * from "./lib.mjs";', - 'lib.mjs': 'export { sum } from "./sum.mjs";', - 'sum.mjs': 'export function sum(a, b) { return a + b }', - 'console.mjs': ` - import { sum } from './sum.mjs'; - globalThis.sum = sum; - console.log("loaded over http"); - `, -}; - -const requestListener = ({ url }, rsp) => { - const filename = path.basename(url); - const content = files[filename]; - - if (content) { - return rsp - .writeHead(200, { 'Content-Type': 'application/javascript' }) - .end(content); - } - - return rsp - .writeHead(404) - .end(); -}; - -// Leave closing idle connections to the client, so that the loader never -// reuses a keep-alive socket the server has just closed. -const server = http.createServer({ keepAliveTimeout: 0 }, requestListener); - -await promisify(server.listen.bind(server))({ - host: '127.0.0.1', - port: 0, -}); - -const { - address: host, - port, -} = server.address(); - -describe('ESM: http import via loader', { concurrency: !process.env.TEST_PARALLEL }, () => { - it('should load using --import flag', async () => { - // ! MUST NOT use spawnSync to avoid blocking the event loop - const { code, signal, stderr, stdout } = await spawnPromisified( - execPath, - [ - '--no-warnings', - '--loader', - fixtures.fileURL('es-module-loaders', 'http-loader.mjs'), - '--import', `http://${host}:${port}/console.mjs`, - '--eval', - 'console.log(sum(1, 2))', - ] - ); - assert.strictEqual(stderr, ''); - assert.match(stdout, /loaded over http\r?\n3/); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - it('should load using import inside --eval code', async () => { - // ! MUST NOT use spawnSync to avoid blocking the event loop - const { code, signal, stderr, stdout } = await spawnPromisified( - execPath, - [ - '--no-warnings', - '--loader', - fixtures.fileURL('es-module-loaders', 'http-loader.mjs'), - '--input-type=module', - '--eval', - `import * as main from 'http://${host}:${port}/main.mjs'; console.log(main)`, - ] - ); - - assert.strictEqual(stderr, ''); - assert.strictEqual(stdout, '[Module: null prototype] { sum: [Function: sum] }\n'); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - - after(() => server.close()); -}); diff --git a/test/es-module/test-esm-loader-invalid-format.mjs b/test/es-module/test-esm-loader-invalid-format.mjs deleted file mode 100644 index 5a16381e5889..000000000000 --- a/test/es-module/test-esm-loader-invalid-format.mjs +++ /dev/null @@ -1,10 +0,0 @@ -// Flags: --experimental-loader ./test/fixtures/es-module-loaders/loader-invalid-format.mjs -import { expectsError, mustCall } from '../common/index.mjs'; -import assert from 'assert'; - -import('../fixtures/es-modules/test-esm-ok.mjs') -.then(assert.fail, expectsError({ - code: 'ERR_UNKNOWN_MODULE_FORMAT', - message: /Unknown module format: esm/, -})) -.then(mustCall()); diff --git a/test/es-module/test-esm-loader-invalid-url.mjs b/test/es-module/test-esm-loader-invalid-url.mjs deleted file mode 100644 index 1ba7c621f7e9..000000000000 --- a/test/es-module/test-esm-loader-invalid-url.mjs +++ /dev/null @@ -1,10 +0,0 @@ -// Flags: --experimental-loader ./test/fixtures/es-module-loaders/loader-invalid-url.mjs -import { expectsError, mustCall } from '../common/index.mjs'; -import assert from 'assert'; - -import('../fixtures/es-modules/test-esm-ok.mjs') -.then(assert.fail, (error) => { - expectsError({ code: 'ERR_INVALID_RETURN_PROPERTY_VALUE' })(error); - assert.match(error.message, /loader-invalid-url\.mjs/); -}) -.then(mustCall()); diff --git a/test/es-module/test-esm-loader-mock.mjs b/test/es-module/test-esm-loader-mock.mjs deleted file mode 100644 index cdf56d85415b..000000000000 --- a/test/es-module/test-esm-loader-mock.mjs +++ /dev/null @@ -1,42 +0,0 @@ -import '../common/index.mjs'; -import assert from 'node:assert/strict'; -import { mock } from '../fixtures/es-module-loaders/mock.mjs'; - -mock('node:events', { - EventEmitter: 'This is mocked!', -}); - -// This resolves to node:events -// It is intercepted by mock-loader and doesn't return the normal value -assert.deepStrictEqual(await import('events'), Object.defineProperty({ - __proto__: null, - EventEmitter: 'This is mocked!', -}, Symbol.toStringTag, { - enumerable: false, - value: 'Module', -})); - -const mutator = mock('node:events', { - EventEmitter: 'This is mocked v2!', -}); - -// It is intercepted by mock-loader and doesn't return the normal value. -// This is resolved separately from the import above since the specifiers -// are different. -const mockedV2 = await import('node:events'); -assert.deepStrictEqual(mockedV2, Object.defineProperty({ - __proto__: null, - EventEmitter: 'This is mocked v2!', -}, Symbol.toStringTag, { - enumerable: false, - value: 'Module', -})); - -mutator.EventEmitter = 'This is mocked v3!'; -assert.deepStrictEqual(mockedV2, Object.defineProperty({ - __proto__: null, - EventEmitter: 'This is mocked v3!', -}, Symbol.toStringTag, { - enumerable: false, - value: 'Module', -})); diff --git a/test/es-module/test-esm-loader-modulemap.js b/test/es-module/test-esm-loader-modulemap.js index ba0358368ec1..6dada059c4af 100644 --- a/test/es-module/test-esm-loader-modulemap.js +++ b/test/es-module/test-esm-loader-modulemap.js @@ -4,7 +4,7 @@ require('../common'); const assert = require('assert'); -const { createModuleLoader } = require('internal/modules/esm/loader'); +const { ModuleLoader } = require('internal/modules/esm/loader'); const { LoadCache, ResolveCache } = require('internal/modules/esm/module_map'); const { ModuleJob } = require('internal/modules/esm/module_job'); const createDynamicModule = require( @@ -16,7 +16,7 @@ const jsonModuleDataUrl = 'data:application/json,""'; const stubJsModule = createDynamicModule([], ['default'], jsModuleDataUrl); const stubJsonModule = createDynamicModule([], ['default'], jsonModuleDataUrl); -const loader = createModuleLoader(); +const loader = new ModuleLoader(); const jsModuleJob = new ModuleJob(loader, jsModuleDataUrl, {}, stubJsModule.module); const jsonModuleJob = new ModuleJob(loader, jsonModuleDataUrl, { type: 'json' }, stubJsonModule.module); diff --git a/test/es-module/test-esm-loader-not-found.mjs b/test/es-module/test-esm-loader-not-found.mjs deleted file mode 100644 index 2abaf3078d11..000000000000 --- a/test/es-module/test-esm-loader-not-found.mjs +++ /dev/null @@ -1,24 +0,0 @@ -import { spawnPromisified } from '../common/index.mjs'; -import * as fixtures from '../common/fixtures.mjs'; -import assert from 'node:assert'; -import { execPath } from 'node:process'; -import { describe, it } from 'node:test'; - - -describe('ESM: nonexistent loader', () => { - it('should throw', async () => { - const { code, stderr } = await spawnPromisified(execPath, [ - '--experimental-loader', - 'i-dont-exist', - fixtures.path('print-error-message.js'), - ]); - - assert.notStrictEqual(code, 0); - - // Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'i-dont-exist' imported from - assert.match(stderr, /ERR_MODULE_NOT_FOUND/); - assert.match(stderr, /'i-dont-exist'/); - - assert.ok(!stderr.includes('Bad command or file name')); - }); -}); diff --git a/test/es-module/test-esm-loader-programmatically.mjs b/test/es-module/test-esm-loader-programmatically.mjs deleted file mode 100644 index dd25e3f2430e..000000000000 --- a/test/es-module/test-esm-loader-programmatically.mjs +++ /dev/null @@ -1,251 +0,0 @@ -import { spawnPromisified } from '../common/index.mjs'; -import * as fixtures from '../common/fixtures.mjs'; -import assert from 'node:assert'; -import { execPath } from 'node:process'; -import { describe, it } from 'node:test'; - -// This test ensures that the register function can register loaders -// programmatically. - -const commonArgs = [ - '--no-warnings', - '--input-type=module', - '--loader=data:text/javascript,', -]; - -const commonEvals = { - import: (module) => `await import(${JSON.stringify(module)});`, - register: (loader, parentURL = 'file:///') => `register(${JSON.stringify(loader)}, ${JSON.stringify(parentURL)});`, - dynamicImport: (module) => `await import(${JSON.stringify(`data:text/javascript,${encodeURIComponent(module)}`)});`, - staticImport: (module) => `import ${JSON.stringify(`data:text/javascript,${encodeURIComponent(module)}`)};`, -}; - -describe('ESM: programmatically register loaders', { concurrency: !process.env.TEST_PARALLEL }, () => { - it('works with only a dummy CLI argument', async () => { - const parentURL = fixtures.fileURL('es-module-loaders', 'loader-resolve-passthru.mjs'); - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - ...commonArgs, - '--eval', - "import { register } from 'node:module';" + - commonEvals.register(fixtures.fileURL('es-module-loaders', 'loader-resolve-passthru.mjs')) + - commonEvals.register(fixtures.fileURL('es-module-loaders', 'loader-load-passthru.mjs')) + - `register(${JSON.stringify('./loader-resolve-passthru.mjs')}, ${JSON.stringify({ parentURL })});` + - `register(${JSON.stringify('./loader-load-passthru.mjs')}, ${JSON.stringify({ parentURL })});` + - commonEvals.dynamicImport('console.log("Hello from dynamic import");'), - ]); - - assert.strictEqual(stderr, ''); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - - const lines = stdout.split('\n'); - - assert.match(lines[0], /resolve passthru/); - assert.match(lines[1], /resolve passthru/); - assert.match(lines[2], /load passthru/); - assert.match(lines[3], /load passthru/); - assert.match(lines[4], /Hello from dynamic import/); - - assert.strictEqual(lines[5], ''); - }); - - describe('registering via --import', { concurrency: !process.env.TEST_PARALLEL }, () => { - for (const moduleType of ['mjs', 'cjs']) { - it(`should programmatically register a loader from a ${moduleType.toUpperCase()} file`, async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - ...commonArgs, - '--import', fixtures.fileURL('es-module-loaders', `register-loader.${moduleType}`).href, - '--eval', commonEvals.staticImport('console.log("entry point")'), - ]); - - assert.strictEqual(stderr, ''); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - - const [ - passthruStdout, - entryPointStdout, - ] = stdout.split('\n'); - - assert.match(passthruStdout, /resolve passthru/); - assert.match(entryPointStdout, /entry point/); - }); - } - }); - - it('programmatically registered loaders are appended to an existing chaining', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - ...commonArgs, - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-passthru.mjs'), - '--eval', - "import { register } from 'node:module';" + - commonEvals.register(fixtures.fileURL('es-module-loaders', 'loader-load-passthru.mjs')) + - commonEvals.dynamicImport('console.log("Hello from dynamic import");'), - ]); - - assert.strictEqual(stderr, ''); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - - const lines = stdout.split('\n'); - - assert.match(lines[0], /resolve passthru/); - assert.match(lines[1], /resolve passthru/); - assert.match(lines[2], /load passthru/); - assert.match(lines[3], /Hello from dynamic import/); - - assert.strictEqual(lines[4], ''); - }); - - it('works registering loaders across files', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - ...commonArgs, - '--eval', - commonEvals.import(fixtures.fileURL('es-module-loaders', 'register-programmatically-loader-load.mjs')) + - commonEvals.import(fixtures.fileURL('es-module-loaders', 'register-programmatically-loader-resolve.mjs')) + - commonEvals.dynamicImport('console.log("Hello from dynamic import");'), - ]); - - assert.strictEqual(stderr, ''); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - - const lines = stdout.split('\n'); - - assert.match(lines[0], /resolve passthru/); - assert.match(lines[1], /load passthru/); - assert.match(lines[2], /Hello from dynamic import/); - - assert.strictEqual(lines[3], ''); - }); - - it('works registering loaders across virtual files', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - ...commonArgs, - '--eval', - commonEvals.import(fixtures.fileURL('es-module-loaders', 'register-programmatically-loader-load.mjs')) + - commonEvals.dynamicImport( - commonEvals.import(fixtures.fileURL('es-module-loaders', 'register-programmatically-loader-resolve.mjs')) + - commonEvals.dynamicImport('console.log("Hello from dynamic import");'), - ), - ]); - - assert.strictEqual(stderr, ''); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - - const lines = stdout.split('\n'); - - assert.match(lines[0], /resolve passthru/); - assert.match(lines[1], /load passthru/); - assert.match(lines[2], /Hello from dynamic import/); - - assert.strictEqual(lines[3], ''); - }); - - it('works registering the same loaders more them once', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - ...commonArgs, - '--eval', - "import { register } from 'node:module';" + - commonEvals.register(fixtures.fileURL('es-module-loaders', 'loader-resolve-passthru.mjs')) + - commonEvals.register(fixtures.fileURL('es-module-loaders', 'loader-load-passthru.mjs')) + - commonEvals.register(fixtures.fileURL('es-module-loaders', 'loader-resolve-passthru.mjs')) + - commonEvals.register(fixtures.fileURL('es-module-loaders', 'loader-load-passthru.mjs')) + - commonEvals.dynamicImport('console.log("Hello from dynamic import");'), - ]); - - assert.strictEqual(stderr, ''); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - - const lines = stdout.split('\n'); - - assert.match(lines[0], /resolve passthru/); - assert.match(lines[1], /resolve passthru/); - assert.match(lines[2], /load passthru/); - assert.match(lines[3], /load passthru/); - assert.match(lines[4], /Hello from dynamic import/); - - assert.strictEqual(lines[5], ''); - }); - - it('works registering loaders as package name', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - ...commonArgs, - '--eval', - "import { register } from 'node:module';" + - commonEvals.register('resolve', fixtures.fileURL('es-module-loaders', 'package.json')) + - commonEvals.register('load', fixtures.fileURL('es-module-loaders', 'package.json')) + - commonEvals.dynamicImport('console.log("Hello from dynamic import");'), - ]); - - assert.strictEqual(stderr, ''); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - - const lines = stdout.split('\n'); - - // Resolve occurs twice because it is first used to resolve the `load` loader - // _AND THEN_ the `register` module. - assert.match(lines[0], /resolve passthru/); - assert.match(lines[1], /resolve passthru/); - assert.match(lines[2], /load passthru/); - assert.match(lines[3], /Hello from dynamic import/); - - assert.strictEqual(lines[4], ''); - }); - - it('works without a CLI flag', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--input-type=module', - '--eval', - "import { register } from 'node:module';" + - commonEvals.register(fixtures.fileURL('es-module-loaders', 'loader-load-passthru.mjs')) + - commonEvals.dynamicImport('console.log("Hello from dynamic import");'), - ]); - - assert.strictEqual(stderr, ''); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - - const lines = stdout.split('\n'); - - assert.match(lines[0], /load passthru/); - assert.match(lines[1], /Hello from dynamic import/); - - assert.strictEqual(lines[2], ''); - }); - - it('does not work with a loader specifier that does not exist', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - ...commonArgs, - '--eval', - "import { register } from 'node:module';" + - commonEvals.register('./not-found.mjs', import.meta.url) + - commonEvals.dynamicImport('console.log("Hello from dynamic import");'), - ]); - - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 1); - assert.strictEqual(signal, null); - assert.match(stderr, /ERR_MODULE_NOT_FOUND/); - }); - - it('does not work with a loader that got syntax errors', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - ...commonArgs, - '--eval', - "import { register } from 'node:module';" + - commonEvals.register(fixtures.fileURL('es-module-loaders', 'syntax-error.mjs')) + - commonEvals.dynamicImport('console.log("Hello from dynamic import");'), - ]); - - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 1); - assert.strictEqual(signal, null); - assert.match(stderr, /SyntaxError/); - }); -}); diff --git a/test/es-module/test-esm-loader-resolve-type.mjs b/test/es-module/test-esm-loader-resolve-type.mjs deleted file mode 100644 index 7dcc65a10c09..000000000000 --- a/test/es-module/test-esm-loader-resolve-type.mjs +++ /dev/null @@ -1,48 +0,0 @@ -import { spawnPromisified } from '../common/index.mjs'; -import * as tmpdir from '../common/tmpdir.js'; -import * as fixtures from '../common/fixtures.mjs'; -import assert from 'node:assert'; -import { mkdir, rm, cp } from 'node:fs/promises'; -import { execPath } from 'node:process'; - -tmpdir.refresh(); - -const base = tmpdir.fileURL(`test-esm-loader-resolve-type-${(Math.random() * Date.now()).toFixed(0)}`); -const moduleName = 'module-counter-by-type'; -const moduleURL = new URL(`${base}/node_modules/${moduleName}`); -try { - await mkdir(moduleURL, { recursive: true }); - await cp( - fixtures.path('es-modules', 'module-counter-by-type'), - moduleURL, - { recursive: true } - ); - - const output = await spawnPromisified( - execPath, - [ - '--no-warnings', - '--input-type=module', - '--eval', - `import { getModuleTypeStats } from ${JSON.stringify(fixtures.fileURL('es-module-loaders', 'hook-resolve-type.mjs'))}; - const before = getModuleTypeStats(); - await import(${JSON.stringify(moduleName)}); - const after = getModuleTypeStats(); - console.log(JSON.stringify({ before, after }));`, - ], - { cwd: base }, - ); - - assert.deepStrictEqual(output, { - code: 0, - signal: null, - stderr: '', - stdout: JSON.stringify({ - before: { importedESM: 0, importedCJS: 0 }, - // Dynamic import in the eval script should increment ESM counter but not CJS counter - after: { importedESM: 1, importedCJS: 0 }, - }) + '\n', - }); -} finally { - await rm(base, { recursive: true, force: true }); -} diff --git a/test/es-module/test-esm-loader-spawn-promisified.mjs b/test/es-module/test-esm-loader-spawn-promisified.mjs deleted file mode 100644 index 2f27f7850f64..000000000000 --- a/test/es-module/test-esm-loader-spawn-promisified.mjs +++ /dev/null @@ -1,304 +0,0 @@ -import { spawnPromisified } from '../common/index.mjs'; -import * as fixtures from '../common/fixtures.mjs'; -import assert from 'node:assert'; -import { execPath } from 'node:process'; -import { describe, it } from 'node:test'; - -describe('Loader hooks throwing errors', { concurrency: !process.env.TEST_PARALLEL }, () => { - it('throws on nonexistent modules', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('/es-module-loaders/hooks-custom.mjs'), - '--input-type=module', - '--eval', - 'import "nonexistent/file.mjs"', - ]); - - assert.match(stderr, /ERR_MODULE_NOT_FOUND/); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 1); - assert.strictEqual(signal, null); - }); - - it('throws on unknown extensions', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('/es-module-loaders/hooks-custom.mjs'), - '--input-type=module', - '--eval', - `import ${JSON.stringify(fixtures.fileURL('/es-modules/file.unknown'))}`, - ]); - - assert.match(stderr, /ERR_UNKNOWN_FILE_EXTENSION/); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 1); - assert.strictEqual(signal, null); - }); - - it('throws on invalid return values', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('/es-module-loaders/hooks-custom.mjs'), - '--input-type=module', - '--eval', - 'import "esmHook/badReturnVal.mjs"', - ]); - - assert.match(stderr, /ERR_INVALID_RETURN_VALUE/); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 1); - assert.strictEqual(signal, null); - }); - - it('throws on boolean false', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('/es-module-loaders/hooks-custom.mjs'), - '--input-type=module', - '--eval', - 'import "esmHook/format.false"', - ]); - - assert.match(stderr, /ERR_INVALID_RETURN_PROPERTY_VALUE/); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 1); - assert.strictEqual(signal, null); - }); - it('throws on boolean true', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('/es-module-loaders/hooks-custom.mjs'), - '--input-type=module', - '--eval', - 'import "esmHook/format.true"', - ]); - - assert.match(stderr, /ERR_INVALID_RETURN_PROPERTY_VALUE/); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 1); - assert.strictEqual(signal, null); - }); - - it('throws on invalid returned object', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('/es-module-loaders/hooks-custom.mjs'), - '--input-type=module', - '--eval', - 'import "esmHook/badReturnFormatVal.mjs"', - ]); - - assert.match(stderr, /ERR_INVALID_RETURN_PROPERTY_VALUE/); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 1); - assert.strictEqual(signal, null); - }); - - it('throws on unsupported format', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('/es-module-loaders/hooks-custom.mjs'), - '--input-type=module', - '--eval', - 'import "esmHook/unsupportedReturnFormatVal.mjs"', - ]); - - assert.match(stderr, /ERR_UNKNOWN_MODULE_FORMAT/); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 1); - assert.strictEqual(signal, null); - }); - - it('throws on invalid format property type', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('/es-module-loaders/hooks-custom.mjs'), - '--input-type=module', - '--eval', - 'import "esmHook/badReturnSourceVal.mjs"', - ]); - - assert.match(stderr, /ERR_INVALID_RETURN_PROPERTY_VALUE/); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 1); - assert.strictEqual(signal, null); - }); - - it('rejects dynamic imports for all of the error cases checked above', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('/es-module-loaders/hooks-custom.mjs'), - '--input-type=module', - '--eval', - `import assert from 'node:assert'; - await Promise.allSettled([ - import('nonexistent/file.mjs'), - import(${JSON.stringify(fixtures.fileURL('/es-modules/file.unknown'))}), - import('esmHook/badReturnVal.mjs'), - import('esmHook/format.false'), - import('esmHook/format.true'), - import('esmHook/badReturnFormatVal.mjs'), - import('esmHook/unsupportedReturnFormatVal.mjs'), - import('esmHook/badReturnSourceVal.mjs'), - ]).then((results) => { - assert.strictEqual(results.every((result) => result.status === 'rejected'), true); - })`, - ]); - - assert.strictEqual(stderr, ''); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); -}); - -describe('Loader hooks parsing modules', { concurrency: !process.env.TEST_PARALLEL }, () => { - it('can parse .js files as ESM', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('/es-module-loaders/hooks-custom.mjs'), - '--input-type=module', - '--eval', - `import assert from 'node:assert'; - await import(${JSON.stringify(fixtures.fileURL('/es-module-loaders/js-as-esm.js'))}) - .then((parsedModule) => { - assert.strictEqual(typeof parsedModule, 'object'); - assert.strictEqual(parsedModule.namedExport, 'named-export'); - })`, - ]); - - assert.strictEqual(stderr, ''); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - - it('can define .ext files as ESM', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('/es-module-loaders/hooks-custom.mjs'), - '--input-type=module', - '--eval', - `import assert from 'node:assert'; - await import(${JSON.stringify(fixtures.fileURL('/es-modules/file.ext'))}) - .then((parsedModule) => { - assert.strictEqual(typeof parsedModule, 'object'); - const { default: defaultExport } = parsedModule; - assert.strictEqual(typeof defaultExport, 'function'); - assert.strictEqual(defaultExport.name, 'iAmReal'); - assert.strictEqual(defaultExport(), true); - })`, - ]); - - assert.strictEqual(stderr, ''); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - - it('can predetermine the format in the custom loader resolve hook', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('/es-module-loaders/hooks-custom.mjs'), - '--input-type=module', - '--eval', - `import assert from 'node:assert'; - await import('esmHook/preknownFormat.pre') - .then((parsedModule) => { - assert.strictEqual(typeof parsedModule, 'object'); - assert.strictEqual(parsedModule.default, 'hello world'); - })`, - ]); - - assert.strictEqual(stderr, ''); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - - it('can provide source for a nonexistent file', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('/es-module-loaders/hooks-custom.mjs'), - '--input-type=module', - '--eval', - `import assert from 'node:assert'; - await import('esmHook/virtual.mjs') - .then((parsedModule) => { - assert.strictEqual(typeof parsedModule, 'object'); - assert.strictEqual(typeof parsedModule.default, 'undefined'); - assert.strictEqual(parsedModule.message, 'WOOHOO!'); - })`, - ]); - - assert.strictEqual(stderr, ''); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - - it('ensures that loaders have a separate context from userland', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('/es-module-loaders/hooks-custom.mjs'), - '--input-type=module', - '--eval', - `import assert from 'node:assert'; - await import(${JSON.stringify(fixtures.fileURL('/es-modules/stateful.mjs'))}) - .then(({ default: count }) => { - assert.strictEqual(count(), 1); - });`, - ]); - - assert.strictEqual(stderr, ''); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - - it('ensures that user loaders are not bound to the internal loader', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('/es-module-loaders/loader-this-value-inside-hook-functions.mjs'), - '--input-type=module', - '--eval', - ';', // Actual test is inside the loader module. - ]); - - assert.strictEqual(stderr, ''); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - - it('throw maximum call stack error on the loader', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('/es-module-loaders/hooks-custom.mjs'), - '--input-type=module', - '--eval', - 'await import("esmHook/maximumCallStack.mjs")', - ]); - - assert(stderr.includes('Maximum call stack size exceeded')); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 1); - assert.strictEqual(signal, null); - }); -}); diff --git a/test/es-module/test-esm-loader-stringify-text.mjs b/test/es-module/test-esm-loader-stringify-text.mjs deleted file mode 100644 index 8502375b31aa..000000000000 --- a/test/es-module/test-esm-loader-stringify-text.mjs +++ /dev/null @@ -1,66 +0,0 @@ -// Flags: --experimental-loader ./test/fixtures/es-module-loaders/string-sources.mjs -import { mustCall } from '../common/index.mjs'; -import assert from 'assert'; - -assert.rejects( - import('test:Array'), - { code: 'ERR_INVALID_RETURN_PROPERTY_VALUE' }, -).then(mustCall()); -import('test:ArrayBuffer').then( - mustCall(), -); -import('test:BigInt64Array').then( - mustCall(), -); -import('test:BigUint64Array').then( - mustCall(), -); -import('test:Float32Array').then( - mustCall(), -); -import('test:Float64Array').then( - mustCall(), -); -import('test:Int8Array').then( - mustCall(), -); -import('test:Int16Array').then( - mustCall(), -); -import('test:Int32Array').then( - mustCall(), -); -assert.rejects( - import('test:null'), - { code: 'ERR_INVALID_RETURN_PROPERTY_VALUE' }, -).then(mustCall()); -assert.rejects( - import('test:Object'), - { code: 'ERR_INVALID_RETURN_PROPERTY_VALUE' }, -).then(mustCall()); -import('test:SharedArrayBuffer').then( - mustCall(), -); -import('test:string').then( - mustCall(), -); -assert.rejects( - import('test:String'), - { code: 'ERR_INVALID_RETURN_PROPERTY_VALUE' }, -).then(mustCall()); -import('test:Uint8ClampedArray').then( - mustCall(), -); -import('test:Uint16Array').then( - mustCall(), -); -import('test:Uint32Array').then( - mustCall(), -); -import('test:Uint8Array').then( - mustCall(), -); -assert.rejects( - import('test:undefined'), - { code: 'ERR_INVALID_RETURN_PROPERTY_VALUE' }, -).then(mustCall()); diff --git a/test/es-module/test-esm-loader-thenable.mjs b/test/es-module/test-esm-loader-thenable.mjs deleted file mode 100644 index d26ba7d476a5..000000000000 --- a/test/es-module/test-esm-loader-thenable.mjs +++ /dev/null @@ -1,43 +0,0 @@ -import { spawnPromisified } from '../common/index.mjs'; -import * as fixtures from '../common/fixtures.mjs'; -import assert from 'assert'; -import { execPath } from 'node:process'; -import { describe, it } from 'node:test'; - - -describe('ESM: thenable loader hooks', { concurrency: !process.env.TEST_PARALLEL }, () => { - it('should behave as a normal promise resolution', async () => { - const { code, stderr } = await spawnPromisified(execPath, [ - '--experimental-loader', - fixtures.fileURL('es-module-loaders', 'thenable-load-hook.mjs').href, - fixtures.path('es-modules', 'test-esm-ok.mjs'), - ]); - - assert.strictEqual(code, 0); - assert.ok(!stderr.includes('must not call')); - }); - - it('should crash the node process rejection with an error', async () => { - const { code, stderr } = await spawnPromisified(execPath, [ - '--experimental-loader', - fixtures.fileURL('es-module-loaders', 'thenable-load-hook-rejected.mjs').href, - fixtures.path('es-modules', 'test-esm-ok.mjs'), - ]); - - assert.notStrictEqual(code, 0); - assert.match(stderr, /\sError: must crash the process\r?\n/); - assert.ok(!stderr.includes('must not call')); - }); - - it('should just reject without an error (but NOT crash the node process)', async () => { - const { code, stderr } = await spawnPromisified(execPath, [ - '--experimental-loader', - fixtures.fileURL('es-module-loaders', 'thenable-load-hook-rejected-no-arguments.mjs').href, - fixtures.path('es-modules', 'test-esm-ok.mjs'), - ]); - - assert.notStrictEqual(code, 0); - assert.match(stderr, /\sundefined\r?\n/); - assert.ok(!stderr.includes('must not call')); - }); -}); diff --git a/test/es-module/test-esm-loader-with-source.mjs b/test/es-module/test-esm-loader-with-source.mjs deleted file mode 100644 index 41dca12dfc6b..000000000000 --- a/test/es-module/test-esm-loader-with-source.mjs +++ /dev/null @@ -1,10 +0,0 @@ -// Flags: --experimental-loader ./test/fixtures/es-module-loaders/preset-cjs-source.mjs -import '../common/index.mjs'; -import * as fixtures from '../common/fixtures.mjs'; -import assert from 'assert'; - -const { default: existingFileSource } = await import(fixtures.fileURL('es-modules', 'cjs-file.cjs')); -const { default: noSuchFileSource } = await import(new URL('./no-such-file.cjs', import.meta.url)); - -assert.strictEqual(existingFileSource, 'no .cjs file was read to get this source'); -assert.strictEqual(noSuchFileSource, 'no .cjs file was read to get this source'); diff --git a/test/es-module/test-esm-loader-with-syntax-error.mjs b/test/es-module/test-esm-loader-with-syntax-error.mjs deleted file mode 100644 index fe41b293a3fc..000000000000 --- a/test/es-module/test-esm-loader-with-syntax-error.mjs +++ /dev/null @@ -1,20 +0,0 @@ -import { spawnPromisified } from '../common/index.mjs'; -import * as fixtures from '../common/fixtures.mjs'; -import assert from 'node:assert'; -import { execPath } from 'node:process'; -import { describe, it } from 'node:test'; - - -describe('ESM: loader with syntax error', { concurrency: !process.env.TEST_PARALLEL }, () => { - it('should crash the node process', async () => { - const { code, stderr } = await spawnPromisified(execPath, [ - '--experimental-loader', - fixtures.fileURL('es-module-loaders', 'syntax-error.mjs').href, - fixtures.path('print-error-message.js'), - ]); - - assert.match(stderr, /SyntaxError/); - assert.ok(!stderr.includes('Bad command or file name')); - assert.notStrictEqual(code, 0); - }); -}); diff --git a/test/es-module/test-esm-loader.mjs b/test/es-module/test-esm-loader.mjs deleted file mode 100644 index de3481432481..000000000000 --- a/test/es-module/test-esm-loader.mjs +++ /dev/null @@ -1,88 +0,0 @@ -// Flags: --experimental-loader ./test/fixtures/es-module-loaders/hooks-custom.mjs -import '../common/index.mjs'; -import assert from 'assert'; - - -await assert.rejects( - import('nonexistent/file.mjs'), - { code: 'ERR_MODULE_NOT_FOUND' }, -); - -await assert.rejects( - import('../fixtures/es-modules/file.unknown'), - { code: 'ERR_UNKNOWN_FILE_EXTENSION' }, -); - -await assert.rejects( - import('esmHook/badReturnVal.mjs'), - { code: 'ERR_INVALID_RETURN_VALUE' }, -); - -// Nullish values are allowed; booleans are not -await assert.rejects( - import('esmHook/format.false'), - { code: 'ERR_INVALID_RETURN_PROPERTY_VALUE' }, -); -await assert.rejects( - import('esmHook/format.true'), - { code: 'ERR_INVALID_RETURN_PROPERTY_VALUE' }, -); - -await assert.rejects( - import('esmHook/badReturnFormatVal.mjs'), - { code: 'ERR_INVALID_RETURN_PROPERTY_VALUE' }, -); - -await assert.rejects( - import('esmHook/unsupportedReturnFormatVal.mjs'), - { code: 'ERR_UNKNOWN_MODULE_FORMAT' }, -); - -await assert.rejects( - import('esmHook/badReturnSourceVal.mjs'), - { code: 'ERR_INVALID_RETURN_PROPERTY_VALUE' }, -); - -await assert.rejects(import('esmHook/commonJsNullSource.mjs'), { - code: 'ERR_INVALID_RETURN_PROPERTY_VALUE', - message: /"source".*'load'.*got type bigint/, -}); - -await import('../fixtures/es-module-loaders/js-as-esm.js') -.then((parsedModule) => { - assert.strictEqual(typeof parsedModule, 'object'); - assert.strictEqual(parsedModule.namedExport, 'named-export'); -}); - -// The custom loader tells node .ext files are MJS -await import('../fixtures/es-modules/file.ext') -.then((parsedModule) => { - assert.strictEqual(typeof parsedModule, 'object'); - const { default: defaultExport } = parsedModule; - assert.strictEqual(typeof defaultExport, 'function'); - assert.strictEqual(defaultExport.name, 'iAmReal'); - assert.strictEqual(defaultExport(), true); -}); - -// The custom loader's resolve hook predetermines the format -await import('esmHook/preknownFormat.pre') -.then((parsedModule) => { - assert.strictEqual(typeof parsedModule, 'object'); - assert.strictEqual(parsedModule.default, 'hello world'); -}); - -// The custom loader provides source even though file does not actually exist -await import('esmHook/virtual.mjs') -.then((parsedModule) => { - assert.strictEqual(typeof parsedModule, 'object'); - assert.strictEqual(typeof parsedModule.default, 'undefined'); - assert.strictEqual(parsedModule.message, 'WOOHOO!'); -}); - -// Ensure custom loaders have separate context from userland -// hooks-custom.mjs also increments count (which starts at 0) -// if both share context, count here would be 2 -await import('../fixtures/es-modules/stateful.mjs') -.then(({ default: count }) => { - assert.strictEqual(count(), 1); -}); diff --git a/test/es-module/test-esm-named-exports.mjs b/test/es-module/test-esm-named-exports.mjs deleted file mode 100644 index bbe9c96b92d9..000000000000 --- a/test/es-module/test-esm-named-exports.mjs +++ /dev/null @@ -1,9 +0,0 @@ -// Flags: --import ./test/fixtures/es-module-loaders/builtin-named-exports.mjs -import '../common/index.mjs'; -import { readFile, __fromLoader } from 'fs'; -import assert from 'assert'; -import ok from '../fixtures/es-modules/test-esm-ok.mjs'; - -assert(ok); -assert(readFile); -assert(__fromLoader); diff --git a/test/es-module/test-esm-preserve-symlinks-not-found-plain.mjs b/test/es-module/test-esm-preserve-symlinks-not-found-plain.mjs deleted file mode 100644 index 7a538e44c072..000000000000 --- a/test/es-module/test-esm-preserve-symlinks-not-found-plain.mjs +++ /dev/null @@ -1,3 +0,0 @@ -// Flags: --experimental-loader ./test/fixtures/es-module-loaders/not-found-assert-loader.mjs -/* eslint-disable node-core/require-common-first, node-core/required-modules */ -import './not-found.js'; diff --git a/test/es-module/test-esm-preserve-symlinks-not-found.mjs b/test/es-module/test-esm-preserve-symlinks-not-found.mjs deleted file mode 100644 index 722dc467d192..000000000000 --- a/test/es-module/test-esm-preserve-symlinks-not-found.mjs +++ /dev/null @@ -1,3 +0,0 @@ -// Flags: --experimental-loader ./test/fixtures/es-module-loaders/not-found-assert-loader.mjs -/* eslint-disable node-core/require-common-first, node-core/required-modules */ -import './not-found.mjs'; diff --git a/test/es-module/test-esm-register-deprecation.mjs b/test/es-module/test-esm-register-deprecation.mjs deleted file mode 100644 index 171e5971a091..000000000000 --- a/test/es-module/test-esm-register-deprecation.mjs +++ /dev/null @@ -1,60 +0,0 @@ -import { spawnPromisified } from '../common/index.mjs'; -import * as fixtures from '../common/fixtures.mjs'; - -import assert from 'node:assert'; -import { execPath } from 'node:process'; -import { describe, it } from 'node:test'; - -const urlToRegister = fixtures.fileURL('es-module-loaders', 'loader-resolve-passthru.mjs'); -const urlToRegisterEscaped = JSON.stringify(urlToRegister.href); - - -describe('module.register() deprecation (DEP0205)', { concurrency: !process.env.TEST_PARALLEL }, () => { - it('emits DEP0205 when module.register() is called', async () => { - const { code, stderr } = await spawnPromisified(execPath, [ - '--input-type=module', - '--eval', - `import { register } from 'node:module'; register(${urlToRegisterEscaped});`, - ]); - - assert.match(stderr, /\[DEP0205\]/); - assert.strictEqual(code, 0); - }); - - it('only emits the warning once per process', async () => { - const { code, stderr } = await spawnPromisified(execPath, [ - '--input-type=module', - '--eval', - `import { register } from 'node:module'; - register(${urlToRegisterEscaped}); - register(${urlToRegisterEscaped});`, - ]); - - assert.strictEqual(stderr.split('[DEP0205]').length - 1, 1); - assert.strictEqual(code, 0); - }); - - it('does not emit when --no-deprecation is set', async () => { - const { code, stderr } = await spawnPromisified(execPath, [ - '--no-deprecation', - '--input-type=module', - '--eval', - `import { register } from 'node:module'; register(${urlToRegisterEscaped});`, - ]); - - assert.doesNotMatch(stderr, /DEP0205/); - assert.strictEqual(code, 0); - }); - - it('throws when --throw-deprecation is set', async () => { - const { code, stderr } = await spawnPromisified(execPath, [ - '--throw-deprecation', - '--input-type=module', - '--eval', - `import { register } from 'node:module'; register(${urlToRegisterEscaped});`, - ]); - - assert.match(stderr, /DEP0205/); - assert.notStrictEqual(code, 0); - }); -}); diff --git a/test/es-module/test-esm-require-after-failed-import.js b/test/es-module/test-esm-require-after-failed-import.js new file mode 100644 index 000000000000..6815243212f9 --- /dev/null +++ b/test/es-module/test-esm-require-after-failed-import.js @@ -0,0 +1,15 @@ +'use strict'; +// Tests that require() of an ES module whose earlier import() failed during +// instantiation throws ERR_REQUIRE_ESM_RACE_CONDITION. +const common = require('../common'); +const assert = require('node:assert'); +const fixtures = require('../common/fixtures'); + +const url = fixtures.fileURL('es-modules/require-after-failed-import/mismatched.mjs'); + +assert.rejects(import(url), { name: 'SyntaxError' }).then(common.mustCall(() => { + assert.throws( + () => require(fixtures.path('es-modules/require-after-failed-import/mismatched.mjs')), + { code: 'ERR_REQUIRE_ESM_RACE_CONDITION' }, + ); +})); diff --git a/test/es-module/test-esm-shared-loader-dep.mjs b/test/es-module/test-esm-shared-loader-dep.mjs deleted file mode 100644 index e0015bd0c755..000000000000 --- a/test/es-module/test-esm-shared-loader-dep.mjs +++ /dev/null @@ -1,10 +0,0 @@ -// Flags: --experimental-loader ./test/fixtures/es-module-loaders/loader-shared-dep.mjs -import { createRequire } from '../common/index.mjs'; - -import assert from 'assert'; -import '../fixtures/es-modules/test-esm-ok.mjs'; - -const require = createRequire(import.meta.url); -const dep = require('../fixtures/es-module-loaders/loader-dep.js'); - -assert.strictEqual(dep.format, 'module'); diff --git a/test/es-module/test-loaders-unknown-builtin-module.mjs b/test/es-module/test-loaders-unknown-builtin-module.mjs deleted file mode 100644 index c97e52d1e63d..000000000000 --- a/test/es-module/test-loaders-unknown-builtin-module.mjs +++ /dev/null @@ -1,12 +0,0 @@ -// Flags: --experimental-loader ./test/fixtures/es-module-loaders/loader-unknown-builtin-module.mjs -import { expectsError, mustCall } from '../common/index.mjs'; -import assert from 'assert'; - -const unknownBuiltinModule = 'node:unknown-builtin-module'; - -import(unknownBuiltinModule) -.then(assert.fail, expectsError({ - code: 'ERR_UNKNOWN_BUILTIN_MODULE', - message: `No such built-in module: ${unknownBuiltinModule}`, -})) -.then(mustCall()); diff --git a/test/es-module/test-loaders-workers-spawned.mjs b/test/es-module/test-loaders-workers-spawned.mjs deleted file mode 100644 index 68c1b863f7ca..000000000000 --- a/test/es-module/test-loaders-workers-spawned.mjs +++ /dev/null @@ -1,83 +0,0 @@ -import { spawnPromisified } from '../common/index.mjs'; -import * as fixtures from '../common/fixtures.mjs'; -import assert from 'node:assert'; -import { execPath } from 'node:process'; -import { describe, it } from 'node:test'; - -describe('Worker threads do not spawn infinitely', { concurrency: !process.env.TEST_PARALLEL }, () => { - it('should not trigger an infinite loop when using a loader exports no recognized hooks', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('empty.js'), - '--eval', - 'setTimeout(() => console.log("hello"),99)', - ]); - - assert.strictEqual(stderr, ''); - assert.match(stdout, /^hello\r?\n$/); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - - it('should support a CommonJS entry point and a loader that imports a CommonJS module', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/loader-with-dep.mjs'), - fixtures.path('print-delayed.js'), - ]); - - assert.strictEqual(stderr, ''); - assert.match(stdout, /^delayed\r?\n$/); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - - it('should support --require and --import along with using a loader written in CJS and CJS entry point', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--eval', - 'setTimeout(() => console.log("D"),99)', - '--import', - fixtures.fileURL('printC.js'), - '--experimental-loader', - fixtures.fileURL('printB.js'), - '--require', - fixtures.path('printA.js'), - ]); - - assert.strictEqual(stderr, ''); - // We are validating that: - // 1. the `--require` flag is run first from the main thread (and A is printed). - // 2. the `--require` flag is then run on the loader thread (and A is printed). - // 3. the `--loader` module is executed (and B is printed). - // 4. the `--import` module is evaluated once, on the main thread (and C is printed). - // 5. the user code is finally executed (and D is printed). - // The worker code should always run before the --import, but the console.log might arrive late. - assert.match(stdout, /^A\r?\n(A\r?\nB\r?\nC|A\r?\nC\r?\nB|C\r?\nA\r?\nB)\r?\nD\r?\n$/); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - - it('should support --require and --import along with using a loader written in ESM and ESM entry point', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--require', - fixtures.path('printA.js'), - '--experimental-loader', - 'data:text/javascript,console.log("B")', - '--import', - fixtures.fileURL('printC.js'), - '--input-type=module', - '--eval', - 'setTimeout(() => console.log("D"),99)', - ]); - - assert.strictEqual(stderr, ''); - // The worker code should always run before the --import, but the console.log might arrive late. - assert.match(stdout, /^A\r?\n(A\r?\nB\r?\nC|A\r?\nC\r?\nB|C\r?\nA\r?\nB)\r?\nD\r?\n$/); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); -}); diff --git a/test/es-module/test-require-esm-from-imported-cjs.js b/test/es-module/test-require-esm-from-imported-cjs.js index bbce967a4b7e..b643923313db 100644 --- a/test/es-module/test-require-esm-from-imported-cjs.js +++ b/test/es-module/test-require-esm-from-imported-cjs.js @@ -20,17 +20,3 @@ spawnSyncAndAssert( stdout: / default: { hello: 'world' }/, } ); - -spawnSyncAndAssert( - process.execPath, - [ - '--experimental-require-module', - '--import', - fixtures.fileURL('es-modules', 'require-esm-in-cjs-cache', 'instrument.js'), - fixtures.path('es-modules', 'require-esm-in-cjs-cache', 'app.cjs'), - ], - { - trim: true, - stdout: / default: { hello: 'world' }/, - } -); diff --git a/test/eslint.config_partial.mjs b/test/eslint.config_partial.mjs index 5800eef9407a..0ef7d93df29d 100644 --- a/test/eslint.config_partial.mjs +++ b/test/eslint.config_partial.mjs @@ -217,7 +217,6 @@ export default [ }, { files: [ - 'test/es-module/test-esm-example-loader.js', 'test/es-module/test-esm-type-flag.js', 'test/es-module/test-esm-type-flag-alias.js', 'test/es-module/test-require-module-detect-entry-point.js', diff --git a/test/fixtures/es-module-loaders/builtin-named-exports-loader.mjs b/test/fixtures/es-module-loaders/builtin-named-exports-loader.mjs deleted file mode 100644 index f1b770d04f4f..000000000000 --- a/test/fixtures/es-module-loaders/builtin-named-exports-loader.mjs +++ /dev/null @@ -1,59 +0,0 @@ -import module from 'node:module'; -import { readFileSync } from 'node:fs'; - -/** @type {string} */ -let GET_BUILTIN; -export function initialize(data) { - GET_BUILTIN = data.GET_BUILTIN; -} - -export async function resolve(specifier, context, next) { - const def = await next(specifier, context); - - if (def.url.startsWith('node:')) { - return { - shortCircuit: true, - url: `custom-${def.url}`, - importAttributes: context.importAttributes, - }; - } - return def; -} - -export function load(url, context, next) { - if (url.startsWith('custom-node:')) { - const urlObj = new URL(url); - return { - shortCircuit: true, - source: generateBuiltinModule(urlObj.pathname), - format: 'commonjs', - }; - } else if (context.format === undefined || context.format === null || context.format === 'commonjs') { - return { - shortCircuit: true, - source: readFileSync(new URL(url)), - format: 'commonjs', - }; - } - return next(url); -} - -function generateBuiltinModule(builtinName) { - const builtinInstance = module._load(builtinName); - const builtinExports = [ - ...Object.keys(builtinInstance), - ]; - return `\ -const $builtinInstance = ${GET_BUILTIN}(${JSON.stringify(builtinName)}); - -module.exports = $builtinInstance; -module.exports.__fromLoader = true; - -// We need this for CJS-module-lexer can parse the exported names. -${ - builtinExports - .map(name => `exports.${name} = $builtinInstance.${name};`) - .join('\n') -} -`; -} diff --git a/test/fixtures/es-module-loaders/builtin-named-exports.mjs b/test/fixtures/es-module-loaders/builtin-named-exports.mjs deleted file mode 100644 index 123b12c26bf0..000000000000 --- a/test/fixtures/es-module-loaders/builtin-named-exports.mjs +++ /dev/null @@ -1,17 +0,0 @@ -import * as fixtures from '../../common/fixtures.mjs'; -import { createRequire, register } from 'node:module'; - -const require = createRequire(import.meta.url); - -const GET_BUILTIN = `$__get_builtin_hole_${Date.now()}`; -Object.defineProperty(globalThis, GET_BUILTIN, { - value: builtinName => require(builtinName), - enumerable: false, - configurable: false, -}); - -register(fixtures.fileURL('es-module-loaders/builtin-named-exports-loader.mjs'), { - data: { - GET_BUILTIN, - }, -}); diff --git a/test/fixtures/es-module-loaders/byop-dummy-loader.mjs b/test/fixtures/es-module-loaders/byop-dummy-loader.mjs deleted file mode 100644 index 17a3430b44e3..000000000000 --- a/test/fixtures/es-module-loaders/byop-dummy-loader.mjs +++ /dev/null @@ -1,30 +0,0 @@ -export function load(url, context, nextLoad) { - switch (url) { - case 'byop://1/index.mjs': - return { - source: 'console.log("index.mjs!")', - format: 'module', - shortCircuit: true, - }; - case 'byop://1/index2.mjs': - return { - source: 'import c from "./sub.mjs"; console.log(c);', - format: 'module', - shortCircuit: true, - }; - case 'byop://1/sub.mjs': - return { - source: 'export default 42', - format: 'module', - shortCircuit: true, - }; - case 'byop://1/index.byoe': - return { - source: 'console.log("index.byoe!")', - format: 'module', - shortCircuit: true, - }; - default: - return nextLoad(url, context); - } -} diff --git a/test/fixtures/es-module-loaders/example-loader.mjs b/test/fixtures/es-module-loaders/example-loader.mjs deleted file mode 100644 index 77d44d55f915..000000000000 --- a/test/fixtures/es-module-loaders/example-loader.mjs +++ /dev/null @@ -1,46 +0,0 @@ -import { URL } from 'url'; -import path from 'path'; -import process from 'process'; -import { builtinModules } from 'module'; - -const JS_EXTENSIONS = new Set(['.js', '.mjs']); - -const baseURL = new URL('file://'); -baseURL.pathname = process.cwd() + '/'; - -export function resolve(specifier, { parentURL = baseURL }, next) { - if (builtinModules.includes(specifier)) { - return { - shortCircuit: true, - url: 'node:' + specifier - }; - } - if (/^\.{1,2}[/]/.test(specifier) !== true && !specifier.startsWith('file:')) { - // For node_modules support: - // return next(specifier); - throw new Error( - `imports must be URLs or begin with './', or '../'; '${specifier}' does not`); - } - const resolved = new URL(specifier, parentURL); - return { - shortCircuit: true, - url: resolved.href - }; -} - -export function getFormat(url, context, defaultGetFormat) { - if (url.startsWith('node:') && builtinModules.includes(url.slice(5))) { - return { - format: 'builtin' - }; - } - const { pathname } = new URL(url); - const ext = path.extname(pathname); - if (!JS_EXTENSIONS.has(ext)) { - throw new Error( - `Cannot load file with non-JavaScript file extension ${ext}.`); - } - return { - format: 'module' - }; -} diff --git a/test/fixtures/es-module-loaders/hook-resolve-type-loader.mjs b/test/fixtures/es-module-loaders/hook-resolve-type-loader.mjs deleted file mode 100644 index c410401876e4..000000000000 --- a/test/fixtures/es-module-loaders/hook-resolve-type-loader.mjs +++ /dev/null @@ -1,25 +0,0 @@ -/** @type {Uint8Array} */ -let data; -/** @type {number} */ -let ESM_MODULE_INDEX; -/** @type {number} */ -let CJS_MODULE_INDEX; - -export function initialize({ sab, ESM_MODULE_INDEX:e, CJS_MODULE_INDEX:c }) { - data = new Uint8Array(sab); - ESM_MODULE_INDEX = e; - CJS_MODULE_INDEX = c; -} - -export async function resolve(specifier, context, next) { - const nextResult = await next(specifier, context); - const { format } = nextResult; - - if (format === 'module' || specifier.endsWith('.mjs')) { - Atomics.add(data, ESM_MODULE_INDEX, 1); - } else if (format == null || format === 'commonjs') { - Atomics.add(data, CJS_MODULE_INDEX, 1); - } - - return nextResult; -} diff --git a/test/fixtures/es-module-loaders/hook-resolve-type.mjs b/test/fixtures/es-module-loaders/hook-resolve-type.mjs deleted file mode 100644 index 7324a08e84b6..000000000000 --- a/test/fixtures/es-module-loaders/hook-resolve-type.mjs +++ /dev/null @@ -1,18 +0,0 @@ -import * as fixtures from '../../common/fixtures.mjs'; -import { register } from 'node:module'; - -const sab = new SharedArrayBuffer(2); -const data = new Uint8Array(sab); - -const ESM_MODULE_INDEX = 0 -const CJS_MODULE_INDEX = 1 - -export function getModuleTypeStats() { - const importedESM = Atomics.load(data, ESM_MODULE_INDEX); - const importedCJS = Atomics.load(data, CJS_MODULE_INDEX); - return { importedESM, importedCJS }; -} - -register(fixtures.fileURL('es-module-loaders/hook-resolve-type-loader.mjs'), { - data: { sab, ESM_MODULE_INDEX, CJS_MODULE_INDEX }, -}); diff --git a/test/fixtures/es-module-loaders/hooks-custom.mjs b/test/fixtures/es-module-loaders/hooks-custom.mjs deleted file mode 100644 index 5109d20f4d37..000000000000 --- a/test/fixtures/es-module-loaders/hooks-custom.mjs +++ /dev/null @@ -1,116 +0,0 @@ -import { pathToFileURL } from 'node:url'; -import count from '../es-modules/stateful.mjs'; - - -// Arbitrary instance of manipulating a module's internal state -// used to assert node-land and user-land have different contexts -count(); - -export function resolve(specifier, { importAttributes }, next) { - let format = ''; - - if (specifier === 'esmHook/format.false') { - format = false; - } - if (specifier === 'esmHook/format.true') { - format = true; - } - if (specifier === 'esmHook/preknownFormat.pre') { - format = 'module'; - } - - if (specifier.startsWith('esmHook')) { - return { - format, - shortCircuit: true, - url: pathToFileURL(specifier).href, - importAttributes, - }; - } - - return next(specifier); -} - -/** - * @param {string} url A fully resolved file url. - * @param {object} context Additional info. - * @param {function} next for now, next is defaultLoad a wrapper for - * defaultGetFormat + defaultGetSource - * @returns {{ format: string, source: (string|SharedArrayBuffer|Uint8Array) }} - */ -export function load(url, context, next) { - // Load all .js files as ESM, regardless of package scope - if (url.endsWith('.js')) { - return next(url, { - ...context, - format: 'module', - }); - } - - if (url.endsWith('.ext')) { - return next(url, { - ...context, - format: 'module', - }); - } - - if (url.endsWith('esmHook/badReturnVal.mjs')) { - return 'export function returnShouldBeObject() {}'; - } - - if (url.endsWith('esmHook/badReturnFormatVal.mjs')) { - return { - format: Array(0), - shortCircuit: true, - source: '', - }; - } - if (url.endsWith('esmHook/unsupportedReturnFormatVal.mjs')) { - return { - format: 'foo', // Not one of the allowable inputs: no translator named 'foo' - shortCircuit: true, - source: '', - }; - } - - if (url.endsWith('esmHook/badReturnSourceVal.mjs')) { - return { - format: 'module', - shortCircuit: true, - source: Array(0), - }; - } - - if (url.endsWith('esmHook/preknownFormat.pre')) { - return { - format: context.format, - shortCircuit: true, - source: `const msg = 'hello world'; export default msg;` - }; - } - - if (url.endsWith('esmHook/virtual.mjs')) { - return { - format: 'module', - shortCircuit: true, - source: `export const message = 'Woohoo!'.toUpperCase();`, - }; - } - - if (url.endsWith('esmHook/commonJsNullSource.mjs')) { - return { - format: 'commonjs', - shortCircuit: true, - source: 1n, - }; - } - - if (url.endsWith('esmHook/maximumCallStack.mjs')) { - function recurse() { - recurse(); - } - recurse(); - } - - return next(url); -} diff --git a/test/fixtures/es-module-loaders/hooks-initialize-port.mjs b/test/fixtures/es-module-loaders/hooks-initialize-port.mjs deleted file mode 100644 index cefe8854297c..000000000000 --- a/test/fixtures/es-module-loaders/hooks-initialize-port.mjs +++ /dev/null @@ -1,16 +0,0 @@ -let thePort = null; - -export async function initialize(port) { - port.postMessage('initialize'); - thePort = port; -} - -export async function resolve(specifier, context, next) { - if (specifier === 'node:fs' || specifier.includes('loader')) { - return next(specifier); - } - - thePort.postMessage(`resolve ${specifier}`); - - return next(specifier); -} diff --git a/test/fixtures/es-module-loaders/hooks-initialize.mjs b/test/fixtures/es-module-loaders/hooks-initialize.mjs deleted file mode 100644 index 7622d982a9d7..000000000000 --- a/test/fixtures/es-module-loaders/hooks-initialize.mjs +++ /dev/null @@ -1,7 +0,0 @@ -import { writeFileSync } from 'node:fs'; - -let counter = 0; - -export async function initialize() { - writeFileSync(1, `hooks initialize ${++counter}\n`); -} diff --git a/test/fixtures/es-module-loaders/hooks-input.mjs b/test/fixtures/es-module-loaders/hooks-input.mjs deleted file mode 100644 index e4eb159b3987..000000000000 --- a/test/fixtures/es-module-loaders/hooks-input.mjs +++ /dev/null @@ -1,94 +0,0 @@ -// This is expected to be used by test-esm-loader-hooks.mjs via: -// node --loader ./test/fixtures/es-module-loaders/hooks-input.mjs ./test/fixtures/es-modules/json-modules.mjs - -import assert from 'assert'; -import { writeSync } from 'fs'; -import { readFile } from 'fs/promises'; -import { fileURLToPath } from 'url'; - - -let resolveCalls = 0; -let loadCalls = 0; - -export async function resolve(specifier, context, next) { - resolveCalls++; - let url; - - if (resolveCalls === 1) { - url = new URL(specifier).href; - assert.match(specifier, /json-modules\.mjs$/); - - if (!(/\[eval\d*\]$/).test(context.parentURL)) { - assert.strictEqual(context.parentURL, undefined); - } - - assert.deepStrictEqual(context.importAttributes, {}); - } else if (resolveCalls === 2) { - url = new URL(specifier, context.parentURL).href; - assert.match(specifier, /experimental\.json$/); - assert.match(context.parentURL, /json-modules\.mjs$/); - assert.deepStrictEqual(context.importAttributes, { - type: 'json', - }); - } else { - throw new Error(`Unexpected resolve call: ${specifier}`); - } - - // Ensure `context` has all and only the properties it's supposed to - assert.deepStrictEqual(Reflect.ownKeys(context), [ - 'conditions', - 'importAttributes', - 'parentURL', - 'importAssertions', - ]); - assert.ok(Array.isArray(context.conditions)); - assert.strictEqual(typeof next, 'function'); - - const returnValue = { - url, - format: 'test', - shortCircuit: true, - } - - writeSync(1, JSON.stringify(returnValue) + '\n'); // For the test to validate when it parses stdout - - return returnValue; -} - -export async function load(url, context, next) { - loadCalls++; - const source = await readFile(fileURLToPath(url)); - let format; - - if (loadCalls === 1) { - assert.match(url, /json-modules\.mjs$/); - assert.deepStrictEqual(context.importAttributes, {}); - format = 'module'; - } else if (loadCalls === 2) { - assert.match(url, /experimental\.json$/); - assert.deepStrictEqual(context.importAttributes, { - type: 'json', - }); - format = 'json'; - } - - assert.ok(new URL(url)); - // Ensure `context` has all and only the properties it's supposed to - assert.deepStrictEqual(Reflect.ownKeys(context), [ - 'format', - 'importAttributes', - 'importAssertions', - ]); - assert.strictEqual(context.format, 'test'); - assert.strictEqual(typeof next, 'function'); - - const returnValue = { - source, - format, - shortCircuit: true, - }; - - writeSync(1, JSON.stringify(returnValue) + '\n'); // For the test to validate when it parses stdout - - return returnValue; -} diff --git a/test/fixtures/es-module-loaders/http-loader.mjs b/test/fixtures/es-module-loaders/http-loader.mjs deleted file mode 100644 index 4fe8fcb47974..000000000000 --- a/test/fixtures/es-module-loaders/http-loader.mjs +++ /dev/null @@ -1,22 +0,0 @@ -import { get } from 'http'; - -export function load(url, context, nextLoad) { - if (url.startsWith('http://')) { - return new Promise((resolve, reject) => { - get(url, (rsp) => { - let data = ''; - rsp.on('data', (chunk) => data += chunk); - rsp.on('end', () => { - resolve({ - format: 'module', - shortCircuit: true, - source: data, - }); - }); - }) - .on('error', reject); - }); - } - - return nextLoad(url); -} diff --git a/test/fixtures/es-module-loaders/js-as-esm.js b/test/fixtures/es-module-loaders/js-as-esm.js deleted file mode 100644 index b4d2741b2fc5..000000000000 --- a/test/fixtures/es-module-loaders/js-as-esm.js +++ /dev/null @@ -1 +0,0 @@ -export const namedExport = 'named-export'; diff --git a/test/fixtures/es-module-loaders/loader-delayed-async-load.mjs b/test/fixtures/es-module-loaders/loader-delayed-async-load.mjs deleted file mode 100644 index 33bae9bdafd9..000000000000 --- a/test/fixtures/es-module-loaders/loader-delayed-async-load.mjs +++ /dev/null @@ -1,3 +0,0 @@ -export function load(a, b, c) { - return new Promise(d => setTimeout(() => d(c(a, b)), 99)); -} diff --git a/test/fixtures/es-module-loaders/loader-dep.js b/test/fixtures/es-module-loaders/loader-dep.js deleted file mode 100644 index c8154ac5dbe6..000000000000 --- a/test/fixtures/es-module-loaders/loader-dep.js +++ /dev/null @@ -1 +0,0 @@ -exports.format = 'module'; diff --git a/test/fixtures/es-module-loaders/loader-edge-cases.mjs b/test/fixtures/es-module-loaders/loader-edge-cases.mjs deleted file mode 100644 index 19af0e2bc078..000000000000 --- a/test/fixtures/es-module-loaders/loader-edge-cases.mjs +++ /dev/null @@ -1,13 +0,0 @@ -import { strictEqual } from "node:assert"; -import { isMainThread, workerData, parentPort } from "node:worker_threads"; - -strictEqual(isMainThread, false); - -// We want to make sure that internals are not leaked on the public module: -strictEqual(workerData, null); -strictEqual(parentPort, null); - -// We don't want `import.meta.resolve` being available from loaders -// as the sync implementation is not compatible with calling async -// functions on the same thread. -strictEqual(typeof import.meta.resolve, 'undefined'); diff --git a/test/fixtures/es-module-loaders/loader-exit-on-load.mjs b/test/fixtures/es-module-loaders/loader-exit-on-load.mjs deleted file mode 100644 index 4ee413fa46ce..000000000000 --- a/test/fixtures/es-module-loaders/loader-exit-on-load.mjs +++ /dev/null @@ -1,4 +0,0 @@ -export function load(a, b, next) { - if (a === 'data:exit') process.exit(42); - return next(a, b); -} diff --git a/test/fixtures/es-module-loaders/loader-exit-on-resolve.mjs b/test/fixtures/es-module-loaders/loader-exit-on-resolve.mjs deleted file mode 100644 index cffa4bcc486a..000000000000 --- a/test/fixtures/es-module-loaders/loader-exit-on-resolve.mjs +++ /dev/null @@ -1,4 +0,0 @@ -export function resolve(a, b, next) { - if (a === 'exit:') process.exit(42); - return next(a, b); -} diff --git a/test/fixtures/es-module-loaders/loader-exit-top-level.mjs b/test/fixtures/es-module-loaders/loader-exit-top-level.mjs deleted file mode 100644 index 6427ca06037f..000000000000 --- a/test/fixtures/es-module-loaders/loader-exit-top-level.mjs +++ /dev/null @@ -1 +0,0 @@ -process.exit(42); diff --git a/test/fixtures/es-module-loaders/loader-globalpreload-and-initialize.mjs b/test/fixtures/es-module-loaders/loader-globalpreload-and-initialize.mjs deleted file mode 100644 index 7055b90f4e75..000000000000 --- a/test/fixtures/es-module-loaders/loader-globalpreload-and-initialize.mjs +++ /dev/null @@ -1,2 +0,0 @@ -export function globalPreload() {} -export function initialize() {} diff --git a/test/fixtures/es-module-loaders/loader-globalpreload-only.mjs b/test/fixtures/es-module-loaders/loader-globalpreload-only.mjs deleted file mode 100644 index 6b7f59d63198..000000000000 --- a/test/fixtures/es-module-loaders/loader-globalpreload-only.mjs +++ /dev/null @@ -1 +0,0 @@ -export function globalPreload() {} diff --git a/test/fixtures/es-module-loaders/loader-globalpreload-with-return.mjs b/test/fixtures/es-module-loaders/loader-globalpreload-with-return.mjs deleted file mode 100644 index f54cf72ce5b9..000000000000 --- a/test/fixtures/es-module-loaders/loader-globalpreload-with-return.mjs +++ /dev/null @@ -1,3 +0,0 @@ -export function globalPreload() { - return ''; -} diff --git a/test/fixtures/es-module-loaders/loader-initialize-exit.mjs b/test/fixtures/es-module-loaders/loader-initialize-exit.mjs deleted file mode 100644 index 53cb4fa26f01..000000000000 --- a/test/fixtures/es-module-loaders/loader-initialize-exit.mjs +++ /dev/null @@ -1,3 +0,0 @@ -export function initialize() { - process.exit(42); -} diff --git a/test/fixtures/es-module-loaders/loader-initialize-never-settling.mjs b/test/fixtures/es-module-loaders/loader-initialize-never-settling.mjs deleted file mode 100644 index 80033b663a5a..000000000000 --- a/test/fixtures/es-module-loaders/loader-initialize-never-settling.mjs +++ /dev/null @@ -1,3 +0,0 @@ -export function initialize() { - return new Promise(() => {}); -} diff --git a/test/fixtures/es-module-loaders/loader-initialize-rejecting.mjs b/test/fixtures/es-module-loaders/loader-initialize-rejecting.mjs deleted file mode 100644 index 899accc15114..000000000000 --- a/test/fixtures/es-module-loaders/loader-initialize-rejecting.mjs +++ /dev/null @@ -1,3 +0,0 @@ -export function initialize() { - return Promise.reject(); -} diff --git a/test/fixtures/es-module-loaders/loader-initialize-throw-null.mjs b/test/fixtures/es-module-loaders/loader-initialize-throw-null.mjs deleted file mode 100644 index 3d834b5c23cf..000000000000 --- a/test/fixtures/es-module-loaders/loader-initialize-throw-null.mjs +++ /dev/null @@ -1,3 +0,0 @@ -export function initialize() { - throw null; -} diff --git a/test/fixtures/es-module-loaders/loader-invalid-format.mjs b/test/fixtures/es-module-loaders/loader-invalid-format.mjs deleted file mode 100644 index dc61a792b2be..000000000000 --- a/test/fixtures/es-module-loaders/loader-invalid-format.mjs +++ /dev/null @@ -1,20 +0,0 @@ -export async function resolve(specifier, { parentURL, importAttributes }, next) { - if (parentURL && specifier === '../fixtures/es-modules/test-esm-ok.mjs') { - return { - shortCircuit: true, - url: 'file:///asdf', - }; - } - return next(specifier); -} - -export async function load(url, context, next) { - if (url === 'file:///asdf') { - return { - format: 'esm', - shortCircuit: true, - source: '', - } - } - return next(url); -} diff --git a/test/fixtures/es-module-loaders/loader-invalid-url.mjs b/test/fixtures/es-module-loaders/loader-invalid-url.mjs deleted file mode 100644 index aac2b16b6f58..000000000000 --- a/test/fixtures/es-module-loaders/loader-invalid-url.mjs +++ /dev/null @@ -1,10 +0,0 @@ -export async function resolve(specifier, { parentURL, importAttributes }, next) { - if (parentURL && specifier === '../fixtures/es-modules/test-esm-ok.mjs') { - return { - shortCircuit: true, - url: specifier, - importAttributes, - }; - } - return next(specifier); -} diff --git a/test/fixtures/es-module-loaders/loader-load-bad-next-context.mjs b/test/fixtures/es-module-loaders/loader-load-bad-next-context.mjs deleted file mode 100644 index fe38da04f9ff..000000000000 --- a/test/fixtures/es-module-loaders/loader-load-bad-next-context.mjs +++ /dev/null @@ -1,3 +0,0 @@ -export async function load(url, context, next) { - return next(url, []); -} diff --git a/test/fixtures/es-module-loaders/loader-load-bad-next-url.mjs b/test/fixtures/es-module-loaders/loader-load-bad-next-url.mjs deleted file mode 100644 index c6a4c7504d43..000000000000 --- a/test/fixtures/es-module-loaders/loader-load-bad-next-url.mjs +++ /dev/null @@ -1,3 +0,0 @@ -export async function load(url, context, next) { - return next([]); -} diff --git a/test/fixtures/es-module-loaders/loader-load-commonjs-with-source.mjs b/test/fixtures/es-module-loaders/loader-load-commonjs-with-source.mjs deleted file mode 100644 index 3f431e630cdd..000000000000 --- a/test/fixtures/es-module-loaders/loader-load-commonjs-with-source.mjs +++ /dev/null @@ -1,13 +0,0 @@ -import { readFile } from 'node:fs/promises'; -import { fileURLToPath } from 'node:url'; - -export async function load(url, context, nextLoad) { - const result = await nextLoad(url, context); - if (url.endsWith('/common/index.js')) { - result.source = '"use strict";module.exports=require("node:module").createRequire(' + - JSON.stringify(url) + ')(' + JSON.stringify(fileURLToPath(url)) + ');\n'; - } else if (url.startsWith('file:') && (context.format == null || context.format === 'commonjs')) { - result.source = await readFile(new URL(url)); - } - return result; -} diff --git a/test/fixtures/es-module-loaders/loader-load-dynamic-import.mjs b/test/fixtures/es-module-loaders/loader-load-dynamic-import.mjs deleted file mode 100644 index 96af5507d172..000000000000 --- a/test/fixtures/es-module-loaders/loader-load-dynamic-import.mjs +++ /dev/null @@ -1,14 +0,0 @@ -import { writeSync } from 'node:fs'; - - -export async function load(url, context, next) { - if (url === 'node:fs' || url.includes('loader')) { - return next(url); - } - - // Here for asserting dynamic import - await import('xxx/loader-load-passthru.mjs'); - - writeSync(1, 'load dynamic import' + '\n'); // Signal that this specific hook ran - return next(url, context); -} diff --git a/test/fixtures/es-module-loaders/loader-load-foo-or-42.mjs b/test/fixtures/es-module-loaders/loader-load-foo-or-42.mjs deleted file mode 100644 index 285b81a910ef..000000000000 --- a/test/fixtures/es-module-loaders/loader-load-foo-or-42.mjs +++ /dev/null @@ -1,19 +0,0 @@ -export async function load(url, context, next) { - // This check is needed to make sure that we don't prevent the - // resolution from follow-up loaders. It wouldn't be a problem - // in real life because loaders aren't supposed to break the - // resolution, but the ones used in our tests do, for convenience. - if (url === 'node:fs' || url.includes('loader')) { - return next(url); - } - - const val = url.includes('42') - ? '42' - : '"foo"'; - - return { - format: 'module', - shortCircuit: true, - source: `export default ${val}`, - }; -} diff --git a/test/fixtures/es-module-loaders/loader-load-impersonating-next-url.mjs b/test/fixtures/es-module-loaders/loader-load-impersonating-next-url.mjs deleted file mode 100644 index 1028e093e468..000000000000 --- a/test/fixtures/es-module-loaders/loader-load-impersonating-next-url.mjs +++ /dev/null @@ -1,3 +0,0 @@ -export async function load(url, context, next) { - return next('not/a/url'); -} diff --git a/test/fixtures/es-module-loaders/loader-load-incomplete.mjs b/test/fixtures/es-module-loaders/loader-load-incomplete.mjs deleted file mode 100644 index bf6d5ea254dc..000000000000 --- a/test/fixtures/es-module-loaders/loader-load-incomplete.mjs +++ /dev/null @@ -1,14 +0,0 @@ -export async function load(url, context, next) { - // This check is needed to make sure that we don't prevent the - // resolution from follow-up loaders. It wouldn't be a problem - // in real life because loaders aren't supposed to break the - // resolution, but the ones used in our tests do, for convenience. - if (url === 'node:fs' || url.includes('loader')) { - return next(url); - } - - return { - format: 'module', - source: 'export default 42', - }; -} diff --git a/test/fixtures/es-module-loaders/loader-load-next-modified.mjs b/test/fixtures/es-module-loaders/loader-load-next-modified.mjs deleted file mode 100644 index 401b52971311..000000000000 --- a/test/fixtures/es-module-loaders/loader-load-next-modified.mjs +++ /dev/null @@ -1,11 +0,0 @@ -export async function load(url, context, next) { - const { - format, - source, - } = await next(url); - - return { - format, - source: source + 1, - }; -} diff --git a/test/fixtures/es-module-loaders/loader-load-null-return.mjs b/test/fixtures/es-module-loaders/loader-load-null-return.mjs deleted file mode 100644 index 252eec4ebc28..000000000000 --- a/test/fixtures/es-module-loaders/loader-load-null-return.mjs +++ /dev/null @@ -1,3 +0,0 @@ -export async function load(specifier, context, next) { - return null; -} diff --git a/test/fixtures/es-module-loaders/loader-load-passing-modified-context.mjs b/test/fixtures/es-module-loaders/loader-load-passing-modified-context.mjs deleted file mode 100644 index 7676be766af5..000000000000 --- a/test/fixtures/es-module-loaders/loader-load-passing-modified-context.mjs +++ /dev/null @@ -1,6 +0,0 @@ -export async function load(url, context, next) { - return next(url, { - ...context, - foo: 'bar', - }); -} diff --git a/test/fixtures/es-module-loaders/loader-load-passthru.mjs b/test/fixtures/es-module-loaders/loader-load-passthru.mjs deleted file mode 100644 index 72ff6b56122d..000000000000 --- a/test/fixtures/es-module-loaders/loader-load-passthru.mjs +++ /dev/null @@ -1,14 +0,0 @@ -import { writeSync } from 'node:fs'; - -export async function load(url, context, next) { - // This check is needed to make sure that we don't prevent the - // resolution from follow-up loaders. It wouldn't be a problem - // in real life because loaders aren't supposed to break the - // resolution, but the ones used in our tests do, for convenience. - if (url === 'node:fs' || url.includes('loader')) { - return next(url); - } - - writeSync(1, 'load passthru' + '\n'); // Signal that this specific hook ran - return next(url); -} diff --git a/test/fixtures/es-module-loaders/loader-load-receiving-modified-context.mjs b/test/fixtures/es-module-loaders/loader-load-receiving-modified-context.mjs deleted file mode 100644 index 6e5a1ee71936..000000000000 --- a/test/fixtures/es-module-loaders/loader-load-receiving-modified-context.mjs +++ /dev/null @@ -1,7 +0,0 @@ -import { writeSync } from 'node:fs'; - - -export async function load(url, context, next) { - writeSync(1, context.foo + '\n'); // Expose actual value the hook was called with - return next(url, context); -} diff --git a/test/fixtures/es-module-loaders/loader-load-source-maps.mjs b/test/fixtures/es-module-loaders/loader-load-source-maps.mjs deleted file mode 100644 index bf5ef6b91028..000000000000 --- a/test/fixtures/es-module-loaders/loader-load-source-maps.mjs +++ /dev/null @@ -1,9 +0,0 @@ -import { readFile } from 'node:fs/promises'; - -export async function load(url, context, nextLoad) { - const resolved = await nextLoad(url, context); - if (context.format === 'commonjs') { - resolved.source = await readFile(new URL(url)); - } - return resolved; -} diff --git a/test/fixtures/es-module-loaders/loader-log-args.mjs b/test/fixtures/es-module-loaders/loader-log-args.mjs deleted file mode 100644 index b56fddbce585..000000000000 --- a/test/fixtures/es-module-loaders/loader-log-args.mjs +++ /dev/null @@ -1,31 +0,0 @@ -import { writeSync } from 'node:fs'; -import { inspect } from 'node:util' - -export async function resolve(...args) { - writeSync(1, `resolve arg count: ${args.length}\n`); - writeSync(1, inspect({ - specifier: args[0], - context: args[1], - next: args[2], - }) + '\n'); - - return { - shortCircuit: true, - url: args[0], - }; -} - -export async function load(...args) { - writeSync(1, `load arg count: ${args.length}\n`); - writeSync(1, inspect({ - url: args[0], - context: args[1], - next: args[2], - }) + '\n'); - - return { - format: 'module', - source: '', - shortCircuit: true, - }; -} diff --git a/test/fixtures/es-module-loaders/loader-mixed-opt-in.mjs b/test/fixtures/es-module-loaders/loader-mixed-opt-in.mjs deleted file mode 100644 index 69b17a88249b..000000000000 --- a/test/fixtures/es-module-loaders/loader-mixed-opt-in.mjs +++ /dev/null @@ -1,28 +0,0 @@ -import { createRequire } from 'module'; - -const require = createRequire(import.meta.url); -const fixtures = require('../../common/fixtures.js'); - -const fixturesPath = fixtures.path('empty.js'); - -export function resolve(s, c, n) { - if (s.endsWith('entry-point')) { - return { - shortCircuit: true, - url: 'file:///c:/virtual-entry-point', - format: 'commonjs', - }; - } - return n(s, c); -} - -export async function load(u, c, n) { - if (u === 'file:///c:/virtual-entry-point') { - return { - shortCircuit: true, - source: `"use strict";require(${JSON.stringify(fixturesPath)});console.log("Hello");`, - format: 'commonjs', - }; - } - return n(u, c); -} diff --git a/test/fixtures/es-module-loaders/loader-resolve-42.mjs b/test/fixtures/es-module-loaders/loader-resolve-42.mjs deleted file mode 100644 index b571a27a9787..000000000000 --- a/test/fixtures/es-module-loaders/loader-resolve-42.mjs +++ /dev/null @@ -1,17 +0,0 @@ -import { writeSync } from 'node:fs'; - - -export async function resolve(specifier, context, next) { - // This check is needed to make sure that we don't prevent the - // resolution from follow-up loaders. It wouldn't be a problem - // in real life because loaders aren't supposed to break the - // resolution, but the ones used in our tests do, for convenience. - if (specifier === 'node:fs' || specifier.includes('loader')) { - return next(specifier); - } - - writeSync(1, 'resolve 42' + '\n'); // Signal that this specific hook ran - writeSync(1, `next: ${next.name}\n`); // Expose actual value the hook was called with - - return next('file:///42.mjs'); -} diff --git a/test/fixtures/es-module-loaders/loader-resolve-bad-next-context.mjs b/test/fixtures/es-module-loaders/loader-resolve-bad-next-context.mjs deleted file mode 100644 index 881f5875dd02..000000000000 --- a/test/fixtures/es-module-loaders/loader-resolve-bad-next-context.mjs +++ /dev/null @@ -1,3 +0,0 @@ -export async function resolve(specifier, context, next) { - return next(specifier, []); -} diff --git a/test/fixtures/es-module-loaders/loader-resolve-bad-next-specifier.mjs b/test/fixtures/es-module-loaders/loader-resolve-bad-next-specifier.mjs deleted file mode 100644 index 66c941754f90..000000000000 --- a/test/fixtures/es-module-loaders/loader-resolve-bad-next-specifier.mjs +++ /dev/null @@ -1,3 +0,0 @@ -export async function resolve(specifier, context, next) { - return next([]); -} diff --git a/test/fixtures/es-module-loaders/loader-resolve-dynamic-import.mjs b/test/fixtures/es-module-loaders/loader-resolve-dynamic-import.mjs deleted file mode 100644 index edc2303ed9aa..000000000000 --- a/test/fixtures/es-module-loaders/loader-resolve-dynamic-import.mjs +++ /dev/null @@ -1,14 +0,0 @@ -import { writeSync } from 'node:fs'; - - -export async function resolve(specifier, context, next) { - if (specifier === 'node:fs' || specifier.includes('loader')) { - return next(specifier); - } - - // Here for asserting dynamic import - await import('xxx/loader-resolve-passthru.mjs'); - - writeSync(1, 'resolve dynamic import' + '\n'); // Signal that this specific hook ran - return next(specifier); -} diff --git a/test/fixtures/es-module-loaders/loader-resolve-foo.mjs b/test/fixtures/es-module-loaders/loader-resolve-foo.mjs deleted file mode 100644 index b5e5a419b99d..000000000000 --- a/test/fixtures/es-module-loaders/loader-resolve-foo.mjs +++ /dev/null @@ -1,15 +0,0 @@ -import { writeSync } from 'node:fs'; - - -export async function resolve(specifier, context, next) { - // This check is needed to make sure that we don't prevent the - // resolution from follow-up loaders. It wouldn't be a problem - // in real life because loaders aren't supposed to break the - // resolution, but the ones used in our tests do, for convenience. - if (specifier === 'node:fs' || specifier.includes('loader')) { - return next(specifier); - } - - writeSync(1, 'resolve foo' + '\n'); // Signal that this specific hook ran - return next('file:///foo.mjs'); -} diff --git a/test/fixtures/es-module-loaders/loader-resolve-incomplete.mjs b/test/fixtures/es-module-loaders/loader-resolve-incomplete.mjs deleted file mode 100644 index cb37a4352718..000000000000 --- a/test/fixtures/es-module-loaders/loader-resolve-incomplete.mjs +++ /dev/null @@ -1,13 +0,0 @@ -export async function resolve(specifier, context, next) { - // This check is needed to make sure that we don't prevent the - // resolution from follow-up loaders. It wouldn't be a problem - // in real life because loaders aren't supposed to break the - // resolution, but the ones used in our tests do, for convenience. - if (specifier === 'node:fs' || specifier.includes('loader')) { - return next(specifier); - } - - return { - url: 'file:///incomplete-resolve-chain.js', - }; -} diff --git a/test/fixtures/es-module-loaders/loader-resolve-multiple-next-calls.mjs b/test/fixtures/es-module-loaders/loader-resolve-multiple-next-calls.mjs deleted file mode 100644 index 91dbec251ede..000000000000 --- a/test/fixtures/es-module-loaders/loader-resolve-multiple-next-calls.mjs +++ /dev/null @@ -1,9 +0,0 @@ -export async function resolve(specifier, context, next) { - const { url: first } = await next(specifier); - const { url: second } = await next(specifier); - - return { - format: 'module', - url: first, - }; -} diff --git a/test/fixtures/es-module-loaders/loader-resolve-next-modified.mjs b/test/fixtures/es-module-loaders/loader-resolve-next-modified.mjs deleted file mode 100644 index a6b8e85455d3..000000000000 --- a/test/fixtures/es-module-loaders/loader-resolve-next-modified.mjs +++ /dev/null @@ -1,19 +0,0 @@ -export async function resolve(url, context, next) { - // This check is needed to make sure that we don't prevent the - // resolution from follow-up loaders. It wouldn't be a problem - // in real life because loaders aren't supposed to break the - // resolution, but the ones used in our tests do, for convenience. - if (url === 'node:fs' || url.includes('loader')) { - return next(url); - } - - const { - format, - url: nextUrl, - } = await next(url, context); - - return { - format, - url: `${nextUrl}?foo`, - }; -} diff --git a/test/fixtures/es-module-loaders/loader-resolve-null-return.mjs b/test/fixtures/es-module-loaders/loader-resolve-null-return.mjs deleted file mode 100644 index 16c9826a8ba5..000000000000 --- a/test/fixtures/es-module-loaders/loader-resolve-null-return.mjs +++ /dev/null @@ -1,3 +0,0 @@ -export async function resolve(specifier, context, next) { - return null; -} diff --git a/test/fixtures/es-module-loaders/loader-resolve-passing-modified-context.mjs b/test/fixtures/es-module-loaders/loader-resolve-passing-modified-context.mjs deleted file mode 100644 index 6a92a6cd8f6a..000000000000 --- a/test/fixtures/es-module-loaders/loader-resolve-passing-modified-context.mjs +++ /dev/null @@ -1,6 +0,0 @@ -export async function resolve(specifier, context, next) { - return next(specifier, { - ...context, - foo: 'bar', - }); -} diff --git a/test/fixtures/es-module-loaders/loader-resolve-passthru.mjs b/test/fixtures/es-module-loaders/loader-resolve-passthru.mjs deleted file mode 100644 index f388f0db273d..000000000000 --- a/test/fixtures/es-module-loaders/loader-resolve-passthru.mjs +++ /dev/null @@ -1,14 +0,0 @@ -import { writeSync } from 'node:fs'; - -export async function resolve(specifier, context, next) { - // This check is needed to make sure that we don't prevent the - // resolution from follow-up loaders. It wouldn't be a problem - // in real life because loaders aren't supposed to break the - // resolution, but the ones used in our tests do, for convenience. - if (specifier === 'node:fs' || specifier.includes('loader')) { - return next(specifier); - } - - writeSync(1, 'resolve passthru' + '\n'); // Signal that this specific hook ran - return next(specifier); -} diff --git a/test/fixtures/es-module-loaders/loader-resolve-receiving-modified-context.mjs b/test/fixtures/es-module-loaders/loader-resolve-receiving-modified-context.mjs deleted file mode 100644 index f6a8fdad53ec..000000000000 --- a/test/fixtures/es-module-loaders/loader-resolve-receiving-modified-context.mjs +++ /dev/null @@ -1,7 +0,0 @@ -import { writeSync } from 'node:fs'; - - -export async function resolve(specifier, context, next) { - writeSync(1, context.foo + '\n'); // Expose actual value the hook was called with - return next(specifier, context); -} diff --git a/test/fixtures/es-module-loaders/loader-resolve-shortcircuit.mjs b/test/fixtures/es-module-loaders/loader-resolve-shortcircuit.mjs deleted file mode 100644 index 00c18e75e2c7..000000000000 --- a/test/fixtures/es-module-loaders/loader-resolve-shortcircuit.mjs +++ /dev/null @@ -1,14 +0,0 @@ -export async function resolve(specifier, context, next) { - // This check is needed to make sure that we don't prevent the - // resolution from follow-up loaders. It wouldn't be a problem - // in real life because loaders aren't supposed to break the - // resolution, but the ones used in our tests do, for convenience. - if (specifier === 'node:fs' || specifier.includes('loader')) { - return next(specifier); - } - - return { - shortCircuit: true, - url: specifier, - } -} diff --git a/test/fixtures/es-module-loaders/loader-resolve-strip-xxx.mjs b/test/fixtures/es-module-loaders/loader-resolve-strip-xxx.mjs deleted file mode 100644 index 996dfd041c0d..000000000000 --- a/test/fixtures/es-module-loaders/loader-resolve-strip-xxx.mjs +++ /dev/null @@ -1,10 +0,0 @@ -import { writeSync } from 'node:fs'; -import { inspect } from 'node:util'; - -export async function resolve(specifier, context, nextResolve) { - if (specifier.startsWith('node:')) { - return nextResolve(specifier); - } - writeSync(1, `loader-a ${inspect({specifier})}\n`); - return nextResolve(specifier.replace(/^xxx\//, `./`)); -} diff --git a/test/fixtures/es-module-loaders/loader-resolve-strip-yyy.mjs b/test/fixtures/es-module-loaders/loader-resolve-strip-yyy.mjs deleted file mode 100644 index 7746469bf817..000000000000 --- a/test/fixtures/es-module-loaders/loader-resolve-strip-yyy.mjs +++ /dev/null @@ -1,7 +0,0 @@ -import { writeSync } from 'node:fs'; -import { inspect } from 'node:util'; - -export async function resolve(specifier, context, nextResolve) { - writeSync(1, `loader-b ${inspect({specifier})}\n`); - return nextResolve(specifier.replace(/^yyy\//, `./`)); -} diff --git a/test/fixtures/es-module-loaders/loader-shared-dep.mjs b/test/fixtures/es-module-loaders/loader-shared-dep.mjs deleted file mode 100644 index d41c1ae40339..000000000000 --- a/test/fixtures/es-module-loaders/loader-shared-dep.mjs +++ /dev/null @@ -1,11 +0,0 @@ -import assert from 'assert'; - -import { createRequire } from '../../common/index.mjs'; - -const require = createRequire(import.meta.url); -const dep = require('./loader-dep.js'); - -export function resolve(specifier, context, next) { - assert.strictEqual(dep.format, 'module'); - return next(specifier); -} diff --git a/test/fixtures/es-module-loaders/loader-this-value-inside-hook-functions.mjs b/test/fixtures/es-module-loaders/loader-this-value-inside-hook-functions.mjs deleted file mode 100644 index 2be18c4969ef..000000000000 --- a/test/fixtures/es-module-loaders/loader-this-value-inside-hook-functions.mjs +++ /dev/null @@ -1,21 +0,0 @@ -export function initialize() { - if (this != null) { - throw new Error('hook function must not be bound to loader instance'); - } -} - -export function resolve(url, _, next) { - if (this != null) { - throw new Error('hook function must not be bound to loader instance'); - } - - return next(url); -} - -export function load(url, _, next) { - if (this != null) { - throw new Error('hook function must not be bound to loader instance'); - } - - return next(url); -} diff --git a/test/fixtures/es-module-loaders/loader-throw-bigint.mjs b/test/fixtures/es-module-loaders/loader-throw-bigint.mjs deleted file mode 100644 index 700b0387fa7f..000000000000 --- a/test/fixtures/es-module-loaders/loader-throw-bigint.mjs +++ /dev/null @@ -1 +0,0 @@ -throw 1n; diff --git a/test/fixtures/es-module-loaders/loader-throw-boolean.mjs b/test/fixtures/es-module-loaders/loader-throw-boolean.mjs deleted file mode 100644 index 874fbd92e5fa..000000000000 --- a/test/fixtures/es-module-loaders/loader-throw-boolean.mjs +++ /dev/null @@ -1 +0,0 @@ -throw true; diff --git a/test/fixtures/es-module-loaders/loader-throw-empty-object.mjs b/test/fixtures/es-module-loaders/loader-throw-empty-object.mjs deleted file mode 100644 index 778886fcb90a..000000000000 --- a/test/fixtures/es-module-loaders/loader-throw-empty-object.mjs +++ /dev/null @@ -1 +0,0 @@ -throw {}; diff --git a/test/fixtures/es-module-loaders/loader-throw-error.mjs b/test/fixtures/es-module-loaders/loader-throw-error.mjs deleted file mode 100644 index 816051b6c783..000000000000 --- a/test/fixtures/es-module-loaders/loader-throw-error.mjs +++ /dev/null @@ -1 +0,0 @@ -throw new Error('error message'); diff --git a/test/fixtures/es-module-loaders/loader-throw-function.mjs b/test/fixtures/es-module-loaders/loader-throw-function.mjs deleted file mode 100644 index 35503d670976..000000000000 --- a/test/fixtures/es-module-loaders/loader-throw-function.mjs +++ /dev/null @@ -1 +0,0 @@ -throw function fnName() {}; diff --git a/test/fixtures/es-module-loaders/loader-throw-null.mjs b/test/fixtures/es-module-loaders/loader-throw-null.mjs deleted file mode 100644 index 37d3d14b8bfe..000000000000 --- a/test/fixtures/es-module-loaders/loader-throw-null.mjs +++ /dev/null @@ -1 +0,0 @@ -throw null; diff --git a/test/fixtures/es-module-loaders/loader-throw-number.mjs b/test/fixtures/es-module-loaders/loader-throw-number.mjs deleted file mode 100644 index 6dbc3994c7ac..000000000000 --- a/test/fixtures/es-module-loaders/loader-throw-number.mjs +++ /dev/null @@ -1 +0,0 @@ -throw 1; diff --git a/test/fixtures/es-module-loaders/loader-throw-object.mjs b/test/fixtures/es-module-loaders/loader-throw-object.mjs deleted file mode 100644 index fa7c9b7d43af..000000000000 --- a/test/fixtures/es-module-loaders/loader-throw-object.mjs +++ /dev/null @@ -1 +0,0 @@ -throw { fn() {}, symbol: Symbol('symbol'), u: undefined }; diff --git a/test/fixtures/es-module-loaders/loader-throw-string.mjs b/test/fixtures/es-module-loaders/loader-throw-string.mjs deleted file mode 100644 index 613a4321f50d..000000000000 --- a/test/fixtures/es-module-loaders/loader-throw-string.mjs +++ /dev/null @@ -1 +0,0 @@ -throw 'literal string'; diff --git a/test/fixtures/es-module-loaders/loader-throw-symbol.mjs b/test/fixtures/es-module-loaders/loader-throw-symbol.mjs deleted file mode 100644 index 05876e0b4ada..000000000000 --- a/test/fixtures/es-module-loaders/loader-throw-symbol.mjs +++ /dev/null @@ -1 +0,0 @@ -throw Symbol('symbol descriptor'); diff --git a/test/fixtures/es-module-loaders/loader-throw-undefined.mjs b/test/fixtures/es-module-loaders/loader-throw-undefined.mjs deleted file mode 100644 index 38ecbdff9801..000000000000 --- a/test/fixtures/es-module-loaders/loader-throw-undefined.mjs +++ /dev/null @@ -1 +0,0 @@ -throw undefined; diff --git a/test/fixtures/es-module-loaders/loader-unknown-builtin-module.mjs b/test/fixtures/es-module-loaders/loader-unknown-builtin-module.mjs deleted file mode 100644 index 65e1adf909d0..000000000000 --- a/test/fixtures/es-module-loaders/loader-unknown-builtin-module.mjs +++ /dev/null @@ -1,7 +0,0 @@ -export function resolve(specifier, context, next) { - if (specifier === 'unknown-builtin-module') return { - url: 'node:unknown-builtin-module' - }; - - return next(specifier); -} diff --git a/test/fixtures/es-module-loaders/loader-with-custom-condition.mjs b/test/fixtures/es-module-loaders/loader-with-custom-condition.mjs deleted file mode 100644 index 90c55fa576bb..000000000000 --- a/test/fixtures/es-module-loaders/loader-with-custom-condition.mjs +++ /dev/null @@ -1,17 +0,0 @@ -import { ok, deepStrictEqual } from 'assert'; - -export async function resolve(specifier, context, defaultResolve) { - ok(Array.isArray(context.conditions), 'loader receives conditions array'); - - deepStrictEqual([...context.conditions].sort(), [ - 'import', - 'module-sync', - 'node', - 'node-addons', - ]); - - return defaultResolve(specifier, { - ...context, - conditions: ['custom-condition', ...context.conditions], - }); -} diff --git a/test/fixtures/es-module-loaders/loader-with-dep.mjs b/test/fixtures/es-module-loaders/loader-with-dep.mjs deleted file mode 100644 index 625341eaed7e..000000000000 --- a/test/fixtures/es-module-loaders/loader-with-dep.mjs +++ /dev/null @@ -1,11 +0,0 @@ -import {createRequire} from '../../common/index.mjs'; - -const require = createRequire(import.meta.url); -const dep = require('./loader-dep.js'); - -export async function resolve(specifier, { parentURL, importAttributes }, defaultResolve) { - return { - url: (await defaultResolve(specifier, { parentURL, importAttributes }, defaultResolve)).url, - format: dep.format - }; -} diff --git a/test/fixtures/es-module-loaders/loader-with-too-many-args.mjs b/test/fixtures/es-module-loaders/loader-with-too-many-args.mjs deleted file mode 100644 index 95f40ec15d20..000000000000 --- a/test/fixtures/es-module-loaders/loader-with-too-many-args.mjs +++ /dev/null @@ -1,7 +0,0 @@ -export async function resolve(specifier, context, next) { - return next(specifier, context, 'resolve-extra-arg'); -} - -export async function load(url, context, next) { - return next(url, context, 'load-extra-arg'); -} diff --git a/test/fixtures/es-module-loaders/loader-worker-spawn.mjs b/test/fixtures/es-module-loaders/loader-worker-spawn.mjs deleted file mode 100644 index 2860d116e647..000000000000 --- a/test/fixtures/es-module-loaders/loader-worker-spawn.mjs +++ /dev/null @@ -1,7 +0,0 @@ -import { Worker } from 'worker_threads'; -import { createRequire } from 'module'; - -const require = createRequire(import.meta.url); -const fixtures = require('../../common/fixtures.js'); - -new Worker(fixtures.path('empty.js')).unref(); diff --git a/test/fixtures/es-module-loaders/missing-dynamic-instantiate-hook.mjs b/test/fixtures/es-module-loaders/missing-dynamic-instantiate-hook.mjs deleted file mode 100644 index 5d61d81bc31e..000000000000 --- a/test/fixtures/es-module-loaders/missing-dynamic-instantiate-hook.mjs +++ /dev/null @@ -1,17 +0,0 @@ -export function resolve(specifier, context, next) { - if (specifier === 'test') { - return { - url: 'file://' - }; - } - return next(specifier); -} - -export function getFormat(url, context, defaultGetFormat) { - if (url === 'file://') { - return { - format: 'dynamic' - } - } - return defaultGetFormat(url, context, defaultGetFormat); -} diff --git a/test/fixtures/es-module-loaders/mock-loader.mjs b/test/fixtures/es-module-loaders/mock-loader.mjs deleted file mode 100644 index 3bb349b53853..000000000000 --- a/test/fixtures/es-module-loaders/mock-loader.mjs +++ /dev/null @@ -1,133 +0,0 @@ -import { receiveMessageOnPort } from 'node:worker_threads'; -const mockedModuleExports = new Map(); -let currentMockVersion = 0; - -// These hooks enable code running on the application thread to -// swap module resolution results for mocking purposes. It uses this instead -// of import.meta so that CommonJS can still use the functionality. -// -// It does so by allowing non-mocked modules to live in normal URL cache -// locations but creates 'mock-facade:' URL cache location for every time a -// module location is mocked. Since a single URL can be mocked multiple -// times but it cannot be removed from the cache, `mock-facade:` URLs have a -// form of mock-facade:$VERSION:$REPLACING_URL with the parameters being URL -// percent encoded every time a module is resolved. So if a module for -// 'file:///app.js' is mocked it might look like -// 'mock-facade:12:file%3A%2F%2F%2Fapp.js'. This encoding is done to prevent -// problems like mocking URLs with special URL characters like '#' or '?' from -// accidentally being picked up as part of the 'mock-facade:' URL containing -// the mocked URL. -// -// NOTE: due to ESM spec, once a specifier has been resolved in a source text -// it cannot be changed. So things like the following DO NOT WORK: -// -// ```mjs -// import mock from 'test-esm-loader-mock'; // See test-esm-loader-mock.mjs -// mock('file:///app.js', {x:1}); -// const namespace1 = await import('file:///app.js'); -// namespace1.x; // 1 -// mock('file:///app.js', {x:2}); -// const namespace2 = await import('file:///app.js'); -// namespace2.x; // STILL 1, because this source text already set the specifier -// // for 'file:///app.js', a different specifier that resolves -// // to that could still get a new namespace though -// assert(namespace1 === namespace2); -// ``` - -/** - * @param param0 message from the application context - */ -function onPreloadPortMessage({ - mockVersion, resolved, exports -}) { - currentMockVersion = mockVersion; - mockedModuleExports.set(resolved, exports); -} - -/** @type {URL['href']} */ -let mainImportURL; -/** @type {MessagePort} */ -let preloadPort; -export async function initialize(data) { - ({ mainImportURL, port: preloadPort } = data); - - data.port.on('message', onPreloadPortMessage); -} - -/** - * Because Node.js internals use a separate MessagePort for cross-thread - * communication, there could be some messages pending that we should handle - * before continuing. - */ -function doDrainPort() { - let msg; - while (msg = receiveMessageOnPort(preloadPort)) { - onPreloadPortMessage(msg.message); - } -} - -// Rewrites node: loading to mock-facade: so that it can be intercepted -export async function resolve(specifier, context, defaultResolve) { - doDrainPort(); - const def = await defaultResolve(specifier, context); - if (context.parentURL?.startsWith('mock-facade:')) { - // Do nothing, let it get the "real" module - } else if (mockedModuleExports.has(def.url)) { - return { - shortCircuit: true, - url: `mock-facade:${currentMockVersion}:${encodeURIComponent(def.url)}` - }; - }; - return { - shortCircuit: true, - url: def.url, - }; -} - -export async function load(url, context, defaultLoad) { - doDrainPort(); - /** - * Mocked fake module, not going to be handled in default way so it - * generates the source text, then short circuits - */ - if (url.startsWith('mock-facade:')) { - const encodedTargetURL = url.slice(url.lastIndexOf(':') + 1); - return { - shortCircuit: true, - source: generateModule(encodedTargetURL), - format: 'module', - }; - } - return defaultLoad(url, context); -} - -/** - * Generate the source code for a mocked module. - * @param {string} encodedTargetURL the module being mocked - * @returns {string} - */ -function generateModule(encodedTargetURL) { - const exports = mockedModuleExports.get( - decodeURIComponent(encodedTargetURL) - ); - let body = [ - `import { mockedModules } from ${JSON.stringify(mainImportURL)};`, - 'export {};', - 'let mapping = {__proto__: null};', - `const mock = mockedModules.get(${JSON.stringify(encodedTargetURL)});`, - ]; - for (const [i, name] of Object.entries(exports)) { - let key = JSON.stringify(name); - body.push(`var _${i} = mock.namespace[${key}];`); - body.push(`Object.defineProperty(mapping, ${key}, { enumerable: true, set(v) {_${i} = v;}, get() {return _${i};} });`); - body.push(`export {_${i} as ${name}};`); - } - body.push(`mock.listeners.push(${ - () => { - for (var k in mapping) { - mapping[k] = mock.namespace[k]; - } - } - });`); - return body.join('\n'); -} diff --git a/test/fixtures/es-module-loaders/mock.mjs b/test/fixtures/es-module-loaders/mock.mjs deleted file mode 100644 index cb167f1d5204..000000000000 --- a/test/fixtures/es-module-loaders/mock.mjs +++ /dev/null @@ -1,70 +0,0 @@ -import { register } from 'node:module'; -import { MessageChannel } from 'node:worker_threads'; - - -const { port1, port2 } = new MessageChannel(); - -register('./mock-loader.mjs', import.meta.url, { - data: { - port: port2, - mainImportURL: import.meta.url, - }, - transferList: [port2], -}); - -/** - * This is the Map that saves *all* the mocked URL -> replacement Module - * mappings - * @type {Map} - */ -export const mockedModules = new Map(); -let mockVersion = 0; - -/** - * @param {string} resolved an absolute URL HREF string - * @param {object} replacementProperties an object to pick properties from - * to act as a module namespace - * @returns {object} a mutator object that can update the module namespace - * since we can't do something like old Object.observe - */ -export function mock(resolved, replacementProperties) { - const exportNames = Object.keys(replacementProperties); - const namespace = { __proto__: null }; - /** - * @type {Array<(name: string)=>void>} functions to call whenever an - * export name is updated - */ - const listeners = []; - for (const name of exportNames) { - let currentValueForPropertyName = replacementProperties[name]; - Object.defineProperty(namespace, name, { - __proto__: null, - enumerable: true, - get() { - return currentValueForPropertyName; - }, - set(v) { - currentValueForPropertyName = v; - for (const fn of listeners) { - try { - fn(name); - } catch { - /* noop */ - } - } - }, - }); - } - mockedModules.set(encodeURIComponent(resolved), { - namespace, - listeners, - }); - mockVersion++; - // Inform the loader that the `resolved` URL should now use the specific - // `mockVersion` and has export names of `exportNames` - // - // This allows the loader to generate a fake module for that version - // and names the next time it resolves a specifier to equal `resolved` - port1.postMessage({ mockVersion, resolved, exports: exportNames }); - return namespace; -} diff --git a/test/fixtures/es-module-loaders/never-settling-resolve-step/import.meta.never-resolve.mjs b/test/fixtures/es-module-loaders/never-settling-resolve-step/import.meta.never-resolve.mjs deleted file mode 100644 index 2bc389cc3e1d..000000000000 --- a/test/fixtures/es-module-loaders/never-settling-resolve-step/import.meta.never-resolve.mjs +++ /dev/null @@ -1,8 +0,0 @@ -import assert from 'node:assert'; -console.log('should be output'); - -assert.throws(() => { - import.meta.resolve('never-settle-resolve'); -}, { - code: 'ERR_ASYNC_LOADER_REQUEST_NEVER_SETTLED' -}); diff --git a/test/fixtures/es-module-loaders/never-settling-resolve-step/loader.mjs b/test/fixtures/es-module-loaders/never-settling-resolve-step/loader.mjs deleted file mode 100644 index c180583b4c74..000000000000 --- a/test/fixtures/es-module-loaders/never-settling-resolve-step/loader.mjs +++ /dev/null @@ -1,10 +0,0 @@ -export function resolve(specifier, context, next) { - if (specifier === 'never-settle-resolve') return new Promise(() => {}); - if (specifier === 'never-settle-load') return { __proto__: null, shortCircuit: true, url: 'never-settle:///' }; - return next(specifier, context); -} - -export function load(url, context, next) { - if (url === 'never-settle:///') return new Promise(() => {}); - return next(url, context); -} diff --git a/test/fixtures/es-module-loaders/never-settling-resolve-step/never-load.cjs b/test/fixtures/es-module-loaders/never-settling-resolve-step/never-load.cjs deleted file mode 100644 index cc81affe7d0d..000000000000 --- a/test/fixtures/es-module-loaders/never-settling-resolve-step/never-load.cjs +++ /dev/null @@ -1,7 +0,0 @@ -'use strict'; - -const neverSettlingDynamicImport = import('never-settle-load'); - -console.log('should be output'); - -neverSettlingDynamicImport.then(() => process.exit(1)); diff --git a/test/fixtures/es-module-loaders/never-settling-resolve-step/never-load.mjs b/test/fixtures/es-module-loaders/never-settling-resolve-step/never-load.mjs deleted file mode 100644 index 3dbf801c7605..000000000000 --- a/test/fixtures/es-module-loaders/never-settling-resolve-step/never-load.mjs +++ /dev/null @@ -1,5 +0,0 @@ -const neverSettlingDynamicImport = import('never-settle-load'); - -console.log('should be output'); - -await neverSettlingDynamicImport; diff --git a/test/fixtures/es-module-loaders/never-settling-resolve-step/never-resolve.cjs b/test/fixtures/es-module-loaders/never-settling-resolve-step/never-resolve.cjs deleted file mode 100644 index bf6387560b47..000000000000 --- a/test/fixtures/es-module-loaders/never-settling-resolve-step/never-resolve.cjs +++ /dev/null @@ -1,7 +0,0 @@ -'use strict'; - -const neverSettlingDynamicImport = import('never-settle-resolve'); - -console.log('should be output'); - -neverSettlingDynamicImport.then(() => process.exit(1)); diff --git a/test/fixtures/es-module-loaders/never-settling-resolve-step/never-resolve.mjs b/test/fixtures/es-module-loaders/never-settling-resolve-step/never-resolve.mjs deleted file mode 100644 index 806a131928c3..000000000000 --- a/test/fixtures/es-module-loaders/never-settling-resolve-step/never-resolve.mjs +++ /dev/null @@ -1,5 +0,0 @@ -const neverSettlingDynamicImport = import('never-settle-resolve'); - -console.log('should be output'); - -await neverSettlingDynamicImport; diff --git a/test/fixtures/es-module-loaders/never-settling-resolve-step/race.cjs b/test/fixtures/es-module-loaders/never-settling-resolve-step/race.cjs deleted file mode 100644 index 41884ab9d2e7..000000000000 --- a/test/fixtures/es-module-loaders/never-settling-resolve-step/race.cjs +++ /dev/null @@ -1,7 +0,0 @@ -'use strict'; - -Promise.race([ - import('never-settle-resolve'), - import('never-settle-load'), - import('node:process'), -]).then(result => console.log(result.default === process)); diff --git a/test/fixtures/es-module-loaders/never-settling-resolve-step/race.mjs b/test/fixtures/es-module-loaders/never-settling-resolve-step/race.mjs deleted file mode 100644 index 8213bbe59d44..000000000000 --- a/test/fixtures/es-module-loaders/never-settling-resolve-step/race.mjs +++ /dev/null @@ -1,7 +0,0 @@ -const result = await Promise.race([ - import('never-settle-resolve'), - import('never-settle-load'), - import('node:process'), -]); - -console.log(result.default === process); diff --git a/test/fixtures/es-module-loaders/node_modules/load/index.mjs b/test/fixtures/es-module-loaders/node_modules/load/index.mjs deleted file mode 100644 index db98e8d9f878..000000000000 --- a/test/fixtures/es-module-loaders/node_modules/load/index.mjs +++ /dev/null @@ -1 +0,0 @@ -export * from '../../loader-load-passthru.mjs' \ No newline at end of file diff --git a/test/fixtures/es-module-loaders/node_modules/load/package.json b/test/fixtures/es-module-loaders/node_modules/load/package.json deleted file mode 100644 index b6629e247888..000000000000 --- a/test/fixtures/es-module-loaders/node_modules/load/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "exports": "./index.mjs" -} diff --git a/test/fixtures/es-module-loaders/node_modules/resolve/index.mjs b/test/fixtures/es-module-loaders/node_modules/resolve/index.mjs deleted file mode 100644 index b3769d961359..000000000000 --- a/test/fixtures/es-module-loaders/node_modules/resolve/index.mjs +++ /dev/null @@ -1 +0,0 @@ -export * from '../../loader-resolve-passthru.mjs' \ No newline at end of file diff --git a/test/fixtures/es-module-loaders/node_modules/resolve/package.json b/test/fixtures/es-module-loaders/node_modules/resolve/package.json deleted file mode 100644 index b6629e247888..000000000000 --- a/test/fixtures/es-module-loaders/node_modules/resolve/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "exports": "./index.mjs" -} diff --git a/test/fixtures/es-module-loaders/not-found-assert-loader.mjs b/test/fixtures/es-module-loaders/not-found-assert-loader.mjs deleted file mode 100644 index bf66efbd0810..000000000000 --- a/test/fixtures/es-module-loaders/not-found-assert-loader.mjs +++ /dev/null @@ -1,16 +0,0 @@ -import assert from 'node:assert'; - -// A loader that asserts that the defaultResolve will throw "not found" -// (skipping the top-level main of course, and the built-in ones needed for run-worker). -let mainLoad = true; -export async function resolve(specifier, { importAttributes }, next) { - if (mainLoad || specifier === 'path' || specifier === 'worker_threads') { - mainLoad = false; - return next(specifier); - } - await assert.rejects(next(specifier), { code: 'ERR_MODULE_NOT_FOUND' }); - return { - url: 'node:fs', - importAttributes, - }; -} diff --git a/test/fixtures/es-module-loaders/preset-cjs-source.mjs b/test/fixtures/es-module-loaders/preset-cjs-source.mjs deleted file mode 100644 index 2cfa851a23f8..000000000000 --- a/test/fixtures/es-module-loaders/preset-cjs-source.mjs +++ /dev/null @@ -1,21 +0,0 @@ -export function resolve(specifier, context, next) { - if (specifier.endsWith('/no-such-file.cjs')) { - // Shortcut to avoid ERR_MODULE_NOT_FOUND for non-existing file, but keep the url for the load hook. - return { - shortCircuit: true, - url: specifier, - }; - } - return next(specifier); -} - -export function load(href, context, next) { - if (href.endsWith('.cjs')) { - return { - format: 'commonjs', - shortCircuit: true, - source: 'module.exports = "no .cjs file was read to get this source";', - }; - } - return next(href); -} diff --git a/test/fixtures/es-module-loaders/register-hooks-with-current-cwd-parent-url.mjs b/test/fixtures/es-module-loaders/register-hooks-with-current-cwd-parent-url.mjs deleted file mode 100644 index 580d092ab565..000000000000 --- a/test/fixtures/es-module-loaders/register-hooks-with-current-cwd-parent-url.mjs +++ /dev/null @@ -1,3 +0,0 @@ -import { register } from 'node:module'; -import { pathToFileURL } from 'node:url'; -register('./hooks-initialize.mjs', pathToFileURL('./')); diff --git a/test/fixtures/es-module-loaders/register-loader-with-url-parenturl.mjs b/test/fixtures/es-module-loaders/register-loader-with-url-parenturl.mjs deleted file mode 100644 index 1d82d2abc557..000000000000 --- a/test/fixtures/es-module-loaders/register-loader-with-url-parenturl.mjs +++ /dev/null @@ -1,11 +0,0 @@ -import {register} from 'node:module'; -import fixtures from '../../common/fixtures.js'; - -register( - fixtures.fileURL('es-module-loaders/loader-load-foo-or-42.mjs'), - new URL('data:'), -); - -import('node:os').then((result) => { - console.log(JSON.stringify(result)); -}); diff --git a/test/fixtures/es-module-loaders/register-loader.cjs b/test/fixtures/es-module-loaders/register-loader.cjs deleted file mode 100644 index 9e18dfa77e28..000000000000 --- a/test/fixtures/es-module-loaders/register-loader.cjs +++ /dev/null @@ -1,4 +0,0 @@ -const { register } = require('node:module'); -const fixtures = require('../../common/fixtures.js'); - -register(fixtures.fileURL('es-module-loaders', 'loader-resolve-passthru.mjs')); diff --git a/test/fixtures/es-module-loaders/register-loader.mjs b/test/fixtures/es-module-loaders/register-loader.mjs deleted file mode 100644 index f3cb2de8da3d..000000000000 --- a/test/fixtures/es-module-loaders/register-loader.mjs +++ /dev/null @@ -1,4 +0,0 @@ -import { register } from 'node:module'; -import fixtures from '../../common/fixtures.js'; - -register(fixtures.fileURL('es-module-loaders', 'loader-resolve-passthru.mjs')); diff --git a/test/fixtures/es-module-loaders/register-programmatically-loader-load.mjs b/test/fixtures/es-module-loaders/register-programmatically-loader-load.mjs deleted file mode 100644 index fe22dc7203e9..000000000000 --- a/test/fixtures/es-module-loaders/register-programmatically-loader-load.mjs +++ /dev/null @@ -1,4 +0,0 @@ -import * as fixtures from '../../common/fixtures.mjs'; -import { register } from 'node:module'; - -register(fixtures.fileURL('es-module-loaders', 'loader-load-passthru.mjs')); diff --git a/test/fixtures/es-module-loaders/register-programmatically-loader-resolve.mjs b/test/fixtures/es-module-loaders/register-programmatically-loader-resolve.mjs deleted file mode 100644 index 3ae8841da834..000000000000 --- a/test/fixtures/es-module-loaders/register-programmatically-loader-resolve.mjs +++ /dev/null @@ -1,3 +0,0 @@ -import { register } from 'node:module'; - -register('./loader-resolve-passthru.mjs', import.meta.url); diff --git a/test/fixtures/es-module-loaders/string-sources.mjs b/test/fixtures/es-module-loaders/string-sources.mjs deleted file mode 100644 index 39ad32c465fa..000000000000 --- a/test/fixtures/es-module-loaders/string-sources.mjs +++ /dev/null @@ -1,43 +0,0 @@ -const SOURCES = { - __proto__: null, - 'test:Array': ['1', '2'], // both `1,2` and `12` are valid ESM - 'test:ArrayBuffer': new ArrayBuffer(0), - 'test:BigInt64Array': new BigInt64Array(0), - 'test:BigUint64Array': new BigUint64Array(0), - 'test:Float32Array': new Float32Array(0), - 'test:Float64Array': new Float64Array(0), - 'test:Int8Array': new Int8Array(0), - 'test:Int16Array': new Int16Array(0), - 'test:Int32Array': new Int32Array(0), - 'test:null': null, - 'test:Object': {}, - 'test:SharedArrayBuffer': new SharedArrayBuffer(0), - 'test:string': '', - 'test:String': new String(''), - 'test:Uint8Array': new Uint8Array(0), - 'test:Uint8ClampedArray': new Uint8ClampedArray(0), - 'test:Uint16Array': new Uint16Array(0), - 'test:Uint32Array': new Uint32Array(0), - 'test:undefined': undefined, -} -export function resolve(specifier, context, next) { - if (specifier.startsWith('test:')) { - return { - importAttributes: context.importAttributes, - shortCircuit: true, - url: specifier, - }; - } - return next(specifier); -} - -export function load(href, context, next) { - if (href.startsWith('test:')) { - return { - format: 'module', - shortCircuit: true, - source: SOURCES[href], - }; - } - return next(href); -} diff --git a/test/fixtures/es-module-loaders/thenable-load-hook-rejected-no-arguments.mjs b/test/fixtures/es-module-loaders/thenable-load-hook-rejected-no-arguments.mjs deleted file mode 100644 index 5e008edecf7c..000000000000 --- a/test/fixtures/es-module-loaders/thenable-load-hook-rejected-no-arguments.mjs +++ /dev/null @@ -1,10 +0,0 @@ -export function load () { - let thenAlreadyAccessed = false; - return { - get then() { - if (thenAlreadyAccessed) throw new Error('must not call'); - thenAlreadyAccessed = true; - return (_, reject) => reject(); - } - }; -} diff --git a/test/fixtures/es-module-loaders/thenable-load-hook-rejected.mjs b/test/fixtures/es-module-loaders/thenable-load-hook-rejected.mjs deleted file mode 100644 index 926f6d0bebd6..000000000000 --- a/test/fixtures/es-module-loaders/thenable-load-hook-rejected.mjs +++ /dev/null @@ -1,10 +0,0 @@ -export function load () { - let thenAlreadyAccessed = false; - return { - get then() { - if (thenAlreadyAccessed) throw new Error('must not call'); - thenAlreadyAccessed = true; - return (_, reject) => reject(new Error('must crash the process')); - } - }; -} diff --git a/test/fixtures/es-module-loaders/thenable-load-hook.mjs b/test/fixtures/es-module-loaders/thenable-load-hook.mjs deleted file mode 100644 index 661f6e21ac69..000000000000 --- a/test/fixtures/es-module-loaders/thenable-load-hook.mjs +++ /dev/null @@ -1,10 +0,0 @@ -export function load(url, context, next) { - let thenAlreadyAccessed = false; - return { - get then() { - if (thenAlreadyAccessed) throw new Error('must not call'); - thenAlreadyAccessed = true; - return (resolve) => resolve(next(url, context)); - } - }; -} diff --git a/test/fixtures/es-modules/require-after-failed-import/dep.mjs b/test/fixtures/es-modules/require-after-failed-import/dep.mjs new file mode 100644 index 000000000000..efeee5db16c0 --- /dev/null +++ b/test/fixtures/es-modules/require-after-failed-import/dep.mjs @@ -0,0 +1 @@ +export const value = 1; diff --git a/test/fixtures/es-modules/require-after-failed-import/mismatched.mjs b/test/fixtures/es-modules/require-after-failed-import/mismatched.mjs new file mode 100644 index 000000000000..9e3f57a169a0 --- /dev/null +++ b/test/fixtures/es-modules/require-after-failed-import/mismatched.mjs @@ -0,0 +1,2 @@ +import { missing } from './dep.mjs'; +export { missing }; diff --git a/test/fixtures/es-modules/require-esm-in-cjs-cache/hooks.js b/test/fixtures/es-modules/require-esm-in-cjs-cache/hooks.js deleted file mode 100644 index f33a40fba773..000000000000 --- a/test/fixtures/es-modules/require-esm-in-cjs-cache/hooks.js +++ /dev/null @@ -1,12 +0,0 @@ -import { readFileSync } from 'node:fs'; -import { fileURLToPath } from 'node:url'; - -export async function load(url, context, nextLoad) { - const result = await nextLoad(url, context); - - if (result.format === 'commonjs' && !result.source) { - result.source = readFileSync(fileURLToPath(url), 'utf8'); - } - - return result; -} diff --git a/test/fixtures/es-modules/require-esm-in-cjs-cache/instrument.js b/test/fixtures/es-modules/require-esm-in-cjs-cache/instrument.js deleted file mode 100644 index 1c99b7e9c759..000000000000 --- a/test/fixtures/es-modules/require-esm-in-cjs-cache/instrument.js +++ /dev/null @@ -1,3 +0,0 @@ -import * as mod from 'node:module'; - -mod.register(new URL('hooks.js', import.meta.url).toString()); diff --git a/test/fixtures/es-modules/require-esm-throws-with-loaders.js b/test/fixtures/es-modules/require-esm-throws-with-loaders.js deleted file mode 100644 index 79c5cf96c141..000000000000 --- a/test/fixtures/es-modules/require-esm-throws-with-loaders.js +++ /dev/null @@ -1,8 +0,0 @@ -'use strict'; -const { readFile, __fromLoader } = require('fs'); -const assert = require('assert'); - -assert.throws(() => require('./test-esm-ok.mjs'), { code: 'ERR_REQUIRE_ESM' }); - -assert(readFile); -assert(__fromLoader); diff --git a/test/fixtures/es-modules/worker-condition/hooks-worker.mjs b/test/fixtures/es-modules/worker-condition/hooks-worker.mjs deleted file mode 100644 index ee9cd45bd6c5..000000000000 --- a/test/fixtures/es-modules/worker-condition/hooks-worker.mjs +++ /dev/null @@ -1,6 +0,0 @@ -import { register } from 'node:module'; - -register('./hooks.mjs', import.meta.url); - -const { default: value } = await import('pkg'); -postMessage(value); diff --git a/test/fixtures/es-modules/worker-condition/hooks.mjs b/test/fixtures/es-modules/worker-condition/hooks.mjs deleted file mode 100644 index 8505bafc98db..000000000000 --- a/test/fixtures/es-modules/worker-condition/hooks.mjs +++ /dev/null @@ -1,6 +0,0 @@ -export async function resolve(specifier, context, nextResolve) { - if (specifier === 'pkg' && !context.conditions.includes('worker')) { - throw new Error(`missing worker condition: ${context.conditions}`); - } - return nextResolve(specifier, context); -} diff --git a/test/fixtures/loader-is-internal-thread.js b/test/fixtures/loader-is-internal-thread.js deleted file mode 100644 index d97328bc2075..000000000000 --- a/test/fixtures/loader-is-internal-thread.js +++ /dev/null @@ -1,3 +0,0 @@ -const { isInternalThread } = require('node:worker_threads'); - -console.log(`isInternalThread: ${isInternalThread}`); \ No newline at end of file diff --git a/test/fixtures/module-hooks/get-stats.js b/test/fixtures/module-hooks/get-stats.js deleted file mode 100644 index fa5869a455ce..000000000000 --- a/test/fixtures/module-hooks/get-stats.js +++ /dev/null @@ -1,20 +0,0 @@ -'use strict'; - -const path = require('path'); - -// Adapted from https://github.com/watson/module-details-from-path/blob/master/index.js -// used by require-in-the-middle to check the logic is still compatible with our new hooks. -exports.getStats = function getStats(filepath) { - const segments = filepath.split(path.sep); - const index = segments.lastIndexOf('node_modules'); - if (index === -1) return {}; - if (!segments[index + 1]) return {}; - const scoped = segments[index + 1][0] === '@'; - const name = scoped ? segments[index + 1] + '/' + segments[index + 2] : segments[index + 1]; - const offset = scoped ? 3 : 2; - return { - name: name, - basedir: segments.slice(0, index + offset).join(path.sep), - path: segments.slice(index + offset).join(path.sep) - } -}; diff --git a/test/fixtures/module-hooks/logger-async-hooks.mjs b/test/fixtures/module-hooks/logger-async-hooks.mjs deleted file mode 100644 index f26d0ce2ae3e..000000000000 --- a/test/fixtures/module-hooks/logger-async-hooks.mjs +++ /dev/null @@ -1,19 +0,0 @@ -import fs from 'node:fs'; - -export async function resolve(specifier, context, nextResolve) { - fs.writeSync(1, `resolve ${specifier} from ${context.parentURL}\n`); - const result = await nextResolve(specifier, context); - result.shortCircuit = true; - return result; -} - -export async function load(url, context, nextLoad) { - fs.writeSync(1, `load ${url}\n`); - const result = await nextLoad(url, context); - result.shortCircuit = true; - // Handle the async loader hook quirk where source can be nullish for CommonJS. - // If this returns that nullish value verbatim, the `require` in imported - // CJS won't get the hook invoked. - result.source ??= fs.readFileSync(new URL(url), 'utf8'); - return result; -} diff --git a/test/fixtures/module-hooks/register-loader-in-sequence.mjs b/test/fixtures/module-hooks/register-loader-in-sequence.mjs deleted file mode 100644 index c129fcfc1809..000000000000 --- a/test/fixtures/module-hooks/register-loader-in-sequence.mjs +++ /dev/null @@ -1,11 +0,0 @@ -import {register} from 'node:module'; -import fixtures from '../../common/fixtures.js'; - -console.log('result 1', register( - fixtures.fileURL('es-module-loaders/hooks-initialize.mjs') -)); -console.log('result 2', register( - fixtures.fileURL('es-module-loaders/hooks-initialize.mjs') -)); - -await import('node:os'); diff --git a/test/fixtures/module-hooks/register-loader-initialize-never-settling.mjs b/test/fixtures/module-hooks/register-loader-initialize-never-settling.mjs deleted file mode 100644 index 13091effa970..000000000000 --- a/test/fixtures/module-hooks/register-loader-initialize-never-settling.mjs +++ /dev/null @@ -1,8 +0,0 @@ -import {register} from 'node:module'; -import fixtures from '../../common/fixtures.js'; - -try { - register(fixtures.fileURL('es-module-loaders/loader-initialize-never-settling.mjs')); -} catch (e) { - console.log('caught', e.code); -} diff --git a/test/fixtures/module-hooks/register-loader-with-cjs.cjs b/test/fixtures/module-hooks/register-loader-with-cjs.cjs deleted file mode 100644 index 2cd6c666bd14..000000000000 --- a/test/fixtures/module-hooks/register-loader-with-cjs.cjs +++ /dev/null @@ -1,14 +0,0 @@ -'use strict'; -const {register} = require('node:module'); -const fixtures = require('../../common/fixtures.js'); - -register( - fixtures.fileURL('es-module-loaders/hooks-initialize.mjs'), -); -register( - fixtures.fileURL('es-module-loaders/loader-load-foo-or-42.mjs'), -); - -import('node:os').then((result) => { - console.log(JSON.stringify(result)); -}); diff --git a/test/fixtures/module-hooks/register-loader-with-ports.mjs b/test/fixtures/module-hooks/register-loader-with-ports.mjs deleted file mode 100644 index 45e125f96a20..000000000000 --- a/test/fixtures/module-hooks/register-loader-with-ports.mjs +++ /dev/null @@ -1,22 +0,0 @@ -import {MessageChannel} from 'node:worker_threads'; -import {register} from 'node:module'; -import {once} from 'node:events'; -import fixtures from '../../common/fixtures.js'; - -const {port1, port2} = new MessageChannel(); -port1.on('message', (msg) => { - console.log('message', msg); -}); -const result = register( - fixtures.fileURL('es-module-loaders/hooks-initialize-port.mjs'), - {data: port2, transferList: [port2]}, -); -console.log('register', result); - -const timeout = setTimeout(() => {}, 2**31 - 1); // to keep the process alive. -await Promise.all([ - once(port1, 'message').then(() => once(port1, 'message')), - import('node:os'), -]); -clearTimeout(timeout); -port1.close(); diff --git a/test/fixtures/module-hooks/require-esm-in-hooks-thread/dep.mjs b/test/fixtures/module-hooks/require-esm-in-hooks-thread/dep.mjs deleted file mode 100644 index 3d34ee667697..000000000000 --- a/test/fixtures/module-hooks/require-esm-in-hooks-thread/dep.mjs +++ /dev/null @@ -1 +0,0 @@ -export const value = 'dep:'; diff --git a/test/fixtures/module-hooks/require-esm-in-hooks-thread/esm-with-import.mjs b/test/fixtures/module-hooks/require-esm-in-hooks-thread/esm-with-import.mjs deleted file mode 100644 index 1aad2b7a98c1..000000000000 --- a/test/fixtures/module-hooks/require-esm-in-hooks-thread/esm-with-import.mjs +++ /dev/null @@ -1,4 +0,0 @@ -import { sep } from 'node:path'; -import { value } from './dep.mjs'; - -export const result = `${value}${typeof sep}`; diff --git a/test/fixtures/module-hooks/require-esm-in-hooks-thread/hooks.mjs b/test/fixtures/module-hooks/require-esm-in-hooks-thread/hooks.mjs deleted file mode 100644 index b80b4ef959b7..000000000000 --- a/test/fixtures/module-hooks/require-esm-in-hooks-thread/hooks.mjs +++ /dev/null @@ -1,9 +0,0 @@ -// Loaded on the module.register() hooks thread. It defines no hooks: require()-ing -// an ES module with imports while the hooks module is being loaded is enough. -import { createRequire } from 'node:module'; - -const require = createRequire(import.meta.url); -const { result } = require('./esm-with-import.mjs'); -if (result !== 'dep:string') { - throw new Error(`Unexpected result: ${result}`); -} diff --git a/test/fixtures/module-hooks/require-resolve-paths-caller.js b/test/fixtures/module-hooks/require-resolve-paths-caller.js deleted file mode 100644 index f18b1f377735..000000000000 --- a/test/fixtures/module-hooks/require-resolve-paths-caller.js +++ /dev/null @@ -1,13 +0,0 @@ -// Fixture CJS file that calls require.resolve with the paths option. -'use strict'; -const path = require('path'); -const fixturesDir = path.resolve(__dirname, '..', '..'); -const nodeModules = path.join(fixturesDir, 'node_modules'); - -// Use the paths option to resolve 'bar' from the fixtures node_modules. -const resolved = require.resolve('bar', { paths: [fixturesDir] }); -const expected = path.join(nodeModules, 'bar.js'); -if (resolved !== expected) { - throw new Error(`Expected ${expected}, got ${resolved}`); -} -module.exports = { resolved }; diff --git a/test/fixtures/module-hooks/sync-and-async/app.js b/test/fixtures/module-hooks/sync-and-async/app.js deleted file mode 100644 index c14477b70f71..000000000000 --- a/test/fixtures/module-hooks/sync-and-async/app.js +++ /dev/null @@ -1,2 +0,0 @@ -console.log('Hello world'); - diff --git a/test/fixtures/module-hooks/sync-and-async/async-customize-loader.js b/test/fixtures/module-hooks/sync-and-async/async-customize-loader.js deleted file mode 100644 index ac961f0b97a3..000000000000 --- a/test/fixtures/module-hooks/sync-and-async/async-customize-loader.js +++ /dev/null @@ -1,10 +0,0 @@ -export async function load(url, context, nextLoad) { - if (url.endsWith('app.js')) { - return { - shortCircuit: true, - format: 'module', - source: 'console.log("customized by async hook");', - }; - } - return nextLoad(url, context); -} diff --git a/test/fixtures/module-hooks/sync-and-async/async-customize.js b/test/fixtures/module-hooks/sync-and-async/async-customize.js deleted file mode 100644 index 78bf7fc5564b..000000000000 --- a/test/fixtures/module-hooks/sync-and-async/async-customize.js +++ /dev/null @@ -1,3 +0,0 @@ -import { register } from 'node:module'; - -register(new URL('async-customize-loader.js', import.meta.url)); diff --git a/test/fixtures/module-hooks/sync-and-async/async-forward-loader.js b/test/fixtures/module-hooks/sync-and-async/async-forward-loader.js deleted file mode 100644 index 4a1ced7e8dbf..000000000000 --- a/test/fixtures/module-hooks/sync-and-async/async-forward-loader.js +++ /dev/null @@ -1,3 +0,0 @@ -export async function load(url, context, nextLoad) { - return nextLoad(url, context); -} diff --git a/test/fixtures/module-hooks/sync-and-async/async-forward.js b/test/fixtures/module-hooks/sync-and-async/async-forward.js deleted file mode 100644 index fd10aa6df898..000000000000 --- a/test/fixtures/module-hooks/sync-and-async/async-forward.js +++ /dev/null @@ -1,3 +0,0 @@ -import { register } from 'node:module'; - -register(new URL('async-forward-loader.js', import.meta.url)); diff --git a/test/fixtures/module-hooks/sync-and-async/sync-customize.js b/test/fixtures/module-hooks/sync-and-async/sync-customize.js deleted file mode 100644 index 90d63db1c7cc..000000000000 --- a/test/fixtures/module-hooks/sync-and-async/sync-customize.js +++ /dev/null @@ -1,14 +0,0 @@ -import { registerHooks } from 'node:module'; - -registerHooks({ - load(url, context, nextLoad) { - if (url.endsWith('app.js')) { - return { - shortCircuit: true, - format: 'module', - source: 'console.log("customized by sync hook")', - }; - } - return nextLoad(url, context); - }, -}); diff --git a/test/fixtures/module-hooks/sync-and-async/sync-forward.js b/test/fixtures/module-hooks/sync-and-async/sync-forward.js deleted file mode 100644 index 2688a240dc88..000000000000 --- a/test/fixtures/module-hooks/sync-and-async/sync-forward.js +++ /dev/null @@ -1,7 +0,0 @@ -import { registerHooks } from 'node:module'; - -registerHooks({ - load(url, context, nextLoad) { - return nextLoad(url, context); - }, -}); diff --git a/test/fixtures/print-delayed.js b/test/fixtures/print-delayed.js deleted file mode 100644 index 42eb45615b2a..000000000000 --- a/test/fixtures/print-delayed.js +++ /dev/null @@ -1,3 +0,0 @@ -setTimeout(() => { - console.log('delayed'); -}, 100); diff --git a/test/module-hooks/test-async-loader-hooks-called-with-expected-args.mjs b/test/module-hooks/test-async-loader-hooks-called-with-expected-args.mjs deleted file mode 100644 index 195bf02bb6fd..000000000000 --- a/test/module-hooks/test-async-loader-hooks-called-with-expected-args.mjs +++ /dev/null @@ -1,28 +0,0 @@ -// Test that loader hooks are called with all expected arguments -import '../common/index.mjs'; -import assert from 'node:assert'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndAssert } from '../common/child_process.js'; - -spawnSyncAndAssert( - execPath, - [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/hooks-input.mjs'), - fixtures.path('es-modules/json-modules.mjs'), - ], - { - stdout(output) { - const lines = output.split('\n'); - assert.match(lines[0], /{"url":"file:\/\/\/.*\/json-modules\.mjs","format":"test","shortCircuit":true}/); - assert.match(lines[1], /{"source":{"type":"Buffer","data":\[.*\]},"format":"module","shortCircuit":true}/); - assert.match(lines[2], /{"url":"file:\/\/\/.*\/experimental\.json","format":"test","shortCircuit":true}/); - assert.match(lines[3], /{"source":{"type":"Buffer","data":\[.*\]},"format":"json","shortCircuit":true}/); - assert.strictEqual(lines.length, 4); - }, - stderr: '', - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-called-with-register.mjs b/test/module-hooks/test-async-loader-hooks-called-with-register.mjs deleted file mode 100644 index d1f9181240d7..000000000000 --- a/test/module-hooks/test-async-loader-hooks-called-with-register.mjs +++ /dev/null @@ -1,31 +0,0 @@ -// Test that loader hooks are called with all expected arguments using register function -import '../common/index.mjs'; -import assert from 'node:assert'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndAssert } from '../common/child_process.js'; - -spawnSyncAndAssert( - execPath, - [ - '--no-warnings', - '--experimental-loader=data:text/javascript,', - '--input-type=module', - '--eval', - "import { register } from 'node:module';" + - `register(${JSON.stringify(fixtures.fileURL('es-module-loaders/hooks-input.mjs'))});` + - `await import(${JSON.stringify(fixtures.fileURL('es-modules/json-modules.mjs'))});`, - ], - { - stdout(output) { - const lines = output.split('\n'); - assert.match(lines[0], /{"url":"file:\/\/\/.*\/json-modules\.mjs","format":"test","shortCircuit":true}/); - assert.match(lines[1], /{"source":{"type":"Buffer","data":\[.*\]},"format":"module","shortCircuit":true}/); - assert.match(lines[2], /{"url":"file:\/\/\/.*\/experimental\.json","format":"test","shortCircuit":true}/); - assert.match(lines[3], /{"source":{"type":"Buffer","data":\[.*\]},"format":"json","shortCircuit":true}/); - assert.strictEqual(lines.length, 4); - }, - stderr: '', - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-globalpreload-no-warning-with-initialize.mjs b/test/module-hooks/test-async-loader-hooks-globalpreload-no-warning-with-initialize.mjs deleted file mode 100644 index 54501748bcea..000000000000 --- a/test/module-hooks/test-async-loader-hooks-globalpreload-no-warning-with-initialize.mjs +++ /dev/null @@ -1,21 +0,0 @@ -// Test that `globalPreload` should not emit warning when `initialize` is supplied - -import '../common/index.mjs'; -import assert from 'node:assert'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndAssert } from '../common/child_process.js'; - -spawnSyncAndAssert( - execPath, - [ - '--experimental-loader', - fixtures.fileURL('es-module-loaders/loader-globalpreload-and-initialize.mjs'), - fixtures.path('empty.js'), - ], - { - stderr(output) { - assert.doesNotMatch(output, /`globalPreload` has been removed; use `initialize` instead/); - }, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-globalpreload-warning.mjs b/test/module-hooks/test-async-loader-hooks-globalpreload-warning.mjs deleted file mode 100644 index 36a2c0089a91..000000000000 --- a/test/module-hooks/test-async-loader-hooks-globalpreload-warning.mjs +++ /dev/null @@ -1,23 +0,0 @@ -// Test that `globalPreload` should emit warning -import '../common/index.mjs'; -import assert from 'node:assert'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndAssert } from '../common/child_process.js'; - -spawnSyncAndAssert( - execPath, - [ - '--experimental-loader', - fixtures.fileURL('es-module-loaders/loader-globalpreload-only.mjs'), - '--experimental-loader', - fixtures.fileURL('es-module-loaders/loader-globalpreload-with-return.mjs'), - fixtures.path('empty.js'), - ], - { - stderr(output) { - const matches = output.match(/`globalPreload` has been removed; use `initialize` instead/g); - assert.strictEqual(matches.length, 1); - }, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-initialize-in-sequence.mjs b/test/module-hooks/test-async-loader-hooks-initialize-in-sequence.mjs deleted file mode 100644 index bc973f587845..000000000000 --- a/test/module-hooks/test-async-loader-hooks-initialize-in-sequence.mjs +++ /dev/null @@ -1,21 +0,0 @@ -// Test that `initialize` should execute in sequence -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndAssert } from '../common/child_process.js'; - -spawnSyncAndAssert( - execPath, - [ - '--no-warnings', - fixtures.path('module-hooks/register-loader-in-sequence.mjs'), - ], - { - stdout: 'hooks initialize 1\n' + - 'result 1 undefined\n' + - 'hooks initialize 2\n' + - 'result 2 undefined', - trim: true, - stderr: '', - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-initialize-invoke.mjs b/test/module-hooks/test-async-loader-hooks-initialize-invoke.mjs deleted file mode 100644 index 72dc87c12e35..000000000000 --- a/test/module-hooks/test-async-loader-hooks-initialize-invoke.mjs +++ /dev/null @@ -1,22 +0,0 @@ -// Test that `initialize` should be invoked correctly -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndAssert } from '../common/child_process.js'; - -spawnSyncAndAssert( - execPath, - [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/hooks-initialize.mjs'), - '--input-type=module', - '--eval', - 'import os from "node:os";', - ], - { - stdout: 'hooks initialize 1', - stderr: '', - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-initialize-never-settling.mjs b/test/module-hooks/test-async-loader-hooks-initialize-never-settling.mjs deleted file mode 100644 index d1c12238ef0c..000000000000 --- a/test/module-hooks/test-async-loader-hooks-initialize-never-settling.mjs +++ /dev/null @@ -1,18 +0,0 @@ -// Test that `initialize` returning never-settling promise should be handled -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndAssert } from '../common/child_process.js'; - -spawnSyncAndAssert( - execPath, - [ - '--no-warnings', - fixtures.path('module-hooks/register-loader-initialize-never-settling.mjs'), - ], - { - stdout: 'caught ERR_ASYNC_LOADER_REQUEST_NEVER_SETTLED', - stderr: '', - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-initialize-process-exit.mjs b/test/module-hooks/test-async-loader-hooks-initialize-process-exit.mjs deleted file mode 100644 index d9476af27e8f..000000000000 --- a/test/module-hooks/test-async-loader-hooks-initialize-process-exit.mjs +++ /dev/null @@ -1,23 +0,0 @@ -// Test that it should be fine to call `process.exit` from a `initialize` hook -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndExit } from '../common/child_process.js'; - -spawnSyncAndExit( - execPath, - [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/loader-initialize-exit.mjs'), - '--input-type=module', - '--eval', - 'import "node:os"', - ], - { - status: 42, - signal: null, - stdout: '', - stderr: '', - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-initialize-rejecting.mjs b/test/module-hooks/test-async-loader-hooks-initialize-rejecting.mjs deleted file mode 100644 index e5d2a62fbc86..000000000000 --- a/test/module-hooks/test-async-loader-hooks-initialize-rejecting.mjs +++ /dev/null @@ -1,24 +0,0 @@ -// Test that `initialize` returning rejecting promise should be handled -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndExit } from '../common/child_process.js'; - -spawnSyncAndExit( - execPath, - [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/loader-initialize-rejecting.mjs'), - '--input-type=module', - '--eval', - 'import "node:os"', - ], - { - status: 1, - signal: null, - stdout: '', - stderr: /undefined$/m, - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-initialize-throw-null.mjs b/test/module-hooks/test-async-loader-hooks-initialize-throw-null.mjs deleted file mode 100644 index 69844e31ae91..000000000000 --- a/test/module-hooks/test-async-loader-hooks-initialize-throw-null.mjs +++ /dev/null @@ -1,24 +0,0 @@ -// Test that `initialize` throwing null should be handled -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndExit } from '../common/child_process.js'; - -spawnSyncAndExit( - execPath, - [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/loader-initialize-throw-null.mjs'), - '--input-type=module', - '--eval', - 'import "node:os"', - ], - { - status: 1, - signal: null, - stdout: '', - stderr: /null$/m, - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-mixed-opt-in.mjs b/test/module-hooks/test-async-loader-hooks-mixed-opt-in.mjs deleted file mode 100644 index 398df25d7bbe..000000000000 --- a/test/module-hooks/test-async-loader-hooks-mixed-opt-in.mjs +++ /dev/null @@ -1,20 +0,0 @@ -// Test that loader should handle mixed of opt-in modules and non-opt-in ones -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndAssert } from '../common/child_process.js'; - -spawnSyncAndAssert( - execPath, - [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/loader-mixed-opt-in.mjs'), - 'entry-point', - ], - { - stdout: 'Hello', - stderr: '', - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-never-settling-import-meta-resolve.mjs b/test/module-hooks/test-async-loader-hooks-never-settling-import-meta-resolve.mjs deleted file mode 100644 index 35a4f7a566e5..000000000000 --- a/test/module-hooks/test-async-loader-hooks-never-settling-import-meta-resolve.mjs +++ /dev/null @@ -1,20 +0,0 @@ -// Test import.meta.resolve of a never-settling resolve should throw in ESM files -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndAssert } from '../common/child_process.js'; - -spawnSyncAndAssert( - execPath, - [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/never-settling-resolve-step/loader.mjs'), - fixtures.path('es-module-loaders/never-settling-resolve-step/import.meta.never-resolve.mjs'), - ], - { - stdout: /^should be output$/, - stderr: '', - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-never-settling-load-cjs.mjs b/test/module-hooks/test-async-loader-hooks-never-settling-load-cjs.mjs deleted file mode 100644 index 05401b9080b3..000000000000 --- a/test/module-hooks/test-async-loader-hooks-never-settling-load-cjs.mjs +++ /dev/null @@ -1,20 +0,0 @@ -// Test never-settling load in CJS files -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndAssert } from '../common/child_process.js'; - -spawnSyncAndAssert( - execPath, - [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/never-settling-resolve-step/loader.mjs'), - fixtures.path('es-module-loaders/never-settling-resolve-step/never-load.cjs'), - ], - { - stdout: /^should be output$/, - stderr: '', - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-never-settling-load-esm-no-warning.mjs b/test/module-hooks/test-async-loader-hooks-never-settling-load-esm-no-warning.mjs deleted file mode 100644 index dbadecc32876..000000000000 --- a/test/module-hooks/test-async-loader-hooks-never-settling-load-esm-no-warning.mjs +++ /dev/null @@ -1,22 +0,0 @@ -// Test top-level await of a never-settling load without warning in ESM files -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndExit } from '../common/child_process.js'; - -spawnSyncAndExit( - execPath, - [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/never-settling-resolve-step/loader.mjs'), - fixtures.path('es-module-loaders/never-settling-resolve-step/never-load.mjs'), - ], - { - status: 13, - signal: null, - stdout: /^should be output$/, - stderr: '', - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-never-settling-load-esm-with-warning.mjs b/test/module-hooks/test-async-loader-hooks-never-settling-load-esm-with-warning.mjs deleted file mode 100644 index d4b30ae23f86..000000000000 --- a/test/module-hooks/test-async-loader-hooks-never-settling-load-esm-with-warning.mjs +++ /dev/null @@ -1,21 +0,0 @@ -// Test top-level await of a never-settling load with warning in ESM files -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndExit } from '../common/child_process.js'; - -spawnSyncAndExit( - execPath, - [ - '--experimental-loader', - fixtures.fileURL('es-module-loaders/never-settling-resolve-step/loader.mjs'), - fixtures.path('es-module-loaders/never-settling-resolve-step/never-load.mjs'), - ], - { - status: 13, - signal: null, - stdout: /^should be output$/, - stderr: /Warning: Detected unsettled top-level await at.+never-load\.mjs:5/, - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-never-settling-race-cjs.mjs b/test/module-hooks/test-async-loader-hooks-never-settling-race-cjs.mjs deleted file mode 100644 index 1de677f88838..000000000000 --- a/test/module-hooks/test-async-loader-hooks-never-settling-race-cjs.mjs +++ /dev/null @@ -1,20 +0,0 @@ -// Test race of never-settling hooks in CJS files -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndAssert } from '../common/child_process.js'; - -spawnSyncAndAssert( - execPath, - [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/never-settling-resolve-step/loader.mjs'), - fixtures.path('es-module-loaders/never-settling-resolve-step/race.cjs'), - ], - { - stdout: /^true$/, - stderr: '', - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-never-settling-race-esm.mjs b/test/module-hooks/test-async-loader-hooks-never-settling-race-esm.mjs deleted file mode 100644 index e662dc43210d..000000000000 --- a/test/module-hooks/test-async-loader-hooks-never-settling-race-esm.mjs +++ /dev/null @@ -1,20 +0,0 @@ -// Test top-level await of a race of never-settling hooks in ESM files -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndAssert } from '../common/child_process.js'; - -spawnSyncAndAssert( - execPath, - [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/never-settling-resolve-step/loader.mjs'), - fixtures.path('es-module-loaders/never-settling-resolve-step/race.mjs'), - ], - { - stdout: /^true$/, - stderr: '', - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-never-settling-resolve-cjs.mjs b/test/module-hooks/test-async-loader-hooks-never-settling-resolve-cjs.mjs deleted file mode 100644 index 1469a7b313cc..000000000000 --- a/test/module-hooks/test-async-loader-hooks-never-settling-resolve-cjs.mjs +++ /dev/null @@ -1,20 +0,0 @@ -// Test never-settling resolve in CJS files -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndAssert } from '../common/child_process.js'; - -spawnSyncAndAssert( - execPath, - [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/never-settling-resolve-step/loader.mjs'), - fixtures.path('es-module-loaders/never-settling-resolve-step/never-resolve.cjs'), - ], - { - stdout: /^should be output$/, - stderr: '', - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-never-settling-resolve-esm-no-warning.mjs b/test/module-hooks/test-async-loader-hooks-never-settling-resolve-esm-no-warning.mjs deleted file mode 100644 index 43a7a98127c8..000000000000 --- a/test/module-hooks/test-async-loader-hooks-never-settling-resolve-esm-no-warning.mjs +++ /dev/null @@ -1,22 +0,0 @@ -// Test top-level await of a never-settling resolve without warning in ESM files -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndExit } from '../common/child_process.js'; - -spawnSyncAndExit( - execPath, - [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/never-settling-resolve-step/loader.mjs'), - fixtures.path('es-module-loaders/never-settling-resolve-step/never-resolve.mjs'), - ], - { - status: 13, - signal: null, - stdout: /^should be output$/, - stderr: '', - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-never-settling-resolve-esm-with-warning.mjs b/test/module-hooks/test-async-loader-hooks-never-settling-resolve-esm-with-warning.mjs deleted file mode 100644 index 1a4c40c8bb96..000000000000 --- a/test/module-hooks/test-async-loader-hooks-never-settling-resolve-esm-with-warning.mjs +++ /dev/null @@ -1,21 +0,0 @@ -// Test top-level await of a never-settling resolve with warning in ESM files -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndExit } from '../common/child_process.js'; - -spawnSyncAndExit( - execPath, - [ - '--experimental-loader', - fixtures.fileURL('es-module-loaders/never-settling-resolve-step/loader.mjs'), - fixtures.path('es-module-loaders/never-settling-resolve-step/never-resolve.mjs'), - ], - { - status: 13, - signal: null, - stdout: /^should be output$/, - stderr: /Warning: Detected unsettled top-level await at.+never-resolve\.mjs:5/, - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-no-leak-internals.mjs b/test/module-hooks/test-async-loader-hooks-no-leak-internals.mjs deleted file mode 100644 index 781aed8601f5..000000000000 --- a/test/module-hooks/test-async-loader-hooks-no-leak-internals.mjs +++ /dev/null @@ -1,20 +0,0 @@ -// Test that loader hooks should not leak internals or expose import.meta.resolve -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndAssert } from '../common/child_process.js'; - -spawnSyncAndAssert( - execPath, - [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/loader-edge-cases.mjs'), - fixtures.path('empty.js'), - ], - { - stdout: '', - stderr: '', - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-process-exit-async.mjs b/test/module-hooks/test-async-loader-hooks-process-exit-async.mjs deleted file mode 100644 index af3b939bcf8c..000000000000 --- a/test/module-hooks/test-async-loader-hooks-process-exit-async.mjs +++ /dev/null @@ -1,24 +0,0 @@ -// Test that it should be fine to call `process.exit` from a custom async hook -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndExit } from '../common/child_process.js'; - -spawnSyncAndExit( - execPath, - [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/loader-exit-on-load.mjs'), - '--input-type=module', - '--eval', - 'import "data:exit"', - ], - { - status: 42, - signal: null, - stdout: '', - stderr: '', - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-process-exit-sync.mjs b/test/module-hooks/test-async-loader-hooks-process-exit-sync.mjs deleted file mode 100644 index 64c079b90b75..000000000000 --- a/test/module-hooks/test-async-loader-hooks-process-exit-sync.mjs +++ /dev/null @@ -1,24 +0,0 @@ -// Test that it should be fine to call `process.exit` from a custom sync hook -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndExit } from '../common/child_process.js'; - -spawnSyncAndExit( - execPath, - [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/loader-exit-on-resolve.mjs'), - '--input-type=module', - '--eval', - 'import "data:text/javascript,import.meta.resolve(%22exit:%22)"', - ], - { - status: 42, - signal: null, - stdout: '', - stderr: '', - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-process-exit-top-level.mjs b/test/module-hooks/test-async-loader-hooks-process-exit-top-level.mjs deleted file mode 100644 index a25c0013c507..000000000000 --- a/test/module-hooks/test-async-loader-hooks-process-exit-top-level.mjs +++ /dev/null @@ -1,22 +0,0 @@ -// Test that it should be fine to call process.exit from the loader thread top-level -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndExit } from '../common/child_process.js'; - -spawnSyncAndExit( - execPath, - [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/loader-exit-top-level.mjs'), - fixtures.path('empty.js'), - ], - { - status: 42, - signal: null, - stdout: '', - stderr: '', - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-register-with-cjs.mjs b/test/module-hooks/test-async-loader-hooks-register-with-cjs.mjs deleted file mode 100644 index 8d83ee6be878..000000000000 --- a/test/module-hooks/test-async-loader-hooks-register-with-cjs.mjs +++ /dev/null @@ -1,20 +0,0 @@ -// Test that `register` should work with cjs -import '../common/index.mjs'; -import assert from 'node:assert'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndAssert } from '../common/child_process.js'; - -spawnSyncAndAssert( - execPath, - [ - '--no-warnings', - fixtures.path('module-hooks/register-loader-with-cjs.cjs'), - ], - { - stdout(output) { - assert.deepStrictEqual(output.split('\n').sort(), ['hooks initialize 1', '{"default":"foo"}', ''].sort()); - }, - stderr: '', - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-register-with-import.mjs b/test/module-hooks/test-async-loader-hooks-register-with-import.mjs deleted file mode 100644 index 454eccb5c624..000000000000 --- a/test/module-hooks/test-async-loader-hooks-register-with-import.mjs +++ /dev/null @@ -1,22 +0,0 @@ -// Test that `register` should work with `import` -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndAssert } from '../common/child_process.js'; - -spawnSyncAndAssert( - execPath, - [ - '--no-warnings', - '--import', - fixtures.fileURL('es-module-loaders/register-loader.mjs'), - '--input-type=module', - '--eval', - 'import "node:os"', - ], - { - stdout: 'resolve passthru', - stderr: '', - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-register-with-ports.mjs b/test/module-hooks/test-async-loader-hooks-register-with-ports.mjs deleted file mode 100644 index 022760596fd9..000000000000 --- a/test/module-hooks/test-async-loader-hooks-register-with-ports.mjs +++ /dev/null @@ -1,18 +0,0 @@ -// Test that loader should allow communicating with loader via `register` ports -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndAssert } from '../common/child_process.js'; - -spawnSyncAndAssert( - execPath, - [ - '--no-warnings', - fixtures.path('module-hooks/register-loader-with-ports.mjs'), - ], - { - stdout: 'register undefined\nmessage initialize\nmessage resolve node:os', - stderr: '', - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-register-with-require.mjs b/test/module-hooks/test-async-loader-hooks-register-with-require.mjs deleted file mode 100644 index 87ea43f8a66c..000000000000 --- a/test/module-hooks/test-async-loader-hooks-register-with-require.mjs +++ /dev/null @@ -1,22 +0,0 @@ -// Test that `register` should work with `require` -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndAssert } from '../common/child_process.js'; - -spawnSyncAndAssert( - execPath, - [ - '--no-warnings', - '--require', - fixtures.path('es-module-loaders/register-loader.cjs'), - '--input-type=module', - '--eval', - 'import "node:os";', - ], - { - stdout: 'resolve passthru\nresolve passthru', - stderr: '', - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-register-with-url-parenturl.mjs b/test/module-hooks/test-async-loader-hooks-register-with-url-parenturl.mjs deleted file mode 100644 index a033ab3c4d2f..000000000000 --- a/test/module-hooks/test-async-loader-hooks-register-with-url-parenturl.mjs +++ /dev/null @@ -1,26 +0,0 @@ -// Test that `register` should accept URL objects as `parentURL` -import '../common/index.mjs'; -import assert from 'node:assert'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndAssert } from '../common/child_process.js'; - -spawnSyncAndAssert( - execPath, - [ - '--no-warnings', - '--import', - fixtures.fileURL('es-module-loaders/register-hooks-with-current-cwd-parent-url.mjs'), - fixtures.path('es-module-loaders/register-loader-with-url-parenturl.mjs'), - ], - { - cwd: fixtures.path('es-module-loaders/'), - }, - { - stdout(output) { - assert.deepStrictEqual(output.split('\n').sort(), ['hooks initialize 1', '{"default":"foo"}'].sort()); - }, - stderr: '', - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-remove-beforeexit-listener.mjs b/test/module-hooks/test-async-loader-hooks-remove-beforeexit-listener.mjs deleted file mode 100644 index 9a28bb59dd31..000000000000 --- a/test/module-hooks/test-async-loader-hooks-remove-beforeexit-listener.mjs +++ /dev/null @@ -1,22 +0,0 @@ -// Test that it should be fine to call `process.removeAllListeners("beforeExit")`("beforeExit") from the main thread -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndAssert } from '../common/child_process.js'; - -spawnSyncAndAssert( - execPath, - [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/loader-delayed-async-load.mjs'), - '--input-type=module', - '--eval', - 'setInterval(() => process.removeAllListeners("beforeExit"),1).unref();await import("data:text/javascript,")', - ], - { - stdout: '', - stderr: '', - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-require-esm-in-hooks-thread.mjs b/test/module-hooks/test-async-loader-hooks-require-esm-in-hooks-thread.mjs deleted file mode 100644 index 8a647568ffee..000000000000 --- a/test/module-hooks/test-async-loader-hooks-require-esm-in-hooks-thread.mjs +++ /dev/null @@ -1,23 +0,0 @@ -// Test that require(esm) of a module with imports works while loading a module.register() hooks module. -// Refs: https://github.com/nodejs/node/issues/66767 -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndAssert } from '../common/child_process.js'; - -spawnSyncAndAssert( - execPath, - [ - '--no-warnings', - '--input-type=module', - '--eval', - `import { register } from 'node:module'; - register(${JSON.stringify(fixtures.fileURL('module-hooks/require-esm-in-hooks-thread/hooks.mjs'))}); - console.log('ok');`, - ], - { - stdout: 'ok', - stderr: '', - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-require-resolve-default.mjs b/test/module-hooks/test-async-loader-hooks-require-resolve-default.mjs deleted file mode 100644 index 71e539fe118b..000000000000 --- a/test/module-hooks/test-async-loader-hooks-require-resolve-default.mjs +++ /dev/null @@ -1,20 +0,0 @@ -// Test that loader should use CJS loader to respond to `require.resolve` calls by default -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndAssert } from '../common/child_process.js'; - -spawnSyncAndAssert( - execPath, - [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/loader-resolve-passthru.mjs'), - fixtures.path('require-resolve.js'), - ], - { - stdout: 'resolve passthru', - stderr: '', - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-require-resolve-opt-in.mjs b/test/module-hooks/test-async-loader-hooks-require-resolve-opt-in.mjs deleted file mode 100644 index 0cbf2fd2d942..000000000000 --- a/test/module-hooks/test-async-loader-hooks-require-resolve-opt-in.mjs +++ /dev/null @@ -1,22 +0,0 @@ -// Test that loader should use ESM loader to respond to `require.resolve` calls when opting in -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndAssert } from '../common/child_process.js'; - -spawnSyncAndAssert( - execPath, - [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/loader-load-commonjs-with-source.mjs'), - '--experimental-loader', - fixtures.fileURL('es-module-loaders/loader-resolve-passthru.mjs'), - fixtures.path('require-resolve.js'), - ], - { - stdout: 'resolve passthru\n'.repeat(10).trim(), - stderr: '', - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-source-maps-cjs.mjs b/test/module-hooks/test-async-loader-hooks-source-maps-cjs.mjs deleted file mode 100644 index 83bd53405d1b..000000000000 --- a/test/module-hooks/test-async-loader-hooks-source-maps-cjs.mjs +++ /dev/null @@ -1,23 +0,0 @@ -// Test that loader should support source maps in commonjs translator -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndExit } from '../common/child_process.js'; - -spawnSyncAndExit( - execPath, - [ - '--no-warnings', - '--enable-source-maps', - '--import', - fixtures.fileURL('es-module-loaders/loader-load-source-maps.mjs'), - fixtures.path('source-map/throw-on-require.js'), - ], - { - status: 1, - signal: null, - stdout: '', - stderr: /throw-on-require\.ts:9:9/, - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-throw-bigint.mjs b/test/module-hooks/test-async-loader-hooks-throw-bigint.mjs deleted file mode 100644 index 34136f81bf21..000000000000 --- a/test/module-hooks/test-async-loader-hooks-throw-bigint.mjs +++ /dev/null @@ -1,22 +0,0 @@ -// Test that loader hooks should handle bigint thrown from top-level -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndExit } from '../common/child_process.js'; - -spawnSyncAndExit( - execPath, - [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/loader-throw-bigint.mjs'), - fixtures.path('empty.js'), - ], - { - status: 1, - signal: null, - stdout: '', - stderr: /^1$/m, - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-throw-boolean.mjs b/test/module-hooks/test-async-loader-hooks-throw-boolean.mjs deleted file mode 100644 index 476fad3c1415..000000000000 --- a/test/module-hooks/test-async-loader-hooks-throw-boolean.mjs +++ /dev/null @@ -1,22 +0,0 @@ -// Test that loader hooks should handle boolean thrown from top-level -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndExit } from '../common/child_process.js'; - -spawnSyncAndExit( - execPath, - [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/loader-throw-boolean.mjs'), - fixtures.path('empty.js'), - ], - { - status: 1, - signal: null, - stdout: '', - stderr: /^true$/m, - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-throw-empty-object.mjs b/test/module-hooks/test-async-loader-hooks-throw-empty-object.mjs deleted file mode 100644 index ac09704c28ca..000000000000 --- a/test/module-hooks/test-async-loader-hooks-throw-empty-object.mjs +++ /dev/null @@ -1,22 +0,0 @@ -// Test that loader hooks should handle empty plain object thrown from top-level -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndExit } from '../common/child_process.js'; - -spawnSyncAndExit( - execPath, - [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/loader-throw-empty-object.mjs'), - fixtures.path('empty.js'), - ], - { - status: 1, - signal: null, - stdout: '', - stderr: /^\{\}$/m, - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-throw-error.mjs b/test/module-hooks/test-async-loader-hooks-throw-error.mjs deleted file mode 100644 index b37ce50db1e3..000000000000 --- a/test/module-hooks/test-async-loader-hooks-throw-error.mjs +++ /dev/null @@ -1,22 +0,0 @@ -// Test that loader hooks should handle regular Error object thrown from top-level -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndExit } from '../common/child_process.js'; - -spawnSyncAndExit( - execPath, - [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/loader-throw-error.mjs'), - fixtures.path('empty.js'), - ], - { - status: 1, - signal: null, - stdout: '', - stderr: /^Error: error message$/m, - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-throw-function.mjs b/test/module-hooks/test-async-loader-hooks-throw-function.mjs deleted file mode 100644 index bb371f727c84..000000000000 --- a/test/module-hooks/test-async-loader-hooks-throw-function.mjs +++ /dev/null @@ -1,22 +0,0 @@ -// Test that loader hooks should handle function thrown from top-level -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndExit } from '../common/child_process.js'; - -spawnSyncAndExit( - execPath, - [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/loader-throw-function.mjs'), - fixtures.path('empty.js'), - ], - { - status: 1, - signal: null, - stdout: '', - stderr: /^\[Function: fnName\]$/m, - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-throw-null.mjs b/test/module-hooks/test-async-loader-hooks-throw-null.mjs deleted file mode 100644 index 33528ff2b975..000000000000 --- a/test/module-hooks/test-async-loader-hooks-throw-null.mjs +++ /dev/null @@ -1,22 +0,0 @@ -// Test that loader hooks should handle null thrown from top-level -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndExit } from '../common/child_process.js'; - -spawnSyncAndExit( - execPath, - [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/loader-throw-null.mjs'), - fixtures.path('empty.js'), - ], - { - status: 1, - signal: null, - stdout: '', - stderr: /^null$/m, - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-throw-number.mjs b/test/module-hooks/test-async-loader-hooks-throw-number.mjs deleted file mode 100644 index da1f301089ca..000000000000 --- a/test/module-hooks/test-async-loader-hooks-throw-number.mjs +++ /dev/null @@ -1,22 +0,0 @@ -// Test that loader hooks should handle number thrown from top-level -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndExit } from '../common/child_process.js'; - -spawnSyncAndExit( - execPath, - [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/loader-throw-number.mjs'), - fixtures.path('empty.js'), - ], - { - status: 1, - signal: null, - stdout: '', - stderr: /^1$/m, - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-throw-object.mjs b/test/module-hooks/test-async-loader-hooks-throw-object.mjs deleted file mode 100644 index 8280bd0d105b..000000000000 --- a/test/module-hooks/test-async-loader-hooks-throw-object.mjs +++ /dev/null @@ -1,22 +0,0 @@ -// Test that loader hooks should handle plain object thrown from top-level -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndExit } from '../common/child_process.js'; - -spawnSyncAndExit( - execPath, - [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/loader-throw-object.mjs'), - fixtures.path('empty.js'), - ], - { - status: 1, - signal: null, - stdout: '', - stderr: /^\{ fn: \[Function: fn\], symbol: Symbol\(symbol\), u: undefined \}$/m, - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-throw-string.mjs b/test/module-hooks/test-async-loader-hooks-throw-string.mjs deleted file mode 100644 index 4ea6372e865c..000000000000 --- a/test/module-hooks/test-async-loader-hooks-throw-string.mjs +++ /dev/null @@ -1,22 +0,0 @@ -// Test that loader hooks should handle string thrown from top-level -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndExit } from '../common/child_process.js'; - -spawnSyncAndExit( - execPath, - [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/loader-throw-string.mjs'), - fixtures.path('empty.js'), - ], - { - status: 1, - signal: null, - stdout: '', - stderr: /^literal string$/m, - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-throw-symbol.mjs b/test/module-hooks/test-async-loader-hooks-throw-symbol.mjs deleted file mode 100644 index 44339537ba9c..000000000000 --- a/test/module-hooks/test-async-loader-hooks-throw-symbol.mjs +++ /dev/null @@ -1,20 +0,0 @@ -// Test that loader hooks should handle symbol thrown from top-level -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndExit } from '../common/child_process.js'; - -spawnSyncAndExit( - execPath, - [ - '--experimental-loader', - fixtures.fileURL('es-module-loaders/loader-throw-symbol.mjs'), - fixtures.path('empty.js'), - ], - { - status: 1, - signal: null, - stdout: '', // Throwing a symbol doesn't produce any output - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-throw-undefined.mjs b/test/module-hooks/test-async-loader-hooks-throw-undefined.mjs deleted file mode 100644 index aff5292a857f..000000000000 --- a/test/module-hooks/test-async-loader-hooks-throw-undefined.mjs +++ /dev/null @@ -1,22 +0,0 @@ -// Test that loader hooks should handle undefined thrown from top-level -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndExit } from '../common/child_process.js'; - -spawnSyncAndExit( - execPath, - [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/loader-throw-undefined.mjs'), - fixtures.path('empty.js'), - ], - { - status: 1, - signal: null, - stdout: '', - stderr: /^undefined$/m, - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-use-hooks-require-esm.mjs b/test/module-hooks/test-async-loader-hooks-use-hooks-require-esm.mjs deleted file mode 100644 index 1eee6a6014aa..000000000000 --- a/test/module-hooks/test-async-loader-hooks-use-hooks-require-esm.mjs +++ /dev/null @@ -1,21 +0,0 @@ -// Test that loader should use hooks for require of ESM -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndAssert } from '../common/child_process.js'; - -spawnSyncAndAssert( - execPath, - [ - '--no-deprecation', - '--no-experimental-require-module', - '--import', - fixtures.fileURL('es-module-loaders/builtin-named-exports.mjs'), - fixtures.path('es-modules/require-esm-throws-with-loaders.js'), - ], - { - stdout: '', - stderr: '', - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-with-worker-permission-allowed.mjs b/test/module-hooks/test-async-loader-hooks-with-worker-permission-allowed.mjs deleted file mode 100644 index 9be7e8c39600..000000000000 --- a/test/module-hooks/test-async-loader-hooks-with-worker-permission-allowed.mjs +++ /dev/null @@ -1,24 +0,0 @@ -// Test that loader hooks should allow spawning workers when allowed by CLI flags -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndAssert } from '../common/child_process.js'; - -spawnSyncAndAssert( - execPath, - [ - '--no-warnings', - '--permission', - '--allow-worker', - '--allow-fs-read', - '*', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/loader-worker-spawn.mjs'), - fixtures.path('es-modules/esm-top-level-await.mjs'), - ], - { - stdout: /^1\n2$/, - stderr: '', - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-with-worker-permission-restricted.mjs b/test/module-hooks/test-async-loader-hooks-with-worker-permission-restricted.mjs deleted file mode 100644 index fe76f2f5842c..000000000000 --- a/test/module-hooks/test-async-loader-hooks-with-worker-permission-restricted.mjs +++ /dev/null @@ -1,25 +0,0 @@ -// Test that loader hooks should not allow spawning workers if restricted by CLI flags -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndExit } from '../common/child_process.js'; - -spawnSyncAndExit( - execPath, - [ - '--no-warnings', - '--permission', - '--allow-fs-read', - '*', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/loader-worker-spawn.mjs'), - fixtures.path('es-modules/esm-top-level-await.mjs'), - ], - { - status: 1, - signal: null, - stdout: '', - stderr: /code: 'ERR_ACCESS_DENIED'/, - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-without-worker-permission.mjs b/test/module-hooks/test-async-loader-hooks-without-worker-permission.mjs deleted file mode 100644 index 21589234144d..000000000000 --- a/test/module-hooks/test-async-loader-hooks-without-worker-permission.mjs +++ /dev/null @@ -1,25 +0,0 @@ -// Test that loader hooks should not work without worker permission -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndExit } from '../common/child_process.js'; - -spawnSyncAndExit( - execPath, - [ - '--no-warnings', - '--permission', - '--allow-fs-read', - '*', - '--experimental-loader', - fixtures.fileURL('empty.js'), - fixtures.path('es-modules/esm-top-level-await.mjs'), - ], - { - status: 1, - signal: null, - stdout: '', - stderr: /Error: Access to this API has been restricted/, - trim: true, - }, -); diff --git a/test/module-hooks/test-module-hooks-load-async-and-sync.js b/test/module-hooks/test-module-hooks-load-async-and-sync.js deleted file mode 100644 index 75f4987942f9..000000000000 --- a/test/module-hooks/test-module-hooks-load-async-and-sync.js +++ /dev/null @@ -1,32 +0,0 @@ -'use strict'; -// This tests that sync and async hooks can be mixed. - -require('../common'); -const { spawnSyncAndAssert } = require('../common/child_process'); -const fixtures = require('../common/fixtures'); - -const app = fixtures.path('module-hooks', 'sync-and-async', 'app.js'); - -const testCases = [ - // When mixing sync and async hooks, the sync ones always run first. - { preload: ['sync-customize', 'async-customize'], stdout: 'customized by sync hook' }, - { preload: ['async-customize', 'sync-customize'], stdout: 'customized by sync hook' }, - // It should still work when neither hook does any customization. - { preload: ['sync-forward', 'async-forward'], stdout: 'Hello world' }, - { preload: ['async-forward', 'sync-forward'], stdout: 'Hello world' }, - // It should work when only one hook is customizing. - { preload: ['sync-customize', 'async-forward'], stdout: 'customized by sync hook' }, - { preload: ['async-customize', 'sync-forward'], stdout: 'customized by async hook' }, -]; - - -for (const { preload, stdout } of testCases) { - const importArgs = []; - for (const p of preload) { - importArgs.push('--import', fixtures.fileURL(`module-hooks/sync-and-async/${p}.js`)); - } - spawnSyncAndAssert(process.execPath, [...importArgs, app], { - stdout, - trim: true, - }); -} diff --git a/test/module-hooks/test-module-hooks-load-builtin-import.mjs b/test/module-hooks/test-module-hooks-load-builtin-import.mjs index f78c69692fe0..159189f53c5d 100644 --- a/test/module-hooks/test-module-hooks-load-builtin-import.mjs +++ b/test/module-hooks/test-module-hooks-load-builtin-import.mjs @@ -5,8 +5,7 @@ import process from 'node:process'; // This tests that imported builtins get null as source from default // step, and the source returned are ignored. -// TODO(joyeecheung): this is to align with the module.register() behavior -// but perhaps the load hooks should not be invoked for builtins at all. +// TODO(joyeecheung): perhaps the load hooks should not be invoked for builtins at all. // Pick a builtin that's unlikely to be loaded already - like zlib. assert(!process.moduleLoadList.includes('NativeModule zlib')); diff --git a/test/module-hooks/test-module-hooks-load-builtin-require.js b/test/module-hooks/test-module-hooks-load-builtin-require.js index d86eb4b9df76..ac4de0f14da3 100644 --- a/test/module-hooks/test-module-hooks-load-builtin-require.js +++ b/test/module-hooks/test-module-hooks-load-builtin-require.js @@ -7,8 +7,7 @@ const { registerHooks } = require('module'); // This tests that required builtins get null as source from default // step, and the source returned are ignored. -// TODO(joyeecheung): this is to align with the module.register() behavior -// but perhaps the load hooks should not be invoked for builtins at all. +// TODO(joyeecheung): perhaps the load hooks should not be invoked for builtins at all. // Pick a builtin that's unlikely to be loaded already - like zlib. assert(!process.moduleLoadList.includes('NativeModule zlib')); diff --git a/test/module-hooks/test-module-hooks-resolve-require-resolve-loaded-with-source.js b/test/module-hooks/test-module-hooks-resolve-require-resolve-loaded-with-source.js deleted file mode 100644 index de66beda5b2e..000000000000 --- a/test/module-hooks/test-module-hooks-resolve-require-resolve-loaded-with-source.js +++ /dev/null @@ -1,35 +0,0 @@ -'use strict'; - -// This tests that require.resolve() in a CJS file loaded via the re-invented -// require (triggered when module.register() installs an async loader that -// provides source for a CJS file) still goes through resolve hooks registered -// via module.registerHooks(). - -const common = require('../common'); -const assert = require('assert'); -const { register, registerHooks } = require('module'); -const fixtures = require('../common/fixtures'); -const { pathToFileURL } = require('url'); - -const redirectedPath = fixtures.path('module-hooks', 'redirected-assert.js'); - -// Register an async loader that provides source for CJS files, which triggers -// the re-invented require path. -register(fixtures.fileURL('module-hooks', 'logger-async-hooks.mjs')); - -const hook = registerHooks({ - resolve: common.mustCall((specifier, context, nextResolve) => { - if (specifier === 'test-require-resolve-hook-target') { - return { - url: pathToFileURL(redirectedPath).href, - shortCircuit: true, - }; - } - return nextResolve(specifier, context); - }, 2), -}); - -import('../fixtures/module-hooks/require-resolve-caller.js').then(common.mustCall((ns) => { - assert.strictEqual(ns.default.resolved, redirectedPath); - hook.deregister(); -})); diff --git a/test/parallel/test-esm-loader-hooks-inspect-brk.js b/test/parallel/test-esm-loader-hooks-inspect-brk.js deleted file mode 100644 index 251ebb230dcd..000000000000 --- a/test/parallel/test-esm-loader-hooks-inspect-brk.js +++ /dev/null @@ -1,31 +0,0 @@ -// This tests esm loader's internal worker will not be blocked by --inspect-brk. -// Regression: https://github.com/nodejs/node/issues/53681 - -'use strict'; -const common = require('../common'); - -common.skipIfInspectorDisabled(); - -const assert = require('assert'); -const fixtures = require('../common/fixtures'); -const { NodeInstance } = require('../common/inspector-helper.js'); - -async function runTest() { - const main = fixtures.path('es-module-loaders', 'register-loader.mjs'); - const child = new NodeInstance(['--inspect-brk=0'], '', main); - - const session = await child.connectInspectorSession(); - await session.send({ method: 'NodeRuntime.enable' }); - await session.waitForNotification('NodeRuntime.waitingForDebugger'); - await session.send([ - { 'method': 'Runtime.enable' }, - { 'method': 'Debugger.enable' }, - { 'method': 'Runtime.runIfWaitingForDebugger' }, - ]); - await session.send({ method: 'NodeRuntime.disable' }); - await session.waitForNotification('Debugger.paused'); - await session.runToCompletion(); - assert.strictEqual((await child.expectShutdown()).exitCode, 0); -} - -runTest(); diff --git a/test/parallel/test-esm-loader-hooks-inspect-wait.js b/test/parallel/test-esm-loader-hooks-inspect-wait.js deleted file mode 100644 index 427b05641cce..000000000000 --- a/test/parallel/test-esm-loader-hooks-inspect-wait.js +++ /dev/null @@ -1,35 +0,0 @@ -// This tests esm loader's internal worker will not be blocked by --inspect-wait. -// Regression: https://github.com/nodejs/node/issues/53681 - -'use strict'; -const common = require('../common'); - -common.skipIfInspectorDisabled(); - -const assert = require('assert'); -const fixtures = require('../common/fixtures'); -const { NodeInstance } = require('../common/inspector-helper.js'); - -async function runTest() { - const main = fixtures.path('es-module-loaders', 'register-loader.mjs'); - process.env.NODE_DEBUG = 'esm,async_loader_worker'; - const child = new NodeInstance(['--inspect-wait=0'], '', main); - - const session = await child.connectInspectorSession(); - await session.send({ method: 'NodeRuntime.enable' }); - await session.waitForNotification('NodeRuntime.waitingForDebugger'); - await session.send([ - { 'method': 'Runtime.enable' }, - { 'method': 'Debugger.enable' }, - { 'method': 'Runtime.runIfWaitingForDebugger' }, - ]); - await session.send({ method: 'NodeRuntime.disable' }); - await session.waitForDisconnect(); - const result = await child.expectShutdown(); - assert.deepStrictEqual(result, { - exitCode: 0, - signal: null, - }); -} - -runTest(); diff --git a/test/parallel/test-is-internal-thread.mjs b/test/parallel/test-is-internal-thread.mjs index 27f20f2d2176..6592e236116d 100644 --- a/test/parallel/test-is-internal-thread.mjs +++ b/test/parallel/test-is-internal-thread.mjs @@ -1,33 +1,16 @@ -import { mustCall, spawnPromisified } from '../common/index.mjs'; +import { mustCall } from '../common/index.mjs'; import * as fixtures from '../common/fixtures.mjs'; import assert from 'node:assert'; -import { execPath } from 'node:process'; import { describe, it } from 'node:test'; import { isInternalThread, Worker } from 'node:worker_threads'; describe('worker_threads.isInternalThread', { concurrency: !process.env.TEST_PARALLEL }, () => { - it('should be true inside the loader thread', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('loader-is-internal-thread.js'), - '--eval', - 'setTimeout(() => {},99)', - ]); - - assert.strictEqual(stderr, ''); - assert.match(stdout, /isInternalThread: true/); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - it('should be false inside the main thread', async () => { assert.strictEqual(isInternalThread, false); }); it('should be false inside a regular worker thread', async () => { const worker = new Worker(fixtures.path('worker-is-internal-thread.js')); - worker.on('message', mustCall((message) => { assert.strictEqual(message, 'isInternalThread: false'); })); diff --git a/test/parallel/test-permission-allow-inspector.js b/test/parallel/test-permission-allow-inspector.js deleted file mode 100644 index 05022ff24044..000000000000 --- a/test/parallel/test-permission-allow-inspector.js +++ /dev/null @@ -1,54 +0,0 @@ -// Flags: --permission --allow-fs-read=* --allow-inspector -'use strict'; - -const common = require('../common'); - -common.skipIfInspectorDisabled(); - -const { Session } = require('node:inspector/promises'); -const assert = require('node:assert'); - -const session = new Session(); -session.connect(); - -// Guarantee WorkerImpl doesn't gets bypassed -(async () => { - await session.post('Debugger.enable'); - await session.post('Runtime.enable'); - - globalThis.Worker = require('node:worker_threads').Worker; - - const { result: { objectId } } = await session.post('Runtime.evaluate', { expression: 'Worker' }); - const { internalProperties } = await session.post('Runtime.getProperties', { objectId: objectId }); - const { - value: { - value: { scriptId } - } - } = internalProperties.filter((prop) => prop.name === '[[FunctionLocation]]')[0]; - const { scriptSource } = await session.post('Debugger.getScriptSource', { scriptId }); - - const lineNumber = scriptSource.substring(0, scriptSource.indexOf('new WorkerImpl')).split('\n').length; - - // Attempt to bypass it based on https://hackerone.com/reports/1962701 - await session.post('Debugger.setBreakpointByUrl', { - lineNumber: lineNumber, - url: 'node:internal/worker', - columnNumber: 0, - condition: '((isInternal = true),false)' - }); - - assert.throws(() => { - new Worker(` - const child_process = require("node:child_process"); - console.log(child_process.execSync("ls -l").toString()); - - console.log(require("fs").readFileSync("/etc/passwd").toString()) - `, { - eval: true, - }); - }, { - message: 'Access to this API has been restricted. Use --allow-worker to manage permissions.', - code: 'ERR_ACCESS_DENIED', - permission: 'WorkerThreads' - }); -})().then(common.mustCall()); diff --git a/test/parallel/test-process-env-allowed-flags-are-documented.js b/test/parallel/test-process-env-allowed-flags-are-documented.js index 910fec097b69..ba33ae989863 100644 --- a/test/parallel/test-process-env-allowed-flags-are-documented.js +++ b/test/parallel/test-process-env-allowed-flags-are-documented.js @@ -137,7 +137,6 @@ assert(undocumented.delete('--experimental-worker')); assert(undocumented.delete('--napi-modules')); assert(undocumented.delete('--node-snapshot')); assert(undocumented.delete('--no-node-snapshot')); -assert(undocumented.delete('--loader')); assert(undocumented.delete('--verify-base-objects')); assert(undocumented.delete('--no-verify-base-objects')); assert(undocumented.delete('--trace-promises')); diff --git a/test/parallel/test-webworker-conditions.js b/test/parallel/test-webworker-conditions.js index 313e3f3d643d..6cf564597eb0 100644 --- a/test/parallel/test-webworker-conditions.js +++ b/test/parallel/test-webworker-conditions.js @@ -70,8 +70,4 @@ function runWebWorker(url, options) { const [threadResult] = await once(thread, 'message'); assert.deepStrictEqual(threadResult, { thread: 'default', webWorker: 'worker' }); - // Async module customization hooks registered from a Web Worker. - assert.strictEqual( - await runWebWorker(new URL('hooks-worker.mjs', base), { type: 'module' }), - 'worker'); })().then(common.mustCall()); diff --git a/typings/internalBinding/worker.d.ts b/typings/internalBinding/worker.d.ts index e168ff8abb17..7801b2c569dd 100644 --- a/typings/internalBinding/worker.d.ts +++ b/typings/internalBinding/worker.d.ts @@ -41,7 +41,6 @@ export interface WorkerBinding { threadId: number; threadName: string; isMainThread: boolean; - isInternalThread: boolean; ownsProcessState: boolean; resourceLimits?: Float64Array; kMaxYoungGenerationSizeMb: number;