Commit cb05e4d0c24 for nodejs

commit cb05e4d0c24d41ece3793cf1fe342855dee53916
Author: Geoffrey Booth <webadmin@geoffreybooth.com>
Date:   Sat Nov 14 13:51:46 2026 -0800

    module: remove async module customization hooks

    Remove `module.register()` (DEP0205), the `--experimental-loader` and
    `--loader` flags, and the machinery that supported them: the loader
    hooks worker thread, the hooks proxy, the async module customization
    hook chains for resolving and loading, the internal worker class used to
    run the loader thread, and the asynchronous module job linking. Module
    customization is now provided only by `module.registerHooks()`, which
    runs synchronously on the thread that loads the modules.

    As the loader thread no longer exists, the ES module loader always loads
    and links modules synchronously, so the code that handled promises and
    the re-invented `require()` for imported CommonJS customized by the
    async module customization hooks is removed too. `import()` still defers
    evaluation of the module to a later microtask, as before.

    `worker_threads.isInternalThread` is kept for compatibility and is now
    always `false`; removing it is left for a future semver-major change.

    The `ERR_ASYNC_LOADER_REQUEST_NEVER_SETTLED` and
    `ERR_LOADER_CHAIN_INCOMPLETE` error codes are no longer used and are
    documented as removed.

    Tests that exist only to exercise the async module customization hooks
    are removed.

    Assisted-by: Claude
    Signed-off-by: Geoffrey Booth <webadmin@geoffreybooth.com>
    PR-URL: https://github.com/nodejs/node/pull/67090
    Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
    Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
    Reviewed-By: Ilyas Shabi <ilyasshabi94@gmail.com>
    Reviewed-By: James M Snell <jasnell@gmail.com>
    Reviewed-By: Jacob Smith <jacob@frende.me>

diff --git a/doc/api/cli.md b/doc/api/cli.md
index f534aa96e77..52ea6f3f10e 100644
--- a/doc/api/cli.md
+++ b/doc/api/cli.md
@@ -1535,33 +1535,6 @@ added:

 Enable experimental support for inspector network resources.

-### `--experimental-loader=module`
-
-<!-- YAML
-added: v8.8.0
-changes:
-  - version:
-    - v23.6.1
-    - v22.13.1
-    - v20.18.2
-    pr-url: https://github.com/nodejs-private/node-private/pull/629
-    description: Using this feature with the permission model enabled requires
-                 passing `--allow-worker`.
-  - version: v12.11.1
-    pr-url: https://github.com/nodejs/node/pull/29752
-    description: This flag was renamed from `--loader` to
-                 `--experimental-loader`.
--->
-
-> 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`

 <!-- YAML
@@ -4340,7 +4313,6 @@ one is included in the list below.
 * `--experimental-import-meta-resolve`
 * `--experimental-import-text`
 * `--experimental-json-modules`
-* `--experimental-loader`
 * `--experimental-modules`
 * `--experimental-package-map`
 * `--experimental-print-required-tla`
@@ -5024,7 +4996,6 @@ node --stack-trace-limit=12 -p -e "Error.stackTraceLimit" # prints 12
 [`dns.setDefaultResultOrder()`]: dns.md#dnssetdefaultresultorderorder
 [`dnsPromises.lookup()`]: dns.md#dnspromiseslookuphostname-options
 [`import.meta.url`]: esm.md#importmetaurl
-[`import` specifier]: esm.md#import-specifiers
 [`inspector.open()`]: inspector.md#inspectoropenport-host-wait
 [`net.getDefaultAutoSelectFamilyAttemptTimeout()`]: net.md#netgetdefaultautoselectfamilyattempttimeout
 [`node:ffi`]: ffi.md
@@ -5041,7 +5012,6 @@ node --stack-trace-limit=12 -p -e "Error.stackTraceLimit" # prints 12
 [`v8.startupSnapshot.setDeserializeMainFunction()`]: v8.md#v8startupsnapshotsetdeserializemainfunctioncallback-data
 [`v8.startupSnapshot` API]: v8.md#startup-snapshot-api
 [`vfs.mount()`]: vfs.md#vfsmount
-[asynchronous module customization hooks]: module.md#asynchronous-customization-hooks
 [benchmark runner]: bench.md#command-line-runner
 [captured by the built-in snapshot of Node.js]: https://github.com/nodejs/node/blob/b19525a33cc84033af4addd0f80acd4dc33ce0cf/test/parallel/test-bootstrap-modules.js#L24
 [collecting code coverage from tests]: test.md#collecting-code-coverage
@@ -5059,7 +5029,6 @@ node --stack-trace-limit=12 -p -e "Error.stackTraceLimit" # prints 12
 [jitless]: https://v8.dev/blog/jitless
 [libuv threadpool documentation]: https://docs.libuv.org/en/latest/threadpool.html
 [module compile cache]: module.md#module-compile-cache
-[preloading asynchronous module customization hooks]: module.md#registration-of-asynchronous-customization-hooks
 [randomizing tests execution order]: test.md#randomizing-tests-execution-order
 [remote code execution]: https://www.owasp.org/index.php/Code_Injection
 [running tests from the command line]: test.md#running-tests-from-the-command-line
diff --git a/doc/api/deprecations.md b/doc/api/deprecations.md
index 95deb305b64..fc333e937d6 100644
--- a/doc/api/deprecations.md
+++ b/doc/api/deprecations.md
@@ -4615,6 +4615,9 @@ no longer supported.

 <!-- YAML
 changes:
+  - version: REPLACEME
+    pr-url: https://github.com/nodejs/node/pull/67090
+    description: End-of-Life.
   - version: v26.0.0
     pr-url: https://github.com/nodejs/node/pull/62401
     description: Runtime deprecation.
@@ -4625,18 +4628,18 @@ changes:
     description: Documentation-only deprecation.
 -->

-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, in-thread and work for all types of modules.
+To continue running async module customization off-thread, users can spawn their
+own threads to run the async customizations, communicate from the in-thread
+synchronous hooks and block for the results on the main thread, similar to how
+`module.register()` worked internally.

 ### DEP0206: Calling `digest()` on an already-finalized `Hmac` instance

@@ -4935,7 +4938,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 +4993,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 8020ec838c8..e6ab55bab05 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.

-<a id="ERR_ASYNC_LOADER_REQUEST_NEVER_SETTLED"></a>
-
-### `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.
-
 <a id="ERR_ASYNC_RESOURCE_DOMAIN_REMOVED"></a>

 ### `ERR_ASYNC_RESOURCE_DOMAIN_REMOVED`
@@ -2364,19 +2357,6 @@ for more information.

 IP is blocked by `net.BlockList`.

-<a id="ERR_LOADER_CHAIN_INCOMPLETE"></a>
-
-### `ERR_LOADER_CHAIN_INCOMPLETE`
-
-<!-- YAML
-added:
-  - v18.6.0
-  - v16.17.0
--->
-
-An ESM loader hook returned without calling `next()` and without explicitly
-signaling a short circuit.
-
 <a id="ERR_LOAD_SQLITE_EXTENSION"></a>

 ### `ERR_LOAD_SQLITE_EXTENSION`
@@ -3936,6 +3916,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.

+<a id="ERR_ASYNC_LOADER_REQUEST_NEVER_SETTLED"></a>
+
+### `ERR_ASYNC_LOADER_REQUEST_NEVER_SETTLED`
+
+<!-- YAML
+removed: REPLACEME
+-->
+
+An operation related to module loading is customized by an asynchronous loader
+hook that never settled the promise before the loader thread exits.
+
 <a id="ERR_CANNOT_TRANSFER_OBJECT"></a>

 ### `ERR_CANNOT_TRANSFER_OBJECT`
@@ -4174,6 +4165,20 @@ changes:

 An invalid transfer object was passed to `postMessage()`.

+<a id="ERR_LOADER_CHAIN_INCOMPLETE"></a>
+
+### `ERR_LOADER_CHAIN_INCOMPLETE`
+
+<!-- YAML
+added:
+  - v18.6.0
+  - v16.17.0
+removed: REPLACEME
+-->
+
+An ESM loader hook returned without calling `next()` and without explicitly
+signaling a short circuit.
+
 <a id="ERR_MANIFEST_ASSERT_INTEGRITY"></a>

 ### `ERR_MANIFEST_ASSERT_INTEGRITY`
diff --git a/doc/api/esm.md b/doc/api/esm.md
index ef15a51dcfe..bc4af89045f 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.

 <!-- Note: The merve link should be kept in-sync with the deps version -->

@@ -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 f86167e8d44..309114f9468 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])`
-
-<!-- YAML
-added:
-  - v20.6.0
-  - v18.19.0
-deprecated:
- - v25.9.0
- - v24.15.0
-changes:
-  - version: v26.0.0
-    pr-url: https://github.com/nodejs/node/pull/62401
-    description: Runtime deprecation (DEP0205).
-  - version:
-    - v23.6.1
-    - v22.13.1
-    - v20.18.2
-    pr-url: https://github.com/nodejs-private/node-private/pull/629
-    description: Using this feature with the permission model enabled requires
-                 passing `--allow-worker`.
-  - version:
-    - v20.8.0
-    - v18.19.0
-    pr-url: https://github.com/nodejs/node/pull/49655
-    description: Add support for WHATWG URL instances.
--->
-
-> 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)`

 <!-- YAML
@@ -253,7 +203,7 @@ changes:

 Register [hooks][] that customize Node.js module resolution and loading behavior.
 See [Customization hooks][]. The returned object can be used to
-[deregister the hooks][deregistration of synchronous customization hooks].
+[deregister the hooks][deregistration of customization hooks].

 ## `module.stripTypeScriptTypes(code[, options])`

@@ -668,30 +618,17 @@ changes:

 <!-- type=misc -->

-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

 <i id="enabling_module_customization_hooks"></i>

-#### 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()`

 <!-- YAML
 added:
@@ -898,7 +834,7 @@ added:
   - v22.15.0
 -->

-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)`

 <!-- YAML
 changes:
@@ -1007,7 +943,7 @@ function resolve(specifier, context, nextResolve) {
 registerHooks({ resolve });
 ```

-#### Synchronous `load(url, context, nextLoad)`
+### `load(url, context, nextLoad)`

 <!-- YAML
 changes:
@@ -1076,7 +1012,7 @@ registerHooks({ load });
 In a more advanced scenario, this can also be used to transform an unsupported
 source to a supported one (see [Examples](#examples) below).

-##### Accepted final formats returned by `load`
+#### Accepted final formats returned by `load`

 The final value of `format` must be one of the following:

@@ -1102,581 +1038,12 @@ not possible to replace the value of a Node.js builtin (core) module.
 If the source value of a text-based format (i.e., `'json'`, `'module'`)
 is not a string, it is converted to a string using [`util.TextDecoder`][].

-### Asynchronous customization hooks
-
-> Stability: 1.1 - Active Development
-
-#### Caveats of asynchronous customization hooks
-
-The asynchronous customization hooks have many caveats and it is uncertain if their
-issues can be resolved. Users are encouraged to use the synchronous customization hooks
-via `module.registerHooks()` instead to avoid these caveats.
-
-* Asynchronous hooks run on a separate thread, so the hook functions cannot directly
-  mutate the global state of the modules being customized. It's typical to use message
-  channels and atomics to pass data between the two or to affect control flows.
-  See [Communication with asynchronous module customization hooks](#communication-with-asynchronous-module-customization-hooks).
-* Asynchronous hooks do not affect all `require()` calls in the module graph.
-  * Custom `require` functions created using `module.createRequire()` are not
-    affected.
-  * If the asynchronous `load` hook does not override the `source` for CommonJS modules
-    that go through it, the child modules loaded by those CommonJS modules via built-in
-    `require()` would not be affected by the asynchronous hooks either.
-* There are several caveats that the asynchronous hooks need to handle when
-  customizing CommonJS modules. See [asynchronous `resolve` hook][] and
-  [asynchronous `load` hook][] for details.
-* When `require()` calls inside CommonJS modules are customized by asynchronous hooks,
-  Node.js may need to load the source code of the CommonJS module multiple times to maintain
-  compatibility with existing CommonJS monkey-patching. If the module code changes between
-  loads, this may lead to unexpected behaviors.
-  * As a side effect, if both asynchronous hooks and synchronous hooks are registered and the
-    asynchronous hooks choose to customize the CommonJS module, the synchronous hooks may be
-    invoked multiple times for the `require()` calls in that CommonJS module.
-
-#### Registration of asynchronous customization hooks
-
-Asynchronous customization hooks are registered using [`module.register()`][`register`] which takes
-a path or URL to another module that exports the [asynchronous hook functions][].
-
-Similar to `registerHooks()`, `register()` can be called in a module preloaded by `--import` or
-`--require`, or called directly within the entry point.
-
-```mjs
-// Use module.register() to register asynchronous hooks in a dedicated thread.
-import { register } from 'node:module';
-register('./hooks.mjs', import.meta.url);
-
-// If my-app.mjs is loaded statically here as `import './my-app.mjs'`, since ESM
-// dependencies are evaluated before the module that imports them,
-// it's loaded _before_ the hooks are registered above and won't be affected.
-// To ensure the hooks are applied, dynamic import() must be used to load ESM
-// after the hooks are registered.
-import('./my-app.mjs');
-```
-
-```cjs
-const { register } = require('node:module');
-const { pathToFileURL } = require('node:url');
-// Use module.register() to register asynchronous hooks in a dedicated thread.
-register('./hooks.mjs', pathToFileURL(__filename));
-
-import('./my-app.mjs');
-```
-
-In `hooks.mjs`:
-
-```mjs
-// hooks.mjs
-export async function resolve(specifier, context, nextResolve) {
-  /* implementation */
-}
-export async function load(url, context, nextLoad) {
-  /* implementation */
-}
-```
-
-Unlike synchronous hooks, the asynchronous hooks would not run for these modules loaded in the file
-that calls `register()`:
-
-```mjs
-// register-hooks.js
-import { register, createRequire } from 'node:module';
-register('./hooks.mjs', import.meta.url);
-
-// Asynchronous hooks does not affect modules loaded via custom require()
-// functions created by module.createRequire().
-const userRequire = createRequire(import.meta.filename);
-userRequire('./my-app-2.cjs');  // Hooks won't affect this
-```
-
-```cjs
-// register-hooks.js
-const { register, createRequire } = require('node:module');
-const { pathToFileURL } = require('node:url');
-register('./hooks.mjs', pathToFileURL(__filename));
-
-// Asynchronous hooks does not affect modules loaded via built-in require()
-// in the module calling `register()`
-require('./my-app-2.cjs');  // Hooks won't affect this
-// .. or custom require() functions created by module.createRequire().
-const userRequire = createRequire(__filename);
-userRequire('./my-app-3.cjs');  // Hooks won't affect this
-```
-
-Asynchronous hooks can also be registered using a `data:` URL with the `--import` flag:
-
-```bash
-node --import 'data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register("my-instrumentation", pathToFileURL("./"));' ./my-app.js
-```
-
-#### Chaining of asynchronous customization hooks
-
-Chaining of `register()` work similarly to `registerHooks()`. If synchronous and asynchronous
-hooks are mixed, the synchronous hooks are always run first before the asynchronous
-hooks start running, that is, in the last synchronous hook being run, its next
-hook includes invocation of the asynchronous hooks.
-
-```mjs
-// entrypoint.mjs
-import { register } from 'node:module';
-
-register('./foo.mjs', import.meta.url);
-register('./bar.mjs', import.meta.url);
-await import('./my-app.mjs');
-```
-
-```cjs
-// entrypoint.cjs
-const { register } = require('node:module');
-const { pathToFileURL } = require('node:url');
-
-const parentURL = pathToFileURL(__filename);
-register('./foo.mjs', parentURL);
-register('./bar.mjs', parentURL);
-import('./my-app.mjs');
-```
-
-If `foo.mjs` and `bar.mjs` define a `resolve` hook, they will be called like so
-(note the right-to-left, starting with `./bar.mjs`, then `./foo.mjs`, then the Node.js default):
-
-Node.js default ← `./foo.mjs` ← `./bar.mjs`
-
-When using the asynchronous hooks, the registered hooks also affect subsequent
-`register` calls, which takes care of loading hook modules. In the example above,
-`bar.mjs` will be resolved and loaded via the hooks registered by `foo.mjs`
-(because `foo`'s hooks will have already been added to the chain). This allows
-for things like writing hooks in non-JavaScript languages, so long as
-earlier registered hooks transpile into JavaScript.
-
-The `register()` method cannot be called from the thread running the hook module that
-exports the asynchronous hooks or its dependencies.
-
-#### Communication with asynchronous module customization hooks
-
-Asynchronous hooks run on a dedicated thread, separate from the main
-thread that runs application code. This means mutating global variables won't
-affect the other thread(s), and message channels must be used to communicate
-between the threads.
-
-The `register` method can be used to pass data to an [`initialize`][] hook. The
-data passed to the hook may include transferable objects like ports.
-
-```mjs
-import { register } from 'node:module';
-import { MessageChannel } from 'node:worker_threads';
-
-// This example demonstrates how a message channel can be used to
-// communicate with the hooks, by sending `port2` to the hooks.
-const { port1, port2 } = new MessageChannel();
-
-port1.on('message', (msg) => {
-  console.log(msg);
-});
-port1.unref();
-
-register('./my-hooks.mjs', {
-  parentURL: import.meta.url,
-  data: { number: 1, port: port2 },
-  transferList: [port2],
-});
-```
-
-```cjs
-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 with the hooks, by sending `port2` to the hooks.
-const { port1, port2 } = new MessageChannel();
-
-port1.on('message', (msg) => {
-  console.log(msg);
-});
-port1.unref();
-
-register('./my-hooks.mjs', {
-  parentURL: pathToFileURL(__filename),
-  data: { number: 1, port: port2 },
-  transferList: [port2],
-});
-```
-
-A hook can send messages to the thread that registered it at any time, as
-above. It must not block on a reply from that thread: resolution and loading
-are synchronous from the perspective of the thread that triggered them, so that
-thread is blocked until the hook settles and cannot answer in the meantime.
-Hooks can otherwise be fully asynchronous; it is only a reply over this
-message channel that cannot be awaited.
-
-#### Asynchronous hooks accepted by `module.register()`
-
-<!-- YAML
-added: v8.8.0
-changes:
-  - version:
-    - v20.6.0
-    - v18.19.0
-    pr-url: https://github.com/nodejs/node/pull/48842
-    description: Added `initialize` hook to replace `globalPreload`.
-  - version:
-    - v18.6.0
-    - v16.17.0
-    pr-url: https://github.com/nodejs/node/pull/42623
-    description: Add support for chaining loaders.
-  - version: v16.12.0
-    pr-url: https://github.com/nodejs/node/pull/37468
-    description: Removed `getFormat`, `getSource`, `transformSource`, and
-                 `globalPreload`; added `load` hook and `getGlobalPreload` hook.
--->
-
-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()`
-
-<!-- YAML
-added:
-  - v20.6.0
-  - v18.19.0
--->
-
-* `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)`
-
-<!-- YAML
-changes:
-  - version:
-    - v21.0.0
-    - v20.10.0
-    - v18.19.0
-    pr-url: https://github.com/nodejs/node/pull/50140
-    description: The property `context.importAssertions` is replaced with
-                 `context.importAttributes`. Using the old name is still
-                 supported and will emit an experimental warning.
-  - version:
-    - v18.6.0
-    - v16.17.0
-    pr-url: https://github.com/nodejs/node/pull/42623
-    description: Add support for chaining resolve hooks. Each hook must either
-      call `nextResolve()` or include a `shortCircuit` property set to `true`
-      in its return.
-  - version:
-    - v17.1.0
-    - v16.14.0
-    pr-url: https://github.com/nodejs/node/pull/40250
-    description: Add support for import assertions.
--->
-
-* `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)`
-
-<!-- YAML
-changes:
-  - version: v22.6.0
-    pr-url: https://github.com/nodejs/node/pull/56350
-    description: Add support for `source` with format `commonjs-typescript` and `module-typescript`.
-  - version: v20.6.0
-    pr-url: https://github.com/nodejs/node/pull/47999
-    description: Add support for `source` with format `commonjs`.
-  - version:
-    - v18.6.0
-    - v16.17.0
-    pr-url: https://github.com/nodejs/node/pull/42623
-    description: Add support for chaining load hooks. Each hook must either
-      call `nextLoad()` or include a `shortCircuit` property set to `true` in
-      its return.
--->
-
-* `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.
-    <!-- TODO(joyeecheung): make it at least optionally non-mandatory by allowing
-         JS-style/TS-style module detection when the format is simply unknown -->
-* 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`.
-
-<!-- TODO(joyeecheung): add an example on how to implement it with a fetchSync based on
-workers and Atomics.wait() - or all these examples are too much to be put in the API
-documentation already and should be put into a repository instead? -->
-
 #### 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 db530c609ad..01703227f8c 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 08da7b35f15..af572039c8a 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 9b9c0f835d0..8453ca49e7d 100644
--- a/doc/node-config-schema.json
+++ b/doc/node-config-schema.json
@@ -367,21 +367,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 af10d324353..5da352d7472 100644
--- a/doc/node.1
+++ b/doc/node.1
@@ -885,11 +885,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.
 .
@@ -2290,8 +2285,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 4993b44face..f0b6f84adb1 100644
--- a/lib/internal/bench_runner/cli.js
+++ b/lib/internal/bench_runner/cli.js
@@ -719,7 +719,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 786fc7e275e..88518563bc6 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);
@@ -1605,13 +1603,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) => {
@@ -1789,13 +1780,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 998c3fb4ada..c662b8c2c26 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 ed12eb94678..9895b86dbb4 100644
--- a/lib/internal/main/worker_thread.js
+++ b/lib/internal/main/worker_thread.js
@@ -106,16 +106,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);

@@ -141,24 +139,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, the SEA VFS needs no provider that a
-      // preload could register, so its mount is never deferred.
-      if (getOptionValue('[sea_vfs]')) {
-        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, the SEA VFS needs no provider that a
+    // preload could register, so its mount is never deferred.
+    if (getOptionValue('[sea_vfs]')) {
+      require('internal/vfs/sea').initSeaVfsInWorker();
     }

     if (!hasStdin)
@@ -168,14 +160,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 c3d1970243f..5ce0ab4418a 100644
--- a/lib/internal/modules/customization_hooks.js
+++ b/lib/internal/modules/customization_hooks.js
@@ -282,7 +282,7 @@ 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
+  // To align with the legacy module.register(), the load hooks are still invoked for
   // the 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:') &&
@@ -302,7 +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
+    // To retain the legacy behavior from the quirk in defaultLoad used by asynchronous loader hooks
     // which sets source to null for commonjs.
     // 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 220dcd33754..00000000000
--- 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<string, string> }} 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<LoadResult>} 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<string, string>) => Promise<ResolveResult>} resolve
- *   Calling the asynchronous `resolve` hook asynchronously.
- * @property {(originalSpecifier: string, parentURL: string,
- *            importAttributes: Record<string, string>) => 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<string, unknown>} exports
-   * @param {any} [data] Arbitrary data to be passed from the custom loader (user-land)
-   *   to the worker.
-   * @returns {any | Promise<any>} 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<any>}
-   */
-  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<HookName>` 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<HookName> 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 06f60f9f672..66e7eb6d5ec 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 2e0b0fef8e9..f8e68e7996c 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>|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>|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>|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> | 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 c8506b42ff3..ba300785c9e 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<ModuleWrap>} 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<ModuleJobBase[]>}
-   */
-  link(requestType) {
-    if (this.loader.isForAsyncLoaderHookWorker) {
-      return this.#asyncLink(requestType);
-    }
-    return this.syncLink(requestType);
-  }
-
-  /**
-   * @param {ModuleRequestType} requestType Type of the module request.
-   * @returns {Promise<ModuleJobBase[]>}
-   */
-  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 207515573c7..05a6c3082d2 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 4200bc87367..00000000000
--- 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 583b6d1fe91..100f791b98e 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 8937b8a6129..b8c8b10d386 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 7d023059a4b..00000000000
--- 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<AsyncLoaderHooksOnLoaderHookWorker>}
- */
-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<void>} 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 71612c27cc5..d043694c8c0 100644
--- a/lib/internal/modules/package_json_reader.js
+++ b/lib/internal/modules/package_json_reader.js
@@ -411,11 +411,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 b0d7d4845ea..ec97f46f5a0 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 0f5aaf857ae..b5933f4e9d6 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 d64ffdfa096..266525c8d52 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 728132f779e..00b0293e85d 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 1217172afb3..60ba0803dc5 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 622bef3f55c..b6a0887784d 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 9892f38d96b..444d3133753 100644
--- a/src/module_wrap.cc
+++ b/src/module_wrap.cc
@@ -1362,7 +1362,7 @@ static Maybe<void> 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<Function> 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 3fac3275752..2be14c7ffb5 100644
--- a/src/node_options.cc
+++ b/src/node_options.cc
@@ -859,11 +859,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 4d3585bb8cf..69d59080231 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;
   }
@@ -185,7 +180,6 @@ class EnvironmentOptions : public Options {
   std::vector<std::string> experimental_test_tag_filter;
   std::vector<std::string> coverage_include_pattern;
   std::vector<std::string> coverage_exclude_pattern;
-  std::vector<std::string> userland_loaders;
   std::vector<std::string> watch_mode_paths;
   std::vector<std::string> preload_cjs_modules;
   std::vector<std::string> preload_esm_modules;
diff --git a/src/node_watchdog.cc b/src/node_watchdog.cc
index ddf986839d0..50ae9d7075b 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 308d987680d..9c8f215e094 100644
--- a/src/node_worker.cc
+++ b/src/node_worker.cc
@@ -64,8 +64,7 @@ Worker::Worker(Environment* env,
                std::shared_ptr<PerIsolateOptions> per_isolate_opts,
                std::vector<std::string>&& exec_argv,
                std::shared_ptr<KVStore> 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<Value>& 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<Value>& 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<String>()));
+        isolate, args[5]->ToString(env->context()).FromMaybe(Local<String>()));
     name.append(value.out(), value.length());
   }

@@ -709,16 +706,6 @@ void Worker::New(const FunctionCallbackInfo<Value>& 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<Value>& args) {
                               per_isolate_opts,
                               std::move(exec_argv_out),
                               env_vars,
-                              snapshot_data,
-                              is_internal);
+                              snapshot_data);

   CHECK(args[3]->IsFloat64Array());
   Local<Float64Array> limit_info = args[3].As<Float64Array>();
@@ -1474,13 +1460,6 @@ void IsMainThreadGetter(Local<v8::Name>,
   info.GetReturnValue().Set(Environment::GetCurrent(info)->is_main_thread());
 }

-void IsInternalThreadGetter(Local<v8::Name>,
-                            const v8::PropertyCallbackInfo<Value>& info) {
-  Worker* worker =
-      Environment::GetCurrent(info)->isolate_data()->worker_context();
-  info.GetReturnValue().Set(worker != nullptr && worker->is_internal());
-}
-
 void OwnsProcessStateGetter(Local<v8::Name>,
                             const v8::PropertyCallbackInfo<Value>& 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<Object> target,
                                       Local<Value> unused,
                                       Local<Context> 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 03e546534c1..4261db0c18f 100644
--- a/src/node_worker.h
+++ b/src/node_worker.h
@@ -36,8 +36,7 @@ class Worker : public AsyncWrap {
          std::shared_ptr<PerIsolateOptions> per_isolate_opts,
          std::vector<std::string>&& exec_argv,
          std::shared_ptr<KVStore> 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 ba0469da50a..104ef026c76 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 b85de4e64c4..00000000000
--- 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 7b3ec4b942e..00000000000
--- 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 2f9b7348a2d..00000000000
--- 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 00000000000..0679493fe96
--- /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 c86d3b43949..00000000000
--- 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 6c266b9da3d..00000000000
--- 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<HookName>\'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<HookName>: 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 075e4fe2b66..00000000000
--- 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 2a69010e050..9f836aa56e0 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 54327b478bf..00000000000
--- 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 d94e55148d8..00000000000
--- 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 96bfffe5b13..00000000000
--- 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 5a16381e588..00000000000
--- 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 1ba7c621f7e..00000000000
--- 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 cdf56d85415..00000000000
--- 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 ba0358368ec..6dada059c4a 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 2abaf3078d1..00000000000
--- 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 dd25e3f2430..00000000000
--- 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 7dcc65a10c0..00000000000
--- 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 2f27f7850f6..00000000000
--- 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 8502375b31a..00000000000
--- 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 d26ba7d476a..00000000000
--- 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 41dca12dfc6..00000000000
--- 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 fe41b293a3f..00000000000
--- 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 de348143248..00000000000
--- 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 bbe9c96b92d..00000000000
--- 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 7a538e44c07..00000000000
--- 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 722dc467d19..00000000000
--- 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 171e5971a09..00000000000
--- 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 00000000000..6815243212f
--- /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 e0015bd0c75..00000000000
--- 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 c97e52d1e63..00000000000
--- 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 68c1b863f7c..00000000000
--- 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 bbce967a4b7..b643923313d 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 5800eef9407..0ef7d93df29 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 f1b770d04f4..00000000000
--- 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 123b12c26bf..00000000000
--- 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 17a3430b44e..00000000000
--- 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 77d44d55f91..00000000000
--- 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 c410401876e..00000000000
--- 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 7324a08e84b..00000000000
--- 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 5109d20f4d3..00000000000
--- 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 cefe8854297..00000000000
--- 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 7622d982a9d..00000000000
--- 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 e4eb159b398..00000000000
--- 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 4fe8fcb4797..00000000000
--- 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 b4d2741b2fc..00000000000
--- 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 33bae9bdafd..00000000000
--- 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 c8154ac5dbe..00000000000
--- 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 19af0e2bc07..00000000000
--- 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 4ee413fa46c..00000000000
--- 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 cffa4bcc486..00000000000
--- 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 6427ca06037..00000000000
--- 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 7055b90f4e7..00000000000
--- 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 6b7f59d6319..00000000000
--- 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 f54cf72ce5b..00000000000
--- 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 53cb4fa26f0..00000000000
--- 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 80033b663a5..00000000000
--- 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 899accc1511..00000000000
--- 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 3d834b5c23c..00000000000
--- 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 dc61a792b2b..00000000000
--- 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 aac2b16b6f5..00000000000
--- 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 fe38da04f9f..00000000000
--- 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 c6a4c7504d4..00000000000
--- 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 3f431e630cd..00000000000
--- 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 96af5507d17..00000000000
--- 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 285b81a910e..00000000000
--- 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 1028e093e46..00000000000
--- 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 bf6d5ea254d..00000000000
--- 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 401b5297131..00000000000
--- 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 252eec4ebc2..00000000000
--- 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 7676be766af..00000000000
--- 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 72ff6b56122..00000000000
--- 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 6e5a1ee7193..00000000000
--- 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 bf5ef6b9102..00000000000
--- 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 b56fddbce58..00000000000
--- 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 69b17a88249..00000000000
--- 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 b571a27a978..00000000000
--- 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<HookName>: ${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 881f5875dd0..00000000000
--- 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 66c941754f9..00000000000
--- 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 edc2303ed9a..00000000000
--- 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 b5e5a419b99..00000000000
--- 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 cb37a435271..00000000000
--- 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 91dbec251ed..00000000000
--- 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 a6b8e85455d..00000000000
--- 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 16c9826a8ba..00000000000
--- 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 6a92a6cd8f6..00000000000
--- 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 f388f0db273..00000000000
--- 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 f6a8fdad53e..00000000000
--- 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 00c18e75e2c..00000000000
--- 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 996dfd041c0..00000000000
--- 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 7746469bf81..00000000000
--- 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 d41c1ae4033..00000000000
--- 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 2be18c4969e..00000000000
--- 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 700b0387fa7..00000000000
--- 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 874fbd92e5f..00000000000
--- 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 778886fcb90..00000000000
--- 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 816051b6c78..00000000000
--- 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 35503d67097..00000000000
--- 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 37d3d14b8bf..00000000000
--- 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 6dbc3994c7a..00000000000
--- 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 fa7c9b7d43a..00000000000
--- 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 613a4321f50..00000000000
--- 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 05876e0b4ad..00000000000
--- 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 38ecbdff980..00000000000
--- 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 65e1adf909d..00000000000
--- 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 90c55fa576b..00000000000
--- 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 625341eaed7..00000000000
--- 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 95f40ec15d2..00000000000
--- 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 2860d116e64..00000000000
--- 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 5d61d81bc31..00000000000
--- 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 3bb349b5385..00000000000
--- 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 cb167f1d520..00000000000
--- 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<string, {namespace, listeners}>}
- */
-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 2bc389cc3e1..00000000000
--- 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 c180583b4c7..00000000000
--- 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 cc81affe7d0..00000000000
--- 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 3dbf801c760..00000000000
--- 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 bf6387560b4..00000000000
--- 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 806a131928c..00000000000
--- 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 41884ab9d2e..00000000000
--- 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 8213bbe59d4..00000000000
--- 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 db98e8d9f87..00000000000
--- 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 b6629e24788..00000000000
--- 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 b3769d96135..00000000000
--- 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 b6629e24788..00000000000
--- 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 bf66efbd081..00000000000
--- 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 2cfa851a23f..00000000000
--- 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 580d092ab56..00000000000
--- 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 1d82d2abc55..00000000000
--- 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 9e18dfa77e2..00000000000
--- 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 f3cb2de8da3..00000000000
--- 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 fe22dc7203e..00000000000
--- 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 3ae8841da83..00000000000
--- 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 39ad32c465f..00000000000
--- 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 5e008edecf7..00000000000
--- 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 926f6d0bebd..00000000000
--- 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 661f6e21ac6..00000000000
--- 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 00000000000..efeee5db16c
--- /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 00000000000..9e3f57a169a
--- /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 f33a40fba77..00000000000
--- 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 1c99b7e9c75..00000000000
--- 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 79c5cf96c14..00000000000
--- 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 ee9cd45bd6c..00000000000
--- 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 8505bafc98d..00000000000
--- 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 d97328bc207..00000000000
--- 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 fa5869a455c..00000000000
--- 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 f26d0ce2ae3..00000000000
--- 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 c129fcfc180..00000000000
--- 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 13091effa97..00000000000
--- 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 2cd6c666bd1..00000000000
--- 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 45e125f96a2..00000000000
--- 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 3d34ee66769..00000000000
--- 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 1aad2b7a98c..00000000000
--- 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 b80b4ef959b..00000000000
--- 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 f18b1f37773..00000000000
--- 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 c14477b70f7..00000000000
--- 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 ac961f0b97a..00000000000
--- 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 78bf7fc5564..00000000000
--- 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 4a1ced7e8db..00000000000
--- 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 fd10aa6df89..00000000000
--- 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 90d63db1c7c..00000000000
--- 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 2688a240dc8..00000000000
--- 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 42eb45615b2..00000000000
--- 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 195bf02bb6f..00000000000
--- 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 d1f9181240d..00000000000
--- 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 54501748bce..00000000000
--- 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 36a2c0089a9..00000000000
--- 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 bc973f58784..00000000000
--- 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 72dc87c12e3..00000000000
--- 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 d1c12238ef0..00000000000
--- 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 d9476af27e8..00000000000
--- 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 e5d2a62fbc8..00000000000
--- 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 69844e31ae9..00000000000
--- 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 398df25d7bb..00000000000
--- 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 35a4f7a566e..00000000000
--- 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 05401b9080b..00000000000
--- 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 dbadecc3287..00000000000
--- 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 d4b30ae23f8..00000000000
--- 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 1de677f8883..00000000000
--- 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 e662dc43210..00000000000
--- 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 1469a7b313c..00000000000
--- 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 43a7a98127c..00000000000
--- 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 1a4c40c8bb9..00000000000
--- 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 781aed8601f..00000000000
--- 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 af3b939bcf8..00000000000
--- 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 64c079b90b7..00000000000
--- 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 a25c0013c50..00000000000
--- 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 8d83ee6be87..00000000000
--- 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 454eccb5c62..00000000000
--- 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 022760596fd..00000000000
--- 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 87ea43f8a66..00000000000
--- 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 a033ab3c4d2..00000000000
--- 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 9a28bb59dd3..00000000000
--- 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 8a647568ffe..00000000000
--- 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 71e539fe118..00000000000
--- 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 0cbf2fd2d94..00000000000
--- 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 83bd53405d1..00000000000
--- 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 34136f81bf2..00000000000
--- 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 476fad3c141..00000000000
--- 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 ac09704c28c..00000000000
--- 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 b37ce50db1e..00000000000
--- 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 bb371f727c8..00000000000
--- 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 33528ff2b97..00000000000
--- 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 da1f301089c..00000000000
--- 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 8280bd0d105..00000000000
--- 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 4ea6372e865..00000000000
--- 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 44339537ba9..00000000000
--- 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 aff5292a857..00000000000
--- 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 1eee6a6014a..00000000000
--- 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 9be7e8c3960..00000000000
--- 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 fe76f2f5842..00000000000
--- 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 21589234144..00000000000
--- 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 75f4987942f..00000000000
--- 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 f78c69692fe..159189f53c5 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 d86eb4b9df7..ac4de0f14da 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 de66beda5b2..00000000000
--- 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 251ebb230dc..00000000000
--- 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 427b05641cc..00000000000
--- 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 27f20f2d217..6592e236116 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 05022ff2404..00000000000
--- 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 910fec097b6..ba33ae98986 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 313e3f3d643..6cf564597eb 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 e168ff8abb1..7801b2c569d 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;