From a82514478172cb52828481d7e1281e2e1da701fe Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Tue, 27 Oct 2026 21:31:55 -0700 Subject: [PATCH] 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 --- doc/api/cli.md | 31 - doc/api/deprecations.md | 21 +- doc/api/errors.md | 45 +- doc/api/esm.md | 11 +- doc/api/module.md | 780 +--------------- doc/api/test.md | 6 +- doc/api/worker_threads.md | 23 +- doc/node-config-schema.json | 15 - doc/node.1 | 7 - lib/internal/bench_runner/cli.js | 1 - lib/internal/errors.js | 18 +- lib/internal/main/eval_string.js | 2 +- lib/internal/main/worker_thread.js | 52 +- lib/internal/modules/customization_hooks.js | 7 +- lib/internal/modules/esm/hooks.js | 870 ------------------ lib/internal/modules/esm/load.js | 64 +- lib/internal/modules/esm/loader.js | 518 ++--------- lib/internal/modules/esm/module_job.js | 165 +--- lib/internal/modules/esm/module_map.js | 3 +- lib/internal/modules/esm/shared_constants.js | 25 - lib/internal/modules/esm/translators.js | 148 +-- lib/internal/modules/esm/utils.js | 21 +- lib/internal/modules/esm/worker.js | 324 ------- lib/internal/modules/package_json_reader.js | 8 +- lib/internal/modules/run_main.js | 14 +- lib/internal/process/pre_execution.js | 21 +- lib/internal/test_runner/mock/loader.js | 5 +- lib/internal/worker.js | 34 +- lib/module.js | 2 - lib/worker_threads.js | 3 +- src/module_wrap.cc | 2 +- src/node_options.cc | 5 - src/node_options.h | 6 - src/node_watchdog.cc | 2 +- src/node_worker.cc | 35 +- src/node_worker.h | 5 +- test/doctool/test-doc-api-json.mjs | 4 +- test/es-module/test-esm-example-loader.mjs | 6 - .../test-esm-experimental-warnings.mjs | 50 - .../test-esm-import-assertion-warning.mjs | 42 - .../test-esm-import-defers-evaluation.js | 16 + test/es-module/test-esm-initialization.mjs | 30 - test/es-module/test-esm-loader-chaining.mjs | 507 ---------- .../test-esm-loader-custom-condition.mjs | 21 - .../test-esm-loader-default-resolver.mjs | 45 - test/es-module/test-esm-loader-dependency.mjs | 5 - test/es-module/test-esm-loader-event-loop.mjs | 12 - .../test-esm-loader-http-imports.mjs | 91 -- .../test-esm-loader-invalid-format.mjs | 10 - .../es-module/test-esm-loader-invalid-url.mjs | 10 - test/es-module/test-esm-loader-mock.mjs | 42 - test/es-module/test-esm-loader-modulemap.js | 4 +- test/es-module/test-esm-loader-not-found.mjs | 24 - .../test-esm-loader-programmatically.mjs | 251 ----- .../test-esm-loader-resolve-type.mjs | 48 - .../test-esm-loader-spawn-promisified.mjs | 304 ------ .../test-esm-loader-stringify-text.mjs | 66 -- test/es-module/test-esm-loader-thenable.mjs | 43 - .../es-module/test-esm-loader-with-source.mjs | 10 - .../test-esm-loader-with-syntax-error.mjs | 20 - test/es-module/test-esm-loader.mjs | 88 -- test/es-module/test-esm-named-exports.mjs | 9 - ...-esm-preserve-symlinks-not-found-plain.mjs | 3 - .../test-esm-preserve-symlinks-not-found.mjs | 3 - .../test-esm-register-deprecation.mjs | 60 -- .../test-esm-require-after-failed-import.js | 15 + test/es-module/test-esm-shared-loader-dep.mjs | 10 - .../test-loaders-unknown-builtin-module.mjs | 12 - .../test-loaders-workers-spawned.mjs | 83 -- .../test-require-esm-from-imported-cjs.js | 14 - test/eslint.config_partial.mjs | 1 - .../builtin-named-exports-loader.mjs | 59 -- .../builtin-named-exports.mjs | 17 - .../es-module-loaders/byop-dummy-loader.mjs | 30 - .../es-module-loaders/example-loader.mjs | 46 - .../hook-resolve-type-loader.mjs | 25 - .../es-module-loaders/hook-resolve-type.mjs | 18 - .../es-module-loaders/hooks-custom.mjs | 116 --- .../hooks-initialize-port.mjs | 16 - .../es-module-loaders/hooks-initialize.mjs | 7 - .../es-module-loaders/hooks-input.mjs | 94 -- .../es-module-loaders/http-loader.mjs | 22 - test/fixtures/es-module-loaders/js-as-esm.js | 1 - .../loader-delayed-async-load.mjs | 3 - test/fixtures/es-module-loaders/loader-dep.js | 1 - .../es-module-loaders/loader-edge-cases.mjs | 13 - .../es-module-loaders/loader-exit-on-load.mjs | 4 - .../loader-exit-on-resolve.mjs | 4 - .../loader-exit-top-level.mjs | 1 - .../loader-globalpreload-and-initialize.mjs | 2 - .../loader-globalpreload-only.mjs | 1 - .../loader-globalpreload-with-return.mjs | 3 - .../loader-initialize-exit.mjs | 3 - .../loader-initialize-never-settling.mjs | 3 - .../loader-initialize-rejecting.mjs | 3 - .../loader-initialize-throw-null.mjs | 3 - .../loader-invalid-format.mjs | 20 - .../es-module-loaders/loader-invalid-url.mjs | 10 - .../loader-load-bad-next-context.mjs | 3 - .../loader-load-bad-next-url.mjs | 3 - .../loader-load-commonjs-with-source.mjs | 13 - .../loader-load-dynamic-import.mjs | 14 - .../loader-load-foo-or-42.mjs | 19 - .../loader-load-impersonating-next-url.mjs | 3 - .../loader-load-incomplete.mjs | 14 - .../loader-load-next-modified.mjs | 11 - .../loader-load-null-return.mjs | 3 - .../loader-load-passing-modified-context.mjs | 6 - .../loader-load-passthru.mjs | 14 - ...loader-load-receiving-modified-context.mjs | 7 - .../loader-load-source-maps.mjs | 9 - .../es-module-loaders/loader-log-args.mjs | 31 - .../es-module-loaders/loader-mixed-opt-in.mjs | 28 - .../es-module-loaders/loader-resolve-42.mjs | 17 - .../loader-resolve-bad-next-context.mjs | 3 - .../loader-resolve-bad-next-specifier.mjs | 3 - .../loader-resolve-dynamic-import.mjs | 14 - .../es-module-loaders/loader-resolve-foo.mjs | 15 - .../loader-resolve-incomplete.mjs | 13 - .../loader-resolve-multiple-next-calls.mjs | 9 - .../loader-resolve-next-modified.mjs | 19 - .../loader-resolve-null-return.mjs | 3 - ...oader-resolve-passing-modified-context.mjs | 6 - .../loader-resolve-passthru.mjs | 14 - ...der-resolve-receiving-modified-context.mjs | 7 - .../loader-resolve-shortcircuit.mjs | 14 - .../loader-resolve-strip-xxx.mjs | 10 - .../loader-resolve-strip-yyy.mjs | 7 - .../es-module-loaders/loader-shared-dep.mjs | 11 - ...oader-this-value-inside-hook-functions.mjs | 21 - .../es-module-loaders/loader-throw-bigint.mjs | 1 - .../loader-throw-boolean.mjs | 1 - .../loader-throw-empty-object.mjs | 1 - .../es-module-loaders/loader-throw-error.mjs | 1 - .../loader-throw-function.mjs | 1 - .../es-module-loaders/loader-throw-null.mjs | 1 - .../es-module-loaders/loader-throw-number.mjs | 1 - .../es-module-loaders/loader-throw-object.mjs | 1 - .../es-module-loaders/loader-throw-string.mjs | 1 - .../es-module-loaders/loader-throw-symbol.mjs | 1 - .../loader-throw-undefined.mjs | 1 - .../loader-unknown-builtin-module.mjs | 7 - .../loader-with-custom-condition.mjs | 17 - .../es-module-loaders/loader-with-dep.mjs | 11 - .../loader-with-too-many-args.mjs | 7 - .../es-module-loaders/loader-worker-spawn.mjs | 7 - .../missing-dynamic-instantiate-hook.mjs | 17 - .../es-module-loaders/mock-loader.mjs | 133 --- test/fixtures/es-module-loaders/mock.mjs | 70 -- .../import.meta.never-resolve.mjs | 8 - .../never-settling-resolve-step/loader.mjs | 10 - .../never-load.cjs | 7 - .../never-load.mjs | 5 - .../never-resolve.cjs | 7 - .../never-resolve.mjs | 5 - .../never-settling-resolve-step/race.cjs | 7 - .../never-settling-resolve-step/race.mjs | 7 - .../node_modules/load/index.mjs | 1 - .../node_modules/load/package.json | 3 - .../node_modules/resolve/index.mjs | 1 - .../node_modules/resolve/package.json | 3 - .../not-found-assert-loader.mjs | 16 - .../es-module-loaders/preset-cjs-source.mjs | 21 - ...ster-hooks-with-current-cwd-parent-url.mjs | 3 - .../register-loader-with-url-parenturl.mjs | 11 - .../es-module-loaders/register-loader.cjs | 4 - .../es-module-loaders/register-loader.mjs | 4 - .../register-programmatically-loader-load.mjs | 4 - ...gister-programmatically-loader-resolve.mjs | 3 - .../es-module-loaders/string-sources.mjs | 43 - ...enable-load-hook-rejected-no-arguments.mjs | 10 - .../thenable-load-hook-rejected.mjs | 10 - .../es-module-loaders/thenable-load-hook.mjs | 10 - .../require-after-failed-import/dep.mjs | 1 + .../mismatched.mjs | 2 + .../require-esm-in-cjs-cache/hooks.js | 12 - .../require-esm-in-cjs-cache/instrument.js | 3 - .../require-esm-throws-with-loaders.js | 8 - .../worker-condition/hooks-worker.mjs | 6 - .../es-modules/worker-condition/hooks.mjs | 6 - test/fixtures/loader-is-internal-thread.js | 3 - test/fixtures/module-hooks/get-stats.js | 20 - .../module-hooks/logger-async-hooks.mjs | 19 - .../register-loader-in-sequence.mjs | 11 - ...ister-loader-initialize-never-settling.mjs | 8 - .../module-hooks/register-loader-with-cjs.cjs | 14 - .../register-loader-with-ports.mjs | 22 - .../require-esm-in-hooks-thread/dep.mjs | 1 - .../esm-with-import.mjs | 4 - .../require-esm-in-hooks-thread/hooks.mjs | 9 - .../require-resolve-paths-caller.js | 13 - .../module-hooks/sync-and-async/app.js | 2 - .../sync-and-async/async-customize-loader.js | 10 - .../sync-and-async/async-customize.js | 3 - .../sync-and-async/async-forward-loader.js | 3 - .../sync-and-async/async-forward.js | 3 - .../sync-and-async/sync-customize.js | 14 - .../sync-and-async/sync-forward.js | 7 - test/fixtures/print-delayed.js | 3 - ...loader-hooks-called-with-expected-args.mjs | 28 - ...sync-loader-hooks-called-with-register.mjs | 31 - ...obalpreload-no-warning-with-initialize.mjs | 21 - ...ync-loader-hooks-globalpreload-warning.mjs | 23 - ...nc-loader-hooks-initialize-in-sequence.mjs | 21 - ...t-async-loader-hooks-initialize-invoke.mjs | 22 - ...loader-hooks-initialize-never-settling.mjs | 18 - ...c-loader-hooks-initialize-process-exit.mjs | 23 - ...sync-loader-hooks-initialize-rejecting.mjs | 24 - ...ync-loader-hooks-initialize-throw-null.mjs | 24 - .../test-async-loader-hooks-mixed-opt-in.mjs | 20 - ...oks-never-settling-import-meta-resolve.mjs | 20 - ...c-loader-hooks-never-settling-load-cjs.mjs | 20 - ...oks-never-settling-load-esm-no-warning.mjs | 22 - ...s-never-settling-load-esm-with-warning.mjs | 21 - ...c-loader-hooks-never-settling-race-cjs.mjs | 20 - ...c-loader-hooks-never-settling-race-esm.mjs | 20 - ...oader-hooks-never-settling-resolve-cjs.mjs | 20 - ...-never-settling-resolve-esm-no-warning.mjs | 22 - ...ever-settling-resolve-esm-with-warning.mjs | 21 - ...t-async-loader-hooks-no-leak-internals.mjs | 20 - ...-async-loader-hooks-process-exit-async.mjs | 24 - ...t-async-loader-hooks-process-exit-sync.mjs | 24 - ...nc-loader-hooks-process-exit-top-level.mjs | 22 - ...t-async-loader-hooks-register-with-cjs.mjs | 20 - ...sync-loader-hooks-register-with-import.mjs | 22 - ...async-loader-hooks-register-with-ports.mjs | 18 - ...ync-loader-hooks-register-with-require.mjs | 22 - ...ader-hooks-register-with-url-parenturl.mjs | 26 - ...oader-hooks-remove-beforeexit-listener.mjs | 22 - ...ader-hooks-require-esm-in-hooks-thread.mjs | 23 - ...c-loader-hooks-require-resolve-default.mjs | 20 - ...nc-loader-hooks-require-resolve-opt-in.mjs | 22 - ...est-async-loader-hooks-source-maps-cjs.mjs | 23 - .../test-async-loader-hooks-throw-bigint.mjs | 22 - .../test-async-loader-hooks-throw-boolean.mjs | 22 - ...-async-loader-hooks-throw-empty-object.mjs | 22 - .../test-async-loader-hooks-throw-error.mjs | 22 - ...test-async-loader-hooks-throw-function.mjs | 22 - .../test-async-loader-hooks-throw-null.mjs | 22 - .../test-async-loader-hooks-throw-number.mjs | 22 - .../test-async-loader-hooks-throw-object.mjs | 22 - .../test-async-loader-hooks-throw-string.mjs | 22 - .../test-async-loader-hooks-throw-symbol.mjs | 20 - ...est-async-loader-hooks-throw-undefined.mjs | 22 - ...ync-loader-hooks-use-hooks-require-esm.mjs | 21 - ...r-hooks-with-worker-permission-allowed.mjs | 24 - ...ooks-with-worker-permission-restricted.mjs | 25 - ...loader-hooks-without-worker-permission.mjs | 25 - .../test-module-hooks-load-async-and-sync.js | 32 - .../test-module-hooks-load-builtin-import.mjs | 3 +- .../test-module-hooks-load-builtin-require.js | 3 +- ...olve-require-resolve-loaded-with-source.js | 35 - .../test-esm-loader-hooks-inspect-brk.js | 31 - .../test-esm-loader-hooks-inspect-wait.js | 35 - test/parallel/test-is-internal-thread.mjs | 20 +- .../test-permission-allow-inspector.js | 54 -- ...rocess-env-allowed-flags-are-documented.js | 1 - test/parallel/test-webworker-conditions.js | 4 - typings/internalBinding/worker.d.ts | 1 - 259 files changed, 260 insertions(+), 7880 deletions(-) delete mode 100644 lib/internal/modules/esm/hooks.js delete mode 100644 lib/internal/modules/esm/shared_constants.js delete mode 100644 lib/internal/modules/esm/worker.js delete mode 100644 test/es-module/test-esm-example-loader.mjs delete mode 100644 test/es-module/test-esm-experimental-warnings.mjs delete mode 100644 test/es-module/test-esm-import-assertion-warning.mjs create mode 100644 test/es-module/test-esm-import-defers-evaluation.js delete mode 100644 test/es-module/test-esm-initialization.mjs delete mode 100644 test/es-module/test-esm-loader-chaining.mjs delete mode 100644 test/es-module/test-esm-loader-custom-condition.mjs delete mode 100644 test/es-module/test-esm-loader-dependency.mjs delete mode 100644 test/es-module/test-esm-loader-event-loop.mjs delete mode 100644 test/es-module/test-esm-loader-http-imports.mjs delete mode 100644 test/es-module/test-esm-loader-invalid-format.mjs delete mode 100644 test/es-module/test-esm-loader-invalid-url.mjs delete mode 100644 test/es-module/test-esm-loader-mock.mjs delete mode 100644 test/es-module/test-esm-loader-not-found.mjs delete mode 100644 test/es-module/test-esm-loader-programmatically.mjs delete mode 100644 test/es-module/test-esm-loader-resolve-type.mjs delete mode 100644 test/es-module/test-esm-loader-spawn-promisified.mjs delete mode 100644 test/es-module/test-esm-loader-stringify-text.mjs delete mode 100644 test/es-module/test-esm-loader-thenable.mjs delete mode 100644 test/es-module/test-esm-loader-with-source.mjs delete mode 100644 test/es-module/test-esm-loader-with-syntax-error.mjs delete mode 100644 test/es-module/test-esm-loader.mjs delete mode 100644 test/es-module/test-esm-named-exports.mjs delete mode 100644 test/es-module/test-esm-preserve-symlinks-not-found-plain.mjs delete mode 100644 test/es-module/test-esm-preserve-symlinks-not-found.mjs delete mode 100644 test/es-module/test-esm-register-deprecation.mjs create mode 100644 test/es-module/test-esm-require-after-failed-import.js delete mode 100644 test/es-module/test-esm-shared-loader-dep.mjs delete mode 100644 test/es-module/test-loaders-unknown-builtin-module.mjs delete mode 100644 test/es-module/test-loaders-workers-spawned.mjs delete mode 100644 test/fixtures/es-module-loaders/builtin-named-exports-loader.mjs delete mode 100644 test/fixtures/es-module-loaders/builtin-named-exports.mjs delete mode 100644 test/fixtures/es-module-loaders/byop-dummy-loader.mjs delete mode 100644 test/fixtures/es-module-loaders/example-loader.mjs delete mode 100644 test/fixtures/es-module-loaders/hook-resolve-type-loader.mjs delete mode 100644 test/fixtures/es-module-loaders/hook-resolve-type.mjs delete mode 100644 test/fixtures/es-module-loaders/hooks-custom.mjs delete mode 100644 test/fixtures/es-module-loaders/hooks-initialize-port.mjs delete mode 100644 test/fixtures/es-module-loaders/hooks-initialize.mjs delete mode 100644 test/fixtures/es-module-loaders/hooks-input.mjs delete mode 100644 test/fixtures/es-module-loaders/http-loader.mjs delete mode 100644 test/fixtures/es-module-loaders/js-as-esm.js delete mode 100644 test/fixtures/es-module-loaders/loader-delayed-async-load.mjs delete mode 100644 test/fixtures/es-module-loaders/loader-dep.js delete mode 100644 test/fixtures/es-module-loaders/loader-edge-cases.mjs delete mode 100644 test/fixtures/es-module-loaders/loader-exit-on-load.mjs delete mode 100644 test/fixtures/es-module-loaders/loader-exit-on-resolve.mjs delete mode 100644 test/fixtures/es-module-loaders/loader-exit-top-level.mjs delete mode 100644 test/fixtures/es-module-loaders/loader-globalpreload-and-initialize.mjs delete mode 100644 test/fixtures/es-module-loaders/loader-globalpreload-only.mjs delete mode 100644 test/fixtures/es-module-loaders/loader-globalpreload-with-return.mjs delete mode 100644 test/fixtures/es-module-loaders/loader-initialize-exit.mjs delete mode 100644 test/fixtures/es-module-loaders/loader-initialize-never-settling.mjs delete mode 100644 test/fixtures/es-module-loaders/loader-initialize-rejecting.mjs delete mode 100644 test/fixtures/es-module-loaders/loader-initialize-throw-null.mjs delete mode 100644 test/fixtures/es-module-loaders/loader-invalid-format.mjs delete mode 100644 test/fixtures/es-module-loaders/loader-invalid-url.mjs delete mode 100644 test/fixtures/es-module-loaders/loader-load-bad-next-context.mjs delete mode 100644 test/fixtures/es-module-loaders/loader-load-bad-next-url.mjs delete mode 100644 test/fixtures/es-module-loaders/loader-load-commonjs-with-source.mjs delete mode 100644 test/fixtures/es-module-loaders/loader-load-dynamic-import.mjs delete mode 100644 test/fixtures/es-module-loaders/loader-load-foo-or-42.mjs delete mode 100644 test/fixtures/es-module-loaders/loader-load-impersonating-next-url.mjs delete mode 100644 test/fixtures/es-module-loaders/loader-load-incomplete.mjs delete mode 100644 test/fixtures/es-module-loaders/loader-load-next-modified.mjs delete mode 100644 test/fixtures/es-module-loaders/loader-load-null-return.mjs delete mode 100644 test/fixtures/es-module-loaders/loader-load-passing-modified-context.mjs delete mode 100644 test/fixtures/es-module-loaders/loader-load-passthru.mjs delete mode 100644 test/fixtures/es-module-loaders/loader-load-receiving-modified-context.mjs delete mode 100644 test/fixtures/es-module-loaders/loader-load-source-maps.mjs delete mode 100644 test/fixtures/es-module-loaders/loader-log-args.mjs delete mode 100644 test/fixtures/es-module-loaders/loader-mixed-opt-in.mjs delete mode 100644 test/fixtures/es-module-loaders/loader-resolve-42.mjs delete mode 100644 test/fixtures/es-module-loaders/loader-resolve-bad-next-context.mjs delete mode 100644 test/fixtures/es-module-loaders/loader-resolve-bad-next-specifier.mjs delete mode 100644 test/fixtures/es-module-loaders/loader-resolve-dynamic-import.mjs delete mode 100644 test/fixtures/es-module-loaders/loader-resolve-foo.mjs delete mode 100644 test/fixtures/es-module-loaders/loader-resolve-incomplete.mjs delete mode 100644 test/fixtures/es-module-loaders/loader-resolve-multiple-next-calls.mjs delete mode 100644 test/fixtures/es-module-loaders/loader-resolve-next-modified.mjs delete mode 100644 test/fixtures/es-module-loaders/loader-resolve-null-return.mjs delete mode 100644 test/fixtures/es-module-loaders/loader-resolve-passing-modified-context.mjs delete mode 100644 test/fixtures/es-module-loaders/loader-resolve-passthru.mjs delete mode 100644 test/fixtures/es-module-loaders/loader-resolve-receiving-modified-context.mjs delete mode 100644 test/fixtures/es-module-loaders/loader-resolve-shortcircuit.mjs delete mode 100644 test/fixtures/es-module-loaders/loader-resolve-strip-xxx.mjs delete mode 100644 test/fixtures/es-module-loaders/loader-resolve-strip-yyy.mjs delete mode 100644 test/fixtures/es-module-loaders/loader-shared-dep.mjs delete mode 100644 test/fixtures/es-module-loaders/loader-this-value-inside-hook-functions.mjs delete mode 100644 test/fixtures/es-module-loaders/loader-throw-bigint.mjs delete mode 100644 test/fixtures/es-module-loaders/loader-throw-boolean.mjs delete mode 100644 test/fixtures/es-module-loaders/loader-throw-empty-object.mjs delete mode 100644 test/fixtures/es-module-loaders/loader-throw-error.mjs delete mode 100644 test/fixtures/es-module-loaders/loader-throw-function.mjs delete mode 100644 test/fixtures/es-module-loaders/loader-throw-null.mjs delete mode 100644 test/fixtures/es-module-loaders/loader-throw-number.mjs delete mode 100644 test/fixtures/es-module-loaders/loader-throw-object.mjs delete mode 100644 test/fixtures/es-module-loaders/loader-throw-string.mjs delete mode 100644 test/fixtures/es-module-loaders/loader-throw-symbol.mjs delete mode 100644 test/fixtures/es-module-loaders/loader-throw-undefined.mjs delete mode 100644 test/fixtures/es-module-loaders/loader-unknown-builtin-module.mjs delete mode 100644 test/fixtures/es-module-loaders/loader-with-custom-condition.mjs delete mode 100644 test/fixtures/es-module-loaders/loader-with-dep.mjs delete mode 100644 test/fixtures/es-module-loaders/loader-with-too-many-args.mjs delete mode 100644 test/fixtures/es-module-loaders/loader-worker-spawn.mjs delete mode 100644 test/fixtures/es-module-loaders/missing-dynamic-instantiate-hook.mjs delete mode 100644 test/fixtures/es-module-loaders/mock-loader.mjs delete mode 100644 test/fixtures/es-module-loaders/mock.mjs delete mode 100644 test/fixtures/es-module-loaders/never-settling-resolve-step/import.meta.never-resolve.mjs delete mode 100644 test/fixtures/es-module-loaders/never-settling-resolve-step/loader.mjs delete mode 100644 test/fixtures/es-module-loaders/never-settling-resolve-step/never-load.cjs delete mode 100644 test/fixtures/es-module-loaders/never-settling-resolve-step/never-load.mjs delete mode 100644 test/fixtures/es-module-loaders/never-settling-resolve-step/never-resolve.cjs delete mode 100644 test/fixtures/es-module-loaders/never-settling-resolve-step/never-resolve.mjs delete mode 100644 test/fixtures/es-module-loaders/never-settling-resolve-step/race.cjs delete mode 100644 test/fixtures/es-module-loaders/never-settling-resolve-step/race.mjs delete mode 100644 test/fixtures/es-module-loaders/node_modules/load/index.mjs delete mode 100644 test/fixtures/es-module-loaders/node_modules/load/package.json delete mode 100644 test/fixtures/es-module-loaders/node_modules/resolve/index.mjs delete mode 100644 test/fixtures/es-module-loaders/node_modules/resolve/package.json delete mode 100644 test/fixtures/es-module-loaders/not-found-assert-loader.mjs delete mode 100644 test/fixtures/es-module-loaders/preset-cjs-source.mjs delete mode 100644 test/fixtures/es-module-loaders/register-hooks-with-current-cwd-parent-url.mjs delete mode 100644 test/fixtures/es-module-loaders/register-loader-with-url-parenturl.mjs delete mode 100644 test/fixtures/es-module-loaders/register-loader.cjs delete mode 100644 test/fixtures/es-module-loaders/register-loader.mjs delete mode 100644 test/fixtures/es-module-loaders/register-programmatically-loader-load.mjs delete mode 100644 test/fixtures/es-module-loaders/register-programmatically-loader-resolve.mjs delete mode 100644 test/fixtures/es-module-loaders/string-sources.mjs delete mode 100644 test/fixtures/es-module-loaders/thenable-load-hook-rejected-no-arguments.mjs delete mode 100644 test/fixtures/es-module-loaders/thenable-load-hook-rejected.mjs delete mode 100644 test/fixtures/es-module-loaders/thenable-load-hook.mjs create mode 100644 test/fixtures/es-modules/require-after-failed-import/dep.mjs create mode 100644 test/fixtures/es-modules/require-after-failed-import/mismatched.mjs delete mode 100644 test/fixtures/es-modules/require-esm-in-cjs-cache/hooks.js delete mode 100644 test/fixtures/es-modules/require-esm-in-cjs-cache/instrument.js delete mode 100644 test/fixtures/es-modules/require-esm-throws-with-loaders.js delete mode 100644 test/fixtures/es-modules/worker-condition/hooks-worker.mjs delete mode 100644 test/fixtures/es-modules/worker-condition/hooks.mjs delete mode 100644 test/fixtures/loader-is-internal-thread.js delete mode 100644 test/fixtures/module-hooks/get-stats.js delete mode 100644 test/fixtures/module-hooks/logger-async-hooks.mjs delete mode 100644 test/fixtures/module-hooks/register-loader-in-sequence.mjs delete mode 100644 test/fixtures/module-hooks/register-loader-initialize-never-settling.mjs delete mode 100644 test/fixtures/module-hooks/register-loader-with-cjs.cjs delete mode 100644 test/fixtures/module-hooks/register-loader-with-ports.mjs delete mode 100644 test/fixtures/module-hooks/require-esm-in-hooks-thread/dep.mjs delete mode 100644 test/fixtures/module-hooks/require-esm-in-hooks-thread/esm-with-import.mjs delete mode 100644 test/fixtures/module-hooks/require-esm-in-hooks-thread/hooks.mjs delete mode 100644 test/fixtures/module-hooks/require-resolve-paths-caller.js delete mode 100644 test/fixtures/module-hooks/sync-and-async/app.js delete mode 100644 test/fixtures/module-hooks/sync-and-async/async-customize-loader.js delete mode 100644 test/fixtures/module-hooks/sync-and-async/async-customize.js delete mode 100644 test/fixtures/module-hooks/sync-and-async/async-forward-loader.js delete mode 100644 test/fixtures/module-hooks/sync-and-async/async-forward.js delete mode 100644 test/fixtures/module-hooks/sync-and-async/sync-customize.js delete mode 100644 test/fixtures/module-hooks/sync-and-async/sync-forward.js delete mode 100644 test/fixtures/print-delayed.js delete mode 100644 test/module-hooks/test-async-loader-hooks-called-with-expected-args.mjs delete mode 100644 test/module-hooks/test-async-loader-hooks-called-with-register.mjs delete mode 100644 test/module-hooks/test-async-loader-hooks-globalpreload-no-warning-with-initialize.mjs delete mode 100644 test/module-hooks/test-async-loader-hooks-globalpreload-warning.mjs delete mode 100644 test/module-hooks/test-async-loader-hooks-initialize-in-sequence.mjs delete mode 100644 test/module-hooks/test-async-loader-hooks-initialize-invoke.mjs delete mode 100644 test/module-hooks/test-async-loader-hooks-initialize-never-settling.mjs delete mode 100644 test/module-hooks/test-async-loader-hooks-initialize-process-exit.mjs delete mode 100644 test/module-hooks/test-async-loader-hooks-initialize-rejecting.mjs delete mode 100644 test/module-hooks/test-async-loader-hooks-initialize-throw-null.mjs delete mode 100644 test/module-hooks/test-async-loader-hooks-mixed-opt-in.mjs delete mode 100644 test/module-hooks/test-async-loader-hooks-never-settling-import-meta-resolve.mjs delete mode 100644 test/module-hooks/test-async-loader-hooks-never-settling-load-cjs.mjs delete mode 100644 test/module-hooks/test-async-loader-hooks-never-settling-load-esm-no-warning.mjs delete mode 100644 test/module-hooks/test-async-loader-hooks-never-settling-load-esm-with-warning.mjs delete mode 100644 test/module-hooks/test-async-loader-hooks-never-settling-race-cjs.mjs delete mode 100644 test/module-hooks/test-async-loader-hooks-never-settling-race-esm.mjs delete mode 100644 test/module-hooks/test-async-loader-hooks-never-settling-resolve-cjs.mjs delete mode 100644 test/module-hooks/test-async-loader-hooks-never-settling-resolve-esm-no-warning.mjs delete mode 100644 test/module-hooks/test-async-loader-hooks-never-settling-resolve-esm-with-warning.mjs delete mode 100644 test/module-hooks/test-async-loader-hooks-no-leak-internals.mjs delete mode 100644 test/module-hooks/test-async-loader-hooks-process-exit-async.mjs delete mode 100644 test/module-hooks/test-async-loader-hooks-process-exit-sync.mjs delete mode 100644 test/module-hooks/test-async-loader-hooks-process-exit-top-level.mjs delete mode 100644 test/module-hooks/test-async-loader-hooks-register-with-cjs.mjs delete mode 100644 test/module-hooks/test-async-loader-hooks-register-with-import.mjs delete mode 100644 test/module-hooks/test-async-loader-hooks-register-with-ports.mjs delete mode 100644 test/module-hooks/test-async-loader-hooks-register-with-require.mjs delete mode 100644 test/module-hooks/test-async-loader-hooks-register-with-url-parenturl.mjs delete mode 100644 test/module-hooks/test-async-loader-hooks-remove-beforeexit-listener.mjs delete mode 100644 test/module-hooks/test-async-loader-hooks-require-esm-in-hooks-thread.mjs delete mode 100644 test/module-hooks/test-async-loader-hooks-require-resolve-default.mjs delete mode 100644 test/module-hooks/test-async-loader-hooks-require-resolve-opt-in.mjs delete mode 100644 test/module-hooks/test-async-loader-hooks-source-maps-cjs.mjs delete mode 100644 test/module-hooks/test-async-loader-hooks-throw-bigint.mjs delete mode 100644 test/module-hooks/test-async-loader-hooks-throw-boolean.mjs delete mode 100644 test/module-hooks/test-async-loader-hooks-throw-empty-object.mjs delete mode 100644 test/module-hooks/test-async-loader-hooks-throw-error.mjs delete mode 100644 test/module-hooks/test-async-loader-hooks-throw-function.mjs delete mode 100644 test/module-hooks/test-async-loader-hooks-throw-null.mjs delete mode 100644 test/module-hooks/test-async-loader-hooks-throw-number.mjs delete mode 100644 test/module-hooks/test-async-loader-hooks-throw-object.mjs delete mode 100644 test/module-hooks/test-async-loader-hooks-throw-string.mjs delete mode 100644 test/module-hooks/test-async-loader-hooks-throw-symbol.mjs delete mode 100644 test/module-hooks/test-async-loader-hooks-throw-undefined.mjs delete mode 100644 test/module-hooks/test-async-loader-hooks-use-hooks-require-esm.mjs delete mode 100644 test/module-hooks/test-async-loader-hooks-with-worker-permission-allowed.mjs delete mode 100644 test/module-hooks/test-async-loader-hooks-with-worker-permission-restricted.mjs delete mode 100644 test/module-hooks/test-async-loader-hooks-without-worker-permission.mjs delete mode 100644 test/module-hooks/test-module-hooks-load-async-and-sync.js delete mode 100644 test/module-hooks/test-module-hooks-resolve-require-resolve-loaded-with-source.js delete mode 100644 test/parallel/test-esm-loader-hooks-inspect-brk.js delete mode 100644 test/parallel/test-esm-loader-hooks-inspect-wait.js delete mode 100644 test/parallel/test-permission-allow-inspector.js diff --git a/doc/api/cli.md b/doc/api/cli.md index bddd4a5c0a5f..af67e5478ea9 100644 --- a/doc/api/cli.md +++ b/doc/api/cli.md @@ -1509,33 +1509,6 @@ added: Enable experimental support for inspector network resources. -### `--experimental-loader=module` - - - -> This flag is discouraged and may be removed in a future version of Node.js. -> Please use -> [`--import` with `register()`][preloading asynchronous module customization hooks] instead. - -Specify the `module` containing exported [asynchronous module customization hooks][]. -`module` may be any string accepted as an [`import` specifier][]. - -This feature requires `--allow-worker` if used with the [Permission Model][]. - ### `--experimental-network-inspection` -Type: Runtime +Type: End-of-Life -[`module.register()`][] is deprecated. Use [`module.registerHooks()`][] -instead. +The `module.register()` API and the `--experimental-loader` and `--loader` +flags have been removed. Use [`module.registerHooks()`][] instead. -The `module.register()` API provides off-thread async hooks for customizing ES modules; -the `module.registerHooks()` API provides similar hooks that are synchronous, in-thread, and -work for all types of modules. -Supporting async hooks has proven to be complex, involving worker threads orchestration, and there are issues -that have proven unresolvable. See [caveats of asynchronous customization hooks][]. Please migrate to -`module.registerHooks()` as soon as possible as `module.register()` will be -removed in a future version of Node.js. +The `module.register()` API provided off-thread async module customization +hooks for customizing ES modules; the `module.registerHooks()` API provides +similar hooks that are synchronous and work for all types of modules. ### DEP0206: Calling `digest()` on an already-finalized `Hmac` instance @@ -4935,7 +4934,6 @@ Type: Documentation-only [`message.trailersDistinct`]: http.md#messagetrailersdistinct [`message.trailers`]: http.md#messagetrailers [`module.createRequire()`]: module.md#modulecreaterequirefilename -[`module.register()`]: module.md#moduleregisterspecifier-parenturl-options [`module.registerHooks()`]: module.md#moduleregisterhooksoptions [`os.networkInterfaces()`]: os.md#osnetworkinterfaces [`os.tmpdir()`]: os.md#ostmpdir @@ -4991,7 +4989,6 @@ Type: Documentation-only [`zlib.bytesWritten`]: zlib.md#zlibbyteswritten [alloc]: buffer.md#static-method-bufferallocsize-fill-encoding [alloc_unsafe_size]: buffer.md#static-method-bufferallocunsafesize-alignment -[caveats of asynchronous customization hooks]: module.md#caveats-of-asynchronous-customization-hooks [from_arraybuffer]: buffer.md#static-method-bufferfromarraybuffer-byteoffset-length [from_string_encoding]: buffer.md#static-method-bufferfromstring-encoding [legacy URL API]: url.md#legacy-url-api diff --git a/doc/api/errors.md b/doc/api/errors.md index 2a1e423d10dc..27004c6d5eb2 100644 --- a/doc/api/errors.md +++ b/doc/api/errors.md @@ -733,13 +733,6 @@ by the `node:assert` module. An attempt was made to register something that is not a function as an `AsyncHooks` callback. - - -### `ERR_ASYNC_LOADER_REQUEST_NEVER_SETTLED` - -An operation related to module loading is customized by an asynchronous loader -hook that never settled the promise before the loader thread exits. - ### `ERR_ASYNC_RESOURCE_DOMAIN_REMOVED` @@ -2354,19 +2347,6 @@ for more information. IP is blocked by `net.BlockList`. - - -### `ERR_LOADER_CHAIN_INCOMPLETE` - - - -An ESM loader hook returned without calling `next()` and without explicitly -signaling a short circuit. - ### `ERR_LOAD_SQLITE_EXTENSION` @@ -3915,6 +3895,17 @@ attempting a [`require()`][] operation or when loading the program entry point. > Stability: 0 - Deprecated. These error codes are either inconsistent, or have > been removed. + + +### `ERR_ASYNC_LOADER_REQUEST_NEVER_SETTLED` + + + +An operation related to module loading is customized by an asynchronous loader +hook that never settled the promise before the loader thread exits. + ### `ERR_CANNOT_TRANSFER_OBJECT` @@ -4153,6 +4144,20 @@ changes: An invalid transfer object was passed to `postMessage()`. + + +### `ERR_LOADER_CHAIN_INCOMPLETE` + + + +An ESM loader hook returned without calling `next()` and without explicitly +signaling a short circuit. + ### `ERR_MANIFEST_ASSERT_INTEGRITY` diff --git a/doc/api/esm.md b/doc/api/esm.md index ef15a51dcfe5..bc4af89045f7 100644 --- a/doc/api/esm.md +++ b/doc/api/esm.md @@ -199,8 +199,7 @@ ES modules are resolved and cached as URLs. This means that special characters must be [percent-encoded][], such as `#` with `%23` and `?` with `%3F`. `file:`, `node:`, and `data:` URL schemes are supported. A specifier like -`'https://example.com/app.js'` is not supported natively in Node.js unless using -a [custom HTTPS loader][]. +`'https://example.com/app.js'` is not supported natively in Node.js. #### `file:` URLs @@ -493,8 +492,6 @@ resolutions are subject to the permitted exports resolutions within the package. * This can result in synchronous file-system operations, which can impact performance similarly to `require.resolve`. -* This feature is not available within custom loaders (it would - create a deadlock). **Non-standard API**: @@ -1316,8 +1313,8 @@ _isImports_, _conditions_) ### Customizing ESM specifier resolution algorithm [Module customization hooks][] provide a mechanism for customizing the ESM -specifier resolution algorithm. An example that provides CommonJS-style -resolution for ESM specifiers is [commonjs-extension-resolution-loader][]. +specifier resolution algorithm, for example to provide CommonJS-style +resolution for ESM specifiers. @@ -1360,8 +1357,6 @@ resolution for ESM specifiers is [commonjs-extension-resolution-loader][]. [`process.dlopen`]: process.md#processdlopenmodule-filename-flags [`require(esm)`]: modules.md#loading-ecmascript-modules-using-require [`url.fileURLToPath()`]: url.md#urlfileurltopathurl-options -[commonjs-extension-resolution-loader]: https://github.com/nodejs/loaders-test/tree/main/commonjs-extension-resolution-loader -[custom https loader]: module.md#import-from-https [import.meta.resolve]: #importmetaresolvespecifier [merve]: https://github.com/anonrig/merve/tree/v1.0.0 [percent-encoded]: url.md#percent-encoding-in-urls diff --git a/doc/api/module.md b/doc/api/module.md index f86167e8d44f..309114f9468c 100644 --- a/doc/api/module.md +++ b/doc/api/module.md @@ -176,56 +176,6 @@ isBuiltin('fs'); // true isBuiltin('wss'); // false ``` -## `module.register(specifier[, parentURL][, options])` - - - -> Stability: 0 - Deprecated: Use [`module.registerHooks()`][] instead. - -* `specifier` {string|URL} Customization hooks to be registered; this should be - the same string that would be passed to `import()`, except that if it is - relative, it is resolved relative to `parentURL`. -* `parentURL` {string|URL} If you want to resolve `specifier` relative to a base - URL, such as `import.meta.url`, you can pass that URL here. **Default:** - `'data:'` -* `options` {Object} - * `parentURL` {string|URL} If you want to resolve `specifier` relative to a - base URL, such as `import.meta.url`, you can pass that URL here. This - property is ignored if the `parentURL` is supplied as the second argument. - **Default:** `'data:'` - * `data` {any} Any arbitrary, cloneable JavaScript value to pass into the - [`initialize`][] hook. - * `transferList` {Object\[]} [transferable objects][] to be passed into the - `initialize` hook. - -Register a module that exports [hooks][] that customize Node.js module -resolution and loading behavior. See [Customization hooks][]. - -This feature requires `--allow-worker` if used with the [Permission Model][]. - ## `module.registerHooks(options)` -Node.js currently supports two types of module customization hooks: - -1. [`module.registerHooks(options)`][`module.registerHooks()`]: takes synchronous hook - functions that are run directly on the thread where the modules are loaded. -2. [`module.register(specifier[, parentURL][, options])`][`register`]: takes specifier to a - module that exports asynchronous hook functions. The functions are run on a - separate loader thread. - -The asynchronous hooks incur extra overhead from inter-thread communication, -and have [several caveats][caveats of asynchronous customization hooks] especially -when customizing CommonJS modules in the module graph. -In most cases, it's recommended to use synchronous hooks via `module.registerHooks()` -for simplicity. - -### Synchronous customization hooks +Node.js supports module customization hooks registered with +[`module.registerHooks()`][]. > Stability: 1.2 - Release candidate -#### Registration of synchronous customization hooks +### Registration of customization hooks -To register synchronous customization hooks, use [`module.registerHooks()`][], which -takes [synchronous hook functions][] directly in-line. +To register customization hooks, use [`module.registerHooks()`][], which +takes [hook functions][] directly in-line. ```mjs // register-hooks.js @@ -711,7 +648,7 @@ registerHooks({ }); ``` -##### Registering hooks before application code runs with flags +#### Registering hooks before application code runs with flags The hooks can be registered before the application code is run by using the [`--import`][] or [`--require`][] flag: @@ -736,7 +673,7 @@ Using `--import` or `--require` ensures that the hooks are registered before any application code is loaded, including the entry point of the application and for any worker threads by default as well. -##### Registering hooks before application code runs programmatically +#### Registering hooks before application code runs programmatically Alternatively, `registerHooks()` can be called from the entry point. @@ -751,7 +688,7 @@ module. ```mjs import { registerHooks } from 'node:module'; -registerHooks({ /* implementation of synchronous hooks */ }); +registerHooks({ /* implementation of hooks */ }); // If loaded using static import, the hooks would not be applied when loading // my-app.mjs, because statically imported modules are all executed before its @@ -765,7 +702,7 @@ await import('./my-app.mjs'); ```cjs const { registerHooks } = require('node:module'); -registerHooks({ /* implementation of synchronous hooks */ }); +registerHooks({ /* implementation of hooks */ }); import('./my-app.mjs'); // Or, if my-app.mjs does not have top-level await or it's a CommonJS module, @@ -773,7 +710,7 @@ import('./my-app.mjs'); // require('./my-app.mjs'); ``` -##### Registering hooks before application code runs with a `data:` URL +#### Registering hooks before application code runs with a `data:` URL Alternatively, inline JavaScript code can be embedded in `data:` URLs to register the hooks before the application code runs. For example, @@ -782,7 +719,7 @@ the hooks before the application code runs. For example, node --import 'data:text/javascript,import {registerHooks} from "node:module"; registerHooks(/* hooks code */);' ./my-app.js ``` -#### Convention of hooks and chaining +### Convention of hooks and chaining Hooks are part of a chain, even if that chain consists of only one custom (user-provided) hook and the default hook, which is always present. @@ -833,14 +770,13 @@ hook to signal that the chain is intentionally ending at your hook. If a hook should be applied when loading other hook modules, the other hook modules should be loaded after the hook is registered. -#### Deregistration of synchronous customization hooks +### Deregistration of customization hooks The object returned by `registerHooks()` has a `deregister()` method that can be used to remove the hooks from the chain. Once `deregister()` is called, the hooks will no longer be invoked during module resolution or loading. -This is currently only available for synchronous hooks registered via `registerHooks()`, not for asynchronous -hooks registered via `module.register()`. +This is available for hooks registered via `registerHooks()`. ```mjs import { registerHooks } from 'node:module'; @@ -890,7 +826,7 @@ hooks.deregister(); require('./another-module.cjs'); ``` -#### Hook functions accepted by `module.registerHooks()` +### Hook functions accepted by `module.registerHooks()` -The `module.registerHooks()` method accepts the following synchronous hook functions. +The `module.registerHooks()` method accepts the following hook functions. ```mjs function resolve(specifier, context, nextResolve) { @@ -910,16 +846,16 @@ function load(url, context, nextLoad) { } ``` -Synchronous hooks are run in the same thread and the same [realm][] where the modules -are loaded, the code in the hook function can pass values to the modules being referenced -directly via global variables or other shared states. +Hooks are run in the same [realm][] as the modules being loaded, so the code in the +hook function can pass values to the modules being referenced directly via global +variables or other shared states. -Unlike the asynchronous hooks, the synchronous hooks are not inherited into child worker -threads by default, though if the hooks are registered using a file preloaded by -[`--import`][] or [`--require`][], child worker threads can inherit the preloaded scripts -via `process.execArgv` inheritance. See [the documentation of `Worker`][] for details. +Hooks are not inherited into child worker threads by default. If the hooks are +registered using a file preloaded by [`--import`][] or [`--require`][], child worker threads +can inherit the preloaded scripts via `process.execArgv` inheritance. See +[the documentation of `Worker`][] for details. -#### Synchronous `resolve(specifier, context, nextResolve)` +### `resolve(specifier, context, nextResolve)` - -The [`register`][] method can be used to register a module that exports a set of -hooks. The hooks are functions that are called by Node.js to customize the -module resolution and loading process. The exported functions must have specific -names and signatures, and they must be exported as named exports. - -```mjs -export async function initialize({ number, port }) { - // Receives data from `register`. -} - -export async function resolve(specifier, context, nextResolve) { - // Take an `import` or `require` specifier and resolve it to a URL. -} - -export async function load(url, context, nextLoad) { - // Take a resolved URL and return the source code to be evaluated. -} -``` - -Asynchronous hooks are run in a separate thread, isolated from the main thread where -application code runs. That means it is a different [realm][]. The hooks thread -may be terminated by the main thread at any time, so do not depend on -asynchronous operations (like `console.log`) to complete. They are inherited into -child workers by default. - -#### `initialize()` - - - -* `data` {any} The data from `register(loader, import.meta.url, { data })`. - -The `initialize` hook is only accepted by [`register`][]. `registerHooks()` does -not support nor need it since initialization done for synchronous hooks can be run -directly before the call to `registerHooks()`. - -The `initialize` hook provides a way to define a custom function that runs in -the hooks thread when the hooks module is initialized. Initialization happens -when the hooks module is registered via [`register`][]. - -This hook can receive data from a [`register`][] invocation, including -ports and other transferable objects. The return value of `initialize` can be a -{Promise}, in which case it will be awaited before the main application thread -execution resumes. - -Module customization code: - -```mjs -// path-to-my-hooks.js - -export async function initialize({ number, port }) { - port.postMessage(`increment: ${number + 1}`); -} -``` - -Caller code: - -```mjs -import assert from 'node:assert'; -import { register } from 'node:module'; -import { MessageChannel } from 'node:worker_threads'; - -// This example showcases how a message channel can be used to communicate -// between the main (application) thread and the hooks running on the hooks -// thread, by sending `port2` to the `initialize` hook. -const { port1, port2 } = new MessageChannel(); - -port1.on('message', (msg) => { - assert.strictEqual(msg, 'increment: 2'); -}); -port1.unref(); - -register('./path-to-my-hooks.js', { - parentURL: import.meta.url, - data: { number: 1, port: port2 }, - transferList: [port2], -}); -``` - -```cjs -const assert = require('node:assert'); -const { register } = require('node:module'); -const { pathToFileURL } = require('node:url'); -const { MessageChannel } = require('node:worker_threads'); - -// This example showcases how a message channel can be used to communicate -// between the main (application) thread and the hooks running on the hooks -// thread, by sending `port2` to the `initialize` hook. -const { port1, port2 } = new MessageChannel(); - -port1.on('message', (msg) => { - assert.strictEqual(msg, 'increment: 2'); -}); -port1.unref(); - -register('./path-to-my-hooks.js', { - parentURL: pathToFileURL(__filename), - data: { number: 1, port: port2 }, - transferList: [port2], -}); -``` - -#### Asynchronous `resolve(specifier, context, nextResolve)` - - - -* `specifier` {string} -* `context` {Object} - * `conditions` {string\[]} Export conditions of the relevant `package.json` - * `importAttributes` {Object} An object whose key-value pairs represent the - attributes for the module to import - * `parentURL` {string|undefined} The module importing this one, or undefined - if this is the Node.js entry point -* `nextResolve` {Function} The subsequent `resolve` hook in the chain, or the - Node.js default `resolve` hook after the last user-supplied `resolve` hook - * `specifier` {string} - * `context` {Object|undefined} When omitted, the defaults are provided. When provided, defaults - are merged in with preference to the provided properties. -* Returns: {Object|Promise} The asynchronous version takes either an object containing the - following properties, or a `Promise` that will resolve to such an object. - * `format` {string|null|undefined} A hint to the `load` hook (it might be ignored). It can be a - module format (such as `'commonjs'` or `'module'`) or an arbitrary value like `'css'` or - `'yaml'`. - * `importAttributes` {Object|undefined} The import attributes to use when - caching the module (optional; if excluded the input will be used) - * `shortCircuit` {undefined|boolean} A signal that this hook intends to - terminate the chain of `resolve` hooks. **Default:** `false` - * `url` {string} The absolute URL to which this input resolves - -The asynchronous version works similarly to the synchronous version, only that the -`nextResolve` function returns a `Promise`, and the `resolve` hook itself can return a `Promise`. - -> **Warning** In the case of the asynchronous version, despite support for returning -> promises and async functions, calls to `resolve` may still block the main thread which -> can impact performance. - -> **Warning** The `resolve` hook invoked for `require()` calls inside CommonJS modules -> customized by asynchronous hooks does not receive the original specifier passed to -> `require()`. Instead, it receives a URL already fully resolved using the default -> CommonJS resolution. - -> **Warning** In the CommonJS modules that are customized by the asynchronous customization hooks, -> `require.resolve()` and `require()` will use `"import"` export condition instead of -> `"require"`, which may cause unexpected behaviors when loading dual packages. - -```mjs -export async function resolve(specifier, context, nextResolve) { - // When calling `defaultResolve`, the arguments can be modified. For example, - // to change the specifier or add conditions. - if (specifier.includes('foo')) { - specifier = specifier.replace('foo', 'bar'); - return nextResolve(specifier, { - ...context, - conditions: [...context.conditions, 'another-condition'], - }); - } - - // The hook can also skips default resolution and provide a custom URL. - if (specifier === 'special-module') { - return { - url: 'file:///path/to/special-module.mjs', - format: 'module', - shortCircuit: true, // This is mandatory if not calling nextResolve(). - }; - } - - // If no customization is needed, defer to the next hook in the chain which would be the - // Node.js default resolve if this is the last user-specified loader. - return nextResolve(specifier); -} -``` - -#### Asynchronous `load(url, context, nextLoad)` - - - -* `url` {string} The URL returned by the `resolve` chain -* `context` {Object} - * `conditions` {string\[]} Export conditions of the relevant `package.json` - * `format` {string|null|undefined} The format optionally supplied by the - `resolve` hook chain. This can be any string value as an input; input values do not need to - conform to the list of acceptable return values described below. - * `importAttributes` {Object} -* `nextLoad` {Function} The subsequent `load` hook in the chain, or the - Node.js default `load` hook after the last user-supplied `load` hook - * `url` {string} - * `context` {Object|undefined} When omitted, defaults are provided. When provided, defaults are - merged in with preference to the provided properties. In the default `nextLoad`, if - the module pointed to by `url` does not have explicit module type information, - `context.format` is mandatory. - -* Returns: {Promise} The asynchronous version takes either an object containing the - following properties, or a `Promise` that will resolve to such an object. - * `format` {string} - * `shortCircuit` {undefined|boolean} A signal that this hook intends to - terminate the chain of `load` hooks. **Default:** `false` - * `source` {string|ArrayBuffer|TypedArray} The source for Node.js to evaluate - -> **Warning**: The asynchronous `load` hook and namespaced exports from CommonJS -> modules are incompatible. Attempting to use them together will result in an empty -> object from the import. This may be addressed in the future. This does not apply -> to the synchronous `load` hook, in which case exports can be used as usual. - -The asynchronous version works similarly to the synchronous version, though -when using the asynchronous `load` hook, omitting vs providing a `source` for -`'commonjs'` has very different effects: - -* When a `source` is provided, all `require` calls from this module will be - processed by the ESM loader with registered `resolve` and `load` hooks; all - `require.resolve` calls from this module will be processed by the ESM loader - with registered `resolve` hooks; only a subset of the CommonJS API will be - available (e.g. no `require.extensions`, no `require.cache`, no - `require.resolve.paths`) and monkey-patching on the CommonJS module loader - will not apply. -* If `source` is undefined or `null`, it will be handled by the CommonJS module - loader and `require`/`require.resolve` calls will not go through the - registered hooks. This behavior for nullish `source` is temporary — in the - future, nullish `source` will not be supported. - -These caveats do not apply to the synchronous `load` hook, in which case -the complete set of CommonJS APIs available to the customized CommonJS -modules, and `require`/`require.resolve` always go through the registered -hooks. - -The Node.js internal asynchronous `load` implementation, which is the value of `next` for the -last hook in the `load` chain, returns `null` for `source` when `format` is -`'commonjs'` for backward compatibility. Here is an example hook that would -opt-in to using the non-default behavior: - -```mjs -import { readFile } from 'node:fs/promises'; - -// Asynchronous version accepted by module.register(). This fix is not needed -// for the synchronous version accepted by module.registerHooks(). -export async function load(url, context, nextLoad) { - const result = await nextLoad(url, context); - if (result.format === 'commonjs') { - result.source ??= await readFile(new URL(result.responseURL ?? url)); - } - return result; -} -``` - -This doesn't apply to the synchronous `load` hook either, in which case the -`source` returned contains source code loaded by the next hook, regardless -of module format. - ### Examples The various module customization hooks can be used together to accomplish wide-ranging customizations of the Node.js code loading and evaluation behaviors. -#### Import from HTTPS - -The hook below registers hooks to enable rudimentary support for such -specifiers. While this may seem like a significant improvement to Node.js core -functionality, there are substantial downsides to actually using these hooks: -performance is much slower than loading files from disk, there is no caching, -and there is no security. - -```mjs -// https-hooks.mjs -import { get } from 'node:https'; - -export function load(url, context, nextLoad) { - // For JavaScript to be loaded over the network, we need to fetch and - // return it. - if (url.startsWith('https://')) { - return new Promise((resolve, reject) => { - get(url, (res) => { - let data = ''; - res.setEncoding('utf8'); - res.on('data', (chunk) => data += chunk); - res.on('end', () => resolve({ - // This example assumes all network-provided JavaScript is ES module - // code. - format: 'module', - shortCircuit: true, - source: data, - })); - }).on('error', (err) => reject(err)); - }); - } - - // Let Node.js handle all other URLs. - return nextLoad(url); -} -``` - -```mjs -// main.mjs -import { VERSION } from 'https://coffeescript.org/browser-compiler-modern/coffeescript.js'; - -console.log(VERSION); -``` - -With the preceding hooks module, running -`node --import 'data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register(pathToFileURL("./https-hooks.mjs"));' ./main.mjs` -prints the current version of CoffeeScript per the module at the URL in -`main.mjs`. - - - #### Transpilation Sources that are in formats Node.js doesn't understand can be converted into @@ -1685,58 +1052,6 @@ JavaScript using the [`load` hook][load hook]. This is less performant than transpiling source files before running Node.js; transpiler hooks should only be used for development and testing purposes. -##### Asynchronous version - -```mjs -// coffeescript-hooks.mjs -import { readFile } from 'node:fs/promises'; -import { findPackageJSON } from 'node:module'; -import coffeescript from 'coffeescript'; - -const extensionsRegex = /\.(coffee|litcoffee|coffee\.md)$/; - -export async function load(url, context, nextLoad) { - if (extensionsRegex.test(url)) { - // CoffeeScript files can be either CommonJS or ES modules. Use a custom format - // to tell Node.js not to detect its module type. - const { source: rawSource } = await nextLoad(url, { ...context, format: 'coffee' }); - // This hook converts CoffeeScript source code into JavaScript source code - // for all imported CoffeeScript files. - const transformedSource = coffeescript.compile(rawSource.toString(), url); - - // To determine how Node.js would interpret the transpilation result, - // search up the file system for the nearest parent package.json file - // and read its "type" field. - return { - format: await getPackageType(url), - shortCircuit: true, - source: transformedSource, - }; - } - - // Let Node.js handle all other URLs. - return nextLoad(url, context); -} - -async function getPackageType(url) { - // `url` is only a file path during the first iteration when passed the - // resolved url from the load() hook - // an actual file path from load() will contain a file extension as it's - // required by the spec - // this simple truthy check for whether `url` contains a file extension will - // work for most projects but does not cover some edge-cases (such as - // extensionless files or a url ending in a trailing space) - const pJson = findPackageJSON(url); - - return readFile(pJson, 'utf8') - .then(JSON.parse) - .then((json) => json?.type) - .catch(() => undefined); -} -``` - -##### Synchronous version - ```mjs // coffeescript-sync-hooks.mjs import { readFileSync } from 'node:fs'; @@ -1807,9 +1122,8 @@ able to return an `format` known to Node.js even in the absence of an explicit t (if undefined) or `ERR_UNKNOWN_MODULE_FORMAT` (if it's not a known format listed in the [load hook][] documentation). -With the preceding hooks modules, running -`node --import 'data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register(pathToFileURL("./coffeescript-hooks.mjs"));' ./main.coffee` -or `node --import ./coffeescript-sync-hooks.mjs ./main.coffee` +With the preceding hooks module, running +`node --import ./coffeescript-sync-hooks.mjs ./main.coffee` causes `main.coffee` to be turned into JavaScript after its source code is loaded from disk but before Node.js executes it; and so on for any `.coffee`, `.litcoffee` or `.coffee.md` files referenced via `import` statements of any @@ -1822,25 +1136,6 @@ The previous two examples defined `load` hooks. This is an example of a which specifiers to override to other URLs (this is a very simplistic implementation of a small subset of the "import maps" specification). -##### Asynchronous version - -```mjs -// import-map-hooks.js -import fs from 'node:fs/promises'; - -const { imports } = JSON.parse(await fs.readFile('import-map.json')); - -export async function resolve(specifier, context, nextResolve) { - if (Object.hasOwn(imports, specifier)) { - return nextResolve(imports[specifier], context); - } - - return nextResolve(specifier, context); -} -``` - -##### Synchronous version - ```mjs // import-map-sync-hooks.js import fs from 'node:fs/promises'; @@ -1882,8 +1177,7 @@ import 'a-module'; console.log('some module!'); ``` -Running `node --import 'data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register(pathToFileURL("./import-map-hooks.js"));' main.js` -or `node --import ./import-map-sync-hooks.js main.js` +Running `node --import ./import-map-sync-hooks.js main.js` should print `some module!`. ## Source Map Support @@ -2104,7 +1398,6 @@ returned object contains the following keys: [Conditional exports]: packages.md#conditional-exports [Customization hooks]: #customization-hooks [ES Modules]: esm.md -[Permission Model]: permissions.md#permission-model [Source Map]: https://tc39.es/ecma426/ [Source map format]: https://tc39.es/ecma426/#sec-source-map-format [V8 JavaScript code coverage]: https://v8project.blogspot.com/2017/12/javascript-code-coverage.html @@ -2120,7 +1413,6 @@ returned object contains the following keys: [`NODE_V8_COVERAGE=dir`]: cli.md#node_v8_coveragedir [`Object.freeze()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze [`SourceMap`]: #class-modulesourcemap -[`initialize`]: #initialize [`module.constants.compileCacheStatus`]: #moduleconstantscompilecachestatus [`module.enableCompileCache()`]: #moduleenablecompilecacheoptions [`module.flushCompileCache()`]: #moduleflushcompilecache @@ -2129,22 +1421,16 @@ returned object contains the following keys: [`module.registerHooks()`]: #moduleregisterhooksoptions [`module.setSourceMapsSupport()`]: #modulesetsourcemapssupportenabled-options [`os.tmpdir()`]: os.md#ostmpdir -[`register`]: #moduleregisterspecifier-parenturl-options [`util.TextDecoder`]: util.md#class-utiltextdecoder [accepted final formats]: #accepted-final-formats-returned-by-load -[asynchronous `load` hook]: #asynchronous-loadurl-context-nextload -[asynchronous `resolve` hook]: #asynchronous-resolvespecifier-context-nextresolve -[asynchronous hook functions]: #asynchronous-hooks-accepted-by-moduleregister -[caveats of asynchronous customization hooks]: #caveats-of-asynchronous-customization-hooks -[deregistration of synchronous customization hooks]: #deregistration-of-synchronous-customization-hooks +[deregistration of customization hooks]: #deregistration-of-customization-hooks +[hook functions]: #hook-functions-accepted-by-moduleregisterhooks [hooks]: #customization-hooks -[load hook]: #synchronous-loadurl-context-nextload +[load hook]: #loadurl-context-nextload [module compile cache]: #module-compile-cache [realm]: https://tc39.es/ecma262/#realm -[resolve hook]: #synchronous-resolvespecifier-context-nextresolve +[resolve hook]: #resolvespecifier-context-nextresolve [source map include directives]: https://tc39.es/ecma426/#sec-linking-generated-code -[synchronous hook functions]: #hook-functions-accepted-by-moduleregisterhooks [the `module` object]: modules.md#the-module-object [the documentation of `Worker`]: worker_threads.md#new-workerfilename-options -[transferable objects]: worker_threads.md#portpostmessagevalue-transferlist [type-stripping]: typescript.md#type-stripping diff --git a/doc/api/test.md b/doc/api/test.md index b80bdb372dc2..fdc709196a75 100644 --- a/doc/api/test.md +++ b/doc/api/test.md @@ -2883,10 +2883,8 @@ Node.js builtin modules. Any references to the original module prior to mocking order to enable module mocking, Node.js must be started with the [`--experimental-test-module-mocks`][] command-line flag. -**Note**: [module customization hooks][] registered via the **synchronous** API effect resolution of -the `specifier` provided to `mock.module`. Customization hooks registered via the **asynchronous** -API are currently ignored (because the test runner's loader is synchronous, and node does not -support multi-chain / cross-chain loading). +**Note**: [module customization hooks][] registered with `module.registerHooks()` affect resolution of +the `specifier` provided to `mock.module`. The following example demonstrates how a mock is created for a module. diff --git a/doc/api/worker_threads.md b/doc/api/worker_threads.md index 08da7b35f152..af572039c8ac 100644 --- a/doc/api/worker_threads.md +++ b/doc/api/worker_threads.md @@ -169,27 +169,16 @@ if (isMainThread) { added: - v23.7.0 - v22.14.0 +changes: + - version: REPLACEME + pr-url: https://github.com/nodejs/node/pull/67090 + description: Always `false`, as Node.js no longer runs code in internal threads. --> * Type: {boolean} -Is `true` if this code is running inside of an internal [`Worker`][] thread (e.g the loader thread). - -```bash -node --experimental-loader ./loader.js main.js -``` - -```mjs -// loader.js -import { isInternalThread } from 'node:worker_threads'; -console.log(isInternalThread); // true -``` - -```cjs -// loader.js -const { isInternalThread } = require('node:worker_threads'); -console.log(isInternalThread); // true -``` +Always `false`. Node.js no longer runs user-visible code in internal [`Worker`][] +threads, so this is retained only for backward compatibility. ```mjs // main.js diff --git a/doc/node-config-schema.json b/doc/node-config-schema.json index 0d2cb83b517d..6787d1d8dbce 100644 --- a/doc/node-config-schema.json +++ b/doc/node-config-schema.json @@ -359,21 +359,6 @@ "type": "boolean", "description": "experimental support for importing source as text with import attributes" }, - "experimental-loader": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "array", - "minItems": 1, - "items": { - "type": "string" - } - } - ], - "description": "use the specified module as a custom loader" - }, "experimental-package-map": { "type": "string", "description": "use the specified file for package map resolution" diff --git a/doc/node.1 b/doc/node.1 index 7a32ece46d21..bc7225a4856e 100644 --- a/doc/node.1 +++ b/doc/node.1 @@ -868,11 +868,6 @@ Enable experimental support for importing modules with .It Fl -experimental-inspector-network-resource Enable experimental support for inspector network resources. . -.It Fl -experimental-loader Ns = Ns Ar module -Specify the \fBmodule\fR containing exported asynchronous module customization hooks. -\fBmodule\fR may be any string accepted as an \fBimport\fR specifier. -This feature requires \fB--allow-worker\fR if used with the Permission Model. -. .It Fl -experimental-network-inspection Enable experimental support for the network inspection with Chrome DevTools. . @@ -2270,8 +2265,6 @@ one is included in the list below. .It \fB--experimental-json-modules\fR .It -\fB--experimental-loader\fR -.It \fB--experimental-modules\fR .It \fB--experimental-package-map\fR diff --git a/lib/internal/bench_runner/cli.js b/lib/internal/bench_runner/cli.js index 5884046e146a..63757edd386b 100644 --- a/lib/internal/bench_runner/cli.js +++ b/lib/internal/bench_runner/cli.js @@ -711,7 +711,6 @@ function runFileOptionRequiresValue(arg) { } const kOptionAliases = new SafeMap([ - ['--loader', '--experimental-loader'], ['-C', '--conditions'], ['-r', '--require'], ]); diff --git a/lib/internal/errors.js b/lib/internal/errors.js index 88c4102eee4f..3c2990111fa3 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -1143,8 +1143,6 @@ E('ERR_AMBIGUOUS_ARGUMENT', 'The "%s" argument is ambiguous. %s', TypeError); E('ERR_ARG_NOT_ITERABLE', '%s must be iterable', TypeError); E('ERR_ASSERTION', '%s', Error); E('ERR_ASYNC_CALLBACK', '%s must be a function', TypeError); -E('ERR_ASYNC_LOADER_REQUEST_NEVER_SETTLED', - 'Async loader request never settled', Error); E('ERR_ASYNC_RESOURCE_DOMAIN_REMOVED', 'The domain property on AsyncResource has been removed. ' + 'Use AsyncLocalStorage instead.', Error); @@ -1603,13 +1601,6 @@ E('ERR_IPC_SYNC_FORK', 'IPC cannot be used with synchronous forks', Error); E('ERR_IP_BLOCKED', function(ip) { return `IP(${ip}) is blocked by net.BlockList`; }, Error); -E( - 'ERR_LOADER_CHAIN_INCOMPLETE', - '"%s" did not call the next hook in its chain and did not' + - ' explicitly signal a short circuit. If this is intentional, include' + - ' `shortCircuit: true` in the hook\'s return.', - Error, -); E('ERR_METHOD_NOT_IMPLEMENTED', 'The %s method is not implemented', Error); E('ERR_MISSING_ARGS', (...args) => { @@ -1787,13 +1778,14 @@ E('ERR_REQUIRE_ESM', 'all ES modules instead).\n'; return msg; }, Error); -E('ERR_REQUIRE_ESM_RACE_CONDITION', (filename, parentFilename, isForAsyncLoaderHookWorker) => { +E('ERR_REQUIRE_ESM_RACE_CONDITION', (filename, parentFilename) => { let raceMessage = `Cannot require() ES Module ${filename} because it is not yet fully loaded.\n`; raceMessage += 'This may be caused by a race condition if the module is simultaneously dynamically '; raceMessage += 'import()-ed via Promise.all().\n'; - raceMessage += 'Try await-ing the import() sequentially in a loop instead.\n'; - raceMessage += ` (From ${parentFilename ? `${parentFilename} in ` : ' '}`; - raceMessage += `${isForAsyncLoaderHookWorker ? 'loader hook worker thread' : 'non-loader-hook thread'})`; + raceMessage += 'Try await-ing the import() sequentially in a loop instead.'; + if (parentFilename) { + raceMessage += `\n (From ${parentFilename})`; + } return raceMessage; }, Error); E('ERR_SCRIPT_EXECUTION_INTERRUPTED', diff --git a/lib/internal/main/eval_string.js b/lib/internal/main/eval_string.js index 998c3fb4ada5..c662b8c2c267 100644 --- a/lib/internal/main/eval_string.js +++ b/lib/internal/main/eval_string.js @@ -30,7 +30,7 @@ markBootstrapComplete(); const code = getOptionValue('--eval'); const print = getOptionValue('--print'); -const shouldLoadESM = getOptionValue('--import').length > 0 || getOptionValue('--experimental-loader').length > 0; +const shouldLoadESM = getOptionValue('--import').length > 0; const inputType = getOptionValue('--input-type'); const tsEnabled = getOptionValue('--strip-types'); if (inputType === 'module') { diff --git a/lib/internal/main/worker_thread.js b/lib/internal/main/worker_thread.js index 1f5fa8f8212b..29bb783a1306 100644 --- a/lib/internal/main/worker_thread.js +++ b/lib/internal/main/worker_thread.js @@ -105,16 +105,14 @@ port.on('message', (message) => { mainThreadPort, } = message; - if (doEval !== 'internal') { - if (argv !== undefined) { - ArrayPrototypePushApply(process.argv, argv); - } - - const publicWorker = require('worker_threads'); - publicWorker.parentPort = publicPort; - publicWorker.workerData = workerData; + if (argv !== undefined) { + ArrayPrototypePushApply(process.argv, argv); } + const publicWorker = require('worker_threads'); + publicWorker.parentPort = publicPort; + publicWorker.workerData = workerData; + require('internal/worker').assignEnvironmentData(environmentData); setupMainThreadPort(mainThreadPort); @@ -140,24 +138,18 @@ port.on('message', (message) => { }; workerIo.sharedCwdCounter = cwdCounter; - const isLoaderHookWorker = (filename === 'internal/modules/esm/worker' && doEval === 'internal'); - if (!isLoaderHookWorker) { - // If we are in the loader hook worker, delay the module loader initializations until - // initializeAsyncLoaderHooksOnLoaderHookWorker() which needs to run preloads - // after the asynchronous loader hooks are registered. - initializeModuleLoaders({ shouldSpawnLoaderHookWorker: true, shouldPreloadModules: true }); - // Re-mount the inherited --vfs-load source so its reserved path (which a - // worker filename may point into) resolves in this thread too. With - // --import, mounting is deferred to after that loop in run_main, matching - // the main thread; finishVfsMounts() is idempotent so it runs once. - if (getOptionValue('--import').length === 0) { - finishVfsMounts(); - } - // Unlike the --vfs-load source, a SEA archive needs no provider that a - // preload could register, so its mount is never deferred. - if (getOptionValue('[sea_vfs_archive]')) { - require('internal/vfs/sea').initSeaVfsInWorker(); - } + initializeModuleLoaders({ shouldPreloadModules: true }); + // Re-mount the inherited --vfs-load source so its reserved path (which a + // worker filename may point into) resolves in this thread too. With + // --import, mounting is deferred to after that loop in run_main, matching + // the main thread; finishVfsMounts() is idempotent so it runs once. + if (getOptionValue('--import').length === 0) { + finishVfsMounts(); + } + // Unlike the --vfs-load source, a SEA archive needs no provider that a + // preload could register, so its mount is never deferred. + if (getOptionValue('[sea_vfs_archive]')) { + require('internal/vfs/sea').initSeaVfsInWorker(); } if (!hasStdin) @@ -167,14 +159,6 @@ port.on('message', (message) => { `(eval = ${doEval}) at cwd = ${process.cwd()}`); port.postMessage({ type: UP_AND_RUNNING }); switch (doEval) { - case 'internal': { - // Currently the only user of internal eval is the async loader hook thread. - assert(isLoaderHookWorker, `Unexpected internal eval ${filename}`); - const setupModuleWorker = require('internal/modules/esm/worker'); - setupModuleWorker(workerData, publicPort); - break; - } - case 'classic': if (webWorkerData?.source !== undefined) { // The source of a web Worker script loaded from a blob: or data: // URL. diff --git a/lib/internal/modules/customization_hooks.js b/lib/internal/modules/customization_hooks.js index c3d1970243fd..aed35529f8c6 100644 --- a/lib/internal/modules/customization_hooks.js +++ b/lib/internal/modules/customization_hooks.js @@ -282,8 +282,8 @@ function validateLoadSloppy(url, context, result) { function validateSourceStrict(url, context, result) { const { source, format } = result; - // To align with module.register(), the load hooks are still invoked for - // the builtins even though the default load step only provides null as source, + // Load hooks are still invoked for builtins even though the default load + // step only provides null as source, // and any source content for builtins provided by the user hooks are ignored. if (!StringPrototypeStartsWith(url, 'node:') && typeof result.source !== 'string' && @@ -302,8 +302,7 @@ function validateSourceStrict(url, context, result) { function validateSourcePermissive(url, context, result) { const { source, format } = result; if (format === 'commonjs' && source == null) { - // Accommodate the quirk in defaultLoad used by asynchronous loader hooks - // which sets source to null for commonjs. + // A hook may return a nullish source for CommonJS, in which case the CJS loader loads it itself. // See: https://github.com/nodejs/node/issues/57327#issuecomment-2701382020 return; } diff --git a/lib/internal/modules/esm/hooks.js b/lib/internal/modules/esm/hooks.js deleted file mode 100644 index 220dcd33754e..000000000000 --- a/lib/internal/modules/esm/hooks.js +++ /dev/null @@ -1,870 +0,0 @@ -'use strict'; - -const { - ArrayPrototypePush, - ArrayPrototypePushApply, - AtomicsLoad, - AtomicsWait, - AtomicsWaitAsync, - Int32Array, - ObjectAssign, - ObjectDefineProperty, - ObjectSetPrototypeOf, - Promise, - ReflectSet, - SafeSet, - StringPrototypeSlice, - StringPrototypeToUpperCase, -} = primordials; - -const { - ERR_ASYNC_LOADER_REQUEST_NEVER_SETTLED, - ERR_INTERNAL_ASSERTION, - ERR_INVALID_ARG_TYPE, - ERR_INVALID_ARG_VALUE, - ERR_INVALID_RETURN_PROPERTY_VALUE, - ERR_INVALID_RETURN_VALUE, - ERR_LOADER_CHAIN_INCOMPLETE, - ERR_WORKER_UNSERIALIZABLE_ERROR, -} = require('internal/errors').codes; -const { URLParse } = require('internal/url'); -const { canParse: URLCanParse } = internalBinding('url'); -const { receiveMessageOnPort } = require('worker_threads'); -const { - isAnyArrayBuffer, - isArrayBufferView, -} = require('internal/util/types'); -const { - validateObject, - validateString, -} = require('internal/validators'); -const { - constructSharedArrayBuffer, - kEmptyObject, -} = require('internal/util'); - -const { - defaultResolve, - throwIfInvalidParentURL, -} = require('internal/modules/esm/resolve'); -const { - getDefaultConditions, -} = require('internal/modules/esm/utils'); -const { hasWorkerCondition } = require('internal/modules/helpers'); -const { deserializeError } = require('internal/error_serdes'); -const { - SHARED_MEMORY_BYTE_LENGTH, - WORKER_TO_MAIN_THREAD_NOTIFICATION, -} = require('internal/modules/esm/shared_constants'); -let debug = require('internal/util/debuglog').debuglog('async_loader_worker', (fn) => { - debug = fn; -}); - -let importAssertionAlreadyWarned = false; - -function emitImportAssertionWarning() { - if (!importAssertionAlreadyWarned) { - importAssertionAlreadyWarned = true; - process.emitWarning('Use `importAttributes` instead of `importAssertions`', 'ExperimentalWarning'); - } -} - -function defineImportAssertionAlias(context) { - return ObjectDefineProperty(context, 'importAssertions', { - __proto__: null, - configurable: true, - get() { - emitImportAssertionWarning(); - return this.importAttributes; - }, - set(value) { - emitImportAssertionWarning(); - return ReflectSet(this, 'importAttributes', value); - }, - }); -} - -/** - * @typedef {object} ExportedHooks - * @property {Function} resolve Resolve hook. - * @property {Function} load Load hook. - */ - -/** - * @typedef {object} KeyedHook - * @property {Function} fn The hook function. - * @property {URL['href']} url The URL of the module. - * @property {KeyedHook?} next The next hook in the chain. - */ - -// [2] `validate...()`s throw the wrong error - -/** - * @typedef {{ format: ModuleFormat, source: ModuleSource }} LoadResult - */ - -/** - * @typedef {{ format: ModuleFormat, url: string, importAttributes: Record }} ResolveResult - */ - -/** - * Interface for classes that implement asynchronous loader hooks that can be attached to the ModuleLoader - * via `ModuleLoader.#setAsyncLoaderHooks()`. - * @typedef {object} AsyncLoaderHooks - * @property {boolean} isForAsyncLoaderHookWorker Whether the instance is running on the loader hook worker thread. - * @property {(url: string, context: object, defaultLoad: Function) => Promise} load - * Calling the asynchronous `load` hook asynchronously. - * @property {(url: string, context: object, defaultLoad: Function) => LoadResult} [loadSync] - * Calling the asynchronous `load` hook synchronously. - * @property {(originalSpecifier: string, parentURL: string, - * importAttributes: Record) => Promise} resolve - * Calling the asynchronous `resolve` hook asynchronously. - * @property {(originalSpecifier: string, parentURL: string, - * importAttributes: Record) => ResolveResult} [resolveSync] - * Calling the asynchronous `resolve` hook synchronously. - * @property {(specifier: string, parentURL: string) => any} register Register asynchronous loader hooks - * @property {() => void} waitForLoaderHookInitialization Force loading of hooks. - */ - -/** - * @implements {AsyncLoaderHooks} - * Instances of this class run directly on the loader hook worker thread and customize the module - * loading of the hooks worker itself. - */ -class AsyncLoaderHooksOnLoaderHookWorker { - #chains = { - /** - * Phase 1 of 2 in ESM loading. - * The output of the `resolve` chain of hooks is passed into the `load` chain of hooks. - * @private - * @property {KeyedHook[]} resolve Last-in-first-out collection of resolve hooks. - */ - resolve: [ - { - fn: defaultResolve, - url: 'node:internal/modules/esm/resolve', - }, - ], - - /** - * Phase 2 of 2 in ESM loading. - * @private - * @property {KeyedHook[]} load Last-in-first-out collection of loader hooks. - */ - load: [ - { - fn: require('internal/modules/esm/load').defaultLoad, - url: 'node:internal/modules/esm/load', - }, - ], - }; - - // Cache URLs we've already validated to avoid repeated validation - #validatedUrls = new SafeSet(); - - isForAsyncLoaderHookWorker = true; - - /** - * Import and register custom/user-defined module loader hook(s). - * @param {string} urlOrSpecifier - * @param {string} parentURL - * @param {any} [data] Arbitrary data to be passed from the custom - * loader (user-land) to the worker. - */ - async register(urlOrSpecifier, parentURL, data, isInternal) { - const cascadedLoader = require('internal/modules/esm/loader').getOrInitializeCascadedLoader(); - const keyedExports = isInternal ? - require(urlOrSpecifier) : - await cascadedLoader.import( - urlOrSpecifier, - parentURL, - kEmptyObject, - ); - await this.addCustomLoader(urlOrSpecifier, keyedExports, data); - } - - /** - * Collect custom/user-defined module loader hook(s). - * @param {string} url Custom loader specifier - * @param {Record} exports - * @param {any} [data] Arbitrary data to be passed from the custom loader (user-land) - * to the worker. - * @returns {any | Promise} User data, ignored unless it's a promise, in which case it will be awaited. - */ - addCustomLoader(url, exports, data) { - const { - initialize, - resolve, - load, - } = pluckHooks(exports); - - if (resolve) { - const next = this.#chains.resolve[this.#chains.resolve.length - 1]; - ArrayPrototypePush(this.#chains.resolve, { __proto__: null, fn: resolve, url, next }); - } - if (load) { - const next = this.#chains.load[this.#chains.load.length - 1]; - ArrayPrototypePush(this.#chains.load, { __proto__: null, fn: load, url, next }); - } - return initialize?.(data); - } - - /** - * Resolve the location of the module. - * - * Internally, this behaves like a backwards iterator, wherein the stack of - * hooks starts at the top and each call to `nextResolve()` moves down 1 step - * until it reaches the bottom or short-circuits. - * @param {string} originalSpecifier The specified URL path of the module to - * be resolved. - * @param {string} [parentURL] The URL path of the module's parent. - * @param {ImportAttributes} [importAttributes] Attributes from the import - * statement or expression. - * @returns {Promise<{ format: string, url: URL['href'] }>} - */ - async resolve( - originalSpecifier, - parentURL, - importAttributes = { __proto__: null }, - ) { - throwIfInvalidParentURL(parentURL); - - const chain = this.#chains.resolve; - const context = { - conditions: getDefaultConditions(), - importAttributes, - parentURL, - }; - const meta = { - chainFinished: null, - context, - hookErrIdentifier: '', - hookName: 'resolve', - shortCircuited: false, - }; - - const validateArgs = (hookErrIdentifier, suppliedSpecifier, ctx) => { - validateString( - suppliedSpecifier, - `${hookErrIdentifier} specifier`, - ); // non-strings can be coerced to a URL string - - if (ctx) { validateObject(ctx, `${hookErrIdentifier} context`); } - }; - const validateOutput = (hookErrIdentifier, output) => { - if (typeof output !== 'object' || output === null) { // [2] - throw new ERR_INVALID_RETURN_VALUE( - 'an object', - hookErrIdentifier, - output, - ); - } - }; - - const nextResolve = nextHookFactory(chain[chain.length - 1], meta, { validateArgs, validateOutput }); - - const resolution = await nextResolve(originalSpecifier, defineImportAssertionAlias(context)); - const { hookErrIdentifier } = meta; // Retrieve the value after all settled - - validateOutput(hookErrIdentifier, resolution); - - if (resolution?.shortCircuit === true) { meta.shortCircuited = true; } - - if (!meta.chainFinished && !meta.shortCircuited) { - throw new ERR_LOADER_CHAIN_INCOMPLETE(hookErrIdentifier); - } - - let resolvedImportAttributes; - const { - format, - url, - } = resolution; - - if (typeof url !== 'string') { - // non-strings can be coerced to a URL string - // validateString() throws a less-specific error - throw new ERR_INVALID_RETURN_PROPERTY_VALUE( - 'a URL string', - hookErrIdentifier, - 'url', - url, - ); - } - - // Avoid expensive URL instantiation for known-good URLs - if (!this.#validatedUrls.has(url)) { - // No need to convert to string, since the type is already validated - if (!URLCanParse(url)) { - throw new ERR_INVALID_RETURN_PROPERTY_VALUE( - 'a URL string', - hookErrIdentifier, - 'url', - url, - ); - } - - this.#validatedUrls.add(url); - } - - if (!('importAttributes' in resolution) && ('importAssertions' in resolution)) { - emitImportAssertionWarning(); - resolvedImportAttributes = resolution.importAssertions; - } else { - resolvedImportAttributes = resolution.importAttributes; - } - - if ( - resolvedImportAttributes != null && - typeof resolvedImportAttributes !== 'object' - ) { - throw new ERR_INVALID_RETURN_PROPERTY_VALUE( - 'an object', - hookErrIdentifier, - 'importAttributes', - resolvedImportAttributes, - ); - } - - if ( - format != null && - typeof format !== 'string' // [2] - ) { - throw new ERR_INVALID_RETURN_PROPERTY_VALUE( - 'a string', - hookErrIdentifier, - 'format', - format, - ); - } - - return { - __proto__: null, - format, - importAttributes: resolvedImportAttributes, - url, - }; - } - - /** - * Provide source that is understood by one of Node's translators. - * - * Internally, this behaves like a backwards iterator, wherein the stack of - * hooks starts at the top and each call to `nextLoad()` moves down 1 step - * until it reaches the bottom or short-circuits. - * @param {URL['href']} url The URL/path of the module to be loaded - * @param {object} context Metadata about the module - * @returns {Promise<{ format: ModuleFormat, source: ModuleSource }>} - */ - async load(url, context = kEmptyObject) { - const chain = this.#chains.load; - const meta = { - chainFinished: null, - context, - hookErrIdentifier: '', - hookName: 'load', - shortCircuited: false, - }; - - const validateArgs = (hookErrIdentifier, nextUrl, ctx) => { - if (typeof nextUrl !== 'string') { - // Non-strings can be coerced to a URL string - // validateString() throws a less-specific error - throw new ERR_INVALID_ARG_TYPE( - `${hookErrIdentifier} url`, - 'a URL string', - nextUrl, - ); - } - - // Avoid expensive URL instantiation for known-good URLs - if (!this.#validatedUrls.has(nextUrl)) { - // No need to convert to string, since the type is already validated - if (!URLCanParse(nextUrl)) { - throw new ERR_INVALID_ARG_VALUE( - `${hookErrIdentifier} url`, - nextUrl, - 'should be a URL string', - ); - } - - this.#validatedUrls.add(nextUrl); - } - - if (ctx) { validateObject(ctx, `${hookErrIdentifier} context`); } - }; - const validateOutput = (hookErrIdentifier, output) => { - if (typeof output !== 'object' || output === null) { // [2] - throw new ERR_INVALID_RETURN_VALUE( - 'an object', - hookErrIdentifier, - output, - ); - } - }; - - const nextLoad = nextHookFactory(chain[chain.length - 1], meta, { validateArgs, validateOutput }); - - const loaded = await nextLoad(url, defineImportAssertionAlias(context)); - const { hookErrIdentifier } = meta; // Retrieve the value after all settled - - validateOutput(hookErrIdentifier, loaded); - - if (loaded?.shortCircuit === true) { meta.shortCircuited = true; } - - if (!meta.chainFinished && !meta.shortCircuited) { - throw new ERR_LOADER_CHAIN_INCOMPLETE(hookErrIdentifier); - } - - const { - format, - source, - } = loaded; - let responseURL = loaded.responseURL; - - if (responseURL === undefined) { - responseURL = url; - } - - let responseURLObj; - if (typeof responseURL === 'string') { - responseURLObj = URLParse(responseURL); - } - - if (responseURLObj?.href !== responseURL) { - throw new ERR_INVALID_RETURN_PROPERTY_VALUE( - 'undefined or a fully resolved URL string', - hookErrIdentifier, - 'responseURL', - responseURL, - ); - } - - if (format == null) { - require('internal/modules/esm/load').throwUnknownModuleFormat(url, format); - } - - if (typeof format !== 'string') { // [2] - throw new ERR_INVALID_RETURN_PROPERTY_VALUE( - 'a string', - hookErrIdentifier, - 'format', - format, - ); - } - - if ( - source != null && - typeof source !== 'string' && - !isAnyArrayBuffer(source) && - !isArrayBufferView(source) - ) { - throw new ERR_INVALID_RETURN_PROPERTY_VALUE( - 'a string, an ArrayBuffer, or a TypedArray', - hookErrIdentifier, - 'source', - source, - ); - } - - return { - __proto__: null, - format, - responseURL, - source, - }; - } - - waitForLoaderHookInitialization() { - // No-op - } -} -ObjectSetPrototypeOf(AsyncLoaderHooksOnLoaderHookWorker.prototype, null); - -/** - * There is only one loader hook thread for each non-loader-hook worker thread - * (i.e. the non-loader-hook thread and any worker threads that are not loader hook workers themselves), - * so there is only 1 MessageChannel. - */ -let MessageChannel; - -/** - * Abstraction over a worker thread that runs the asynchronous module loader hooks. - * Instances of this class run on the non-loader-hook thread and communicate with the loader hooks worker thread. - */ -class AsyncLoaderHookWorker { - /** - * Shared memory. Always use Atomics method to read or write to it. - * @type {Int32Array} - */ - #lock; - /** - * The InternalWorker instance, which lets us communicate with the loader thread. - */ - #worker; - - /** - * The last notification ID received from the worker. This is used to detect - * if the worker has already sent a notification before putting the main - * thread to sleep, to avoid a race condition. - * @type {number} - */ - #workerNotificationLastId = 0; - - /** - * Track how many async responses the main thread should expect. - * @type {number} - */ - #numberOfPendingAsyncResponses = 0; - - #isReady = false; - - constructor() { - const { InternalWorker } = require('internal/worker'); - MessageChannel ??= require('internal/worker/io').MessageChannel; - - const lock = constructSharedArrayBuffer(SHARED_MEMORY_BYTE_LENGTH); - this.#lock = new Int32Array(lock); - - this.#worker = new InternalWorker('internal/modules/esm/worker', { - stderr: false, - stdin: false, - stdout: false, - trackUnmanagedFds: false, - workerData: { - lock, - workerCondition: hasWorkerCondition(), - }, - }); - this.#worker.unref(); // ! Allows the process to eventually exit. - this.#worker.on('exit', process.exit); - } - - waitForWorker() { - if (!this.#isReady) { - const { kIsOnline } = require('internal/worker'); - if (!this.#worker[kIsOnline]) { - debug('wait for signal from worker'); - AtomicsWait(this.#lock, WORKER_TO_MAIN_THREAD_NOTIFICATION, 0); - const response = this.#worker.receiveMessageSync(); - if (response == null) { return; } - if (response.message.status === 'exit') { - process.exit(response.message.body); - } - - // ! This line catches initialization errors in the worker thread. - this.#unwrapMessage(response); - } - - this.#isReady = true; - } - } - - /** - * Invoke a remote method asynchronously. - * @param {string} method Method to invoke - * @param {any[]} [transferList] Objects in `args` to be transferred - * @param {any[]} args Arguments to pass to `method` - * @returns {Promise} - */ - async makeAsyncRequest(method, transferList, ...args) { - this.waitForWorker(); - - MessageChannel ??= require('internal/worker/io').MessageChannel; - const asyncCommChannel = new MessageChannel(); - - // Pass work to the worker. - debug('post async message to worker', { method, args, transferList }); - const finalTransferList = [asyncCommChannel.port2]; - if (transferList) { - ArrayPrototypePushApply(finalTransferList, transferList); - } - this.#worker.postMessage({ - __proto__: null, - method, args, - port: asyncCommChannel.port2, - }, finalTransferList); - - if (this.#numberOfPendingAsyncResponses++ === 0) { - // On the next lines, the main thread will await a response from the worker thread that might - // come AFTER the last task in the event loop has run its course and there would be nothing - // left keeping the thread alive (and once the main thread dies, the whole process stops). - // However we want to keep the process alive until the worker thread responds (or until the - // event loop of the worker thread is also empty), so we ref the worker until we get all the - // responses back. - this.#worker.ref(); - } - - let response; - do { - debug('wait for async response from worker', { method, args }); - await AtomicsWaitAsync(this.#lock, WORKER_TO_MAIN_THREAD_NOTIFICATION, this.#workerNotificationLastId).value; - this.#workerNotificationLastId = AtomicsLoad(this.#lock, WORKER_TO_MAIN_THREAD_NOTIFICATION); - - response = receiveMessageOnPort(asyncCommChannel.port1); - } while (response == null); - debug('got async response from worker', { method, args }, this.#lock); - - if (--this.#numberOfPendingAsyncResponses === 0) { - // We got all the responses from the worker, its job is done (until next time). - this.#worker.unref(); - } - - const body = this.#unwrapMessage(response); - asyncCommChannel.port1.close(); - return body; - } - - /** - * Invoke a remote method synchronously. - * @param {string} method Method to invoke - * @param {any[]} [transferList] Objects in `args` to be transferred - * @param {any[]} args Arguments to pass to `method` - * @returns {any} - */ - makeSyncRequest(method, transferList, ...args) { - this.waitForWorker(); - - // Pass work to the worker. - debug('post sync message to worker', { method, args, transferList }); - this.#worker.postMessage({ __proto__: null, method, args }, transferList); - - let response; - do { - debug('wait for sync response from worker', { method, args }); - // Sleep until worker responds. - AtomicsWait(this.#lock, WORKER_TO_MAIN_THREAD_NOTIFICATION, this.#workerNotificationLastId); - this.#workerNotificationLastId = AtomicsLoad(this.#lock, WORKER_TO_MAIN_THREAD_NOTIFICATION); - - response = this.#worker.receiveMessageSync(); - debug('got sync message from worker', { method, args, response }); - } while (response == null); - - if (response.message.status === 'never-settle') { - const error = new ERR_ASYNC_LOADER_REQUEST_NEVER_SETTLED(); - error.details = { method, args }; - throw error; - } else if (response.message.status === 'exit') { - process.exit(response.message.body); - } - return this.#unwrapMessage(response); - } - - #unwrapMessage(response) { - if (response.message.status === 'never-settle') { - return new Promise(() => {}); - } - const { status, body } = response.message; - if (status === 'error') { - if (body == null || typeof body !== 'object') { throw body; } - if (body.serializationFailed || body.serialized == null) { - throw new ERR_WORKER_UNSERIALIZABLE_ERROR(); - } - - // eslint-disable-next-line no-restricted-syntax - throw deserializeError(body.serialized); - } else { - return body; - } - } -} -ObjectSetPrototypeOf(AsyncLoaderHookWorker.prototype, null); - -// TODO(JakobJingleheimer): Remove this when loaders go "stable". -let globalPreloadWarningWasEmitted = false; - -/** - * A utility function to pluck the hooks from a user-defined loader. - * @param {import('./loader.js').ModuleExports} exports - * @returns {ExportedHooks} - */ -function pluckHooks({ - globalPreload, - initialize, - resolve, - load, -}) { - const acceptedHooks = { __proto__: null }; - - if (resolve) { - acceptedHooks.resolve = resolve; - } - if (load) { - acceptedHooks.load = load; - } - - if (initialize) { - acceptedHooks.initialize = initialize; - } else if (globalPreload && !globalPreloadWarningWasEmitted) { - process.emitWarning( - '`globalPreload` has been removed; use `initialize` instead.', - 'UnsupportedWarning', - ); - globalPreloadWarningWasEmitted = true; - } - - return acceptedHooks; -} - - -/** - * A utility function to iterate through a hook chain, track advancement in the - * chain, and generate and supply the `next` argument to the custom - * hook. - * @param {KeyedHook} current The (currently) first hook in the chain (this shifts - * on every call). - * @param {object} meta Properties that change as the current hook advances - * along the chain. - * @param {boolean} meta.chainFinished Whether the end of the chain has been - * reached AND invoked. - * @param {string} meta.hookErrIdentifier A user-facing identifier to help - * pinpoint where an error occurred. Ex "file:///foo.mjs 'resolve'". - * @param {string} meta.hookName The kind of hook the chain is (ex 'resolve') - * @param {boolean} meta.shortCircuited Whether a hook signaled a short-circuit. - * @param {function(string, unknown): void} validate A wrapper function - * containing all validation of a custom loader hook's intermediary output. Any - * validation within MUST throw. - * @returns {Function} The next hook in the chain. - */ -function nextHookFactory(current, meta, { validateArgs, validateOutput }) { - // First, prepare the current - const { hookName } = meta; - const { - fn: hook, - url: hookFilePath, - next, - } = current; - - // ex 'nextResolve' - const nextHookName = `next${ - StringPrototypeToUpperCase(hookName[0]) + - StringPrototypeSlice(hookName, 1) - }`; - - let nextNextHook; - if (next) { - nextNextHook = nextHookFactory(next, meta, { validateArgs, validateOutput }); - } else { - // eslint-disable-next-line node-core/func-name-matching - nextNextHook = function chainAdvancedTooFar() { - throw new ERR_INTERNAL_ASSERTION( - `ESM custom loader '${hookName}' advanced beyond the end of the chain.`, - ); - }; - } - - return ObjectDefineProperty( - async (arg0 = undefined, context) => { - // Update only when hook is invoked to avoid fingering the wrong filePath - meta.hookErrIdentifier = `${hookFilePath} '${hookName}'`; - - validateArgs(`${meta.hookErrIdentifier} hook's ${nextHookName}()`, arg0, context); - - const outputErrIdentifier = `${hookFilePath} '${hookName}' hook's ${nextHookName}()`; - - // Set when next is actually called, not just generated. - if (!next) { meta.chainFinished = true; } - - if (context) { // `context` has already been validated, so no fancy check needed. - ObjectAssign(meta.context, context); - } - - const output = await hook(arg0, meta.context, nextNextHook); - validateOutput(outputErrIdentifier, output); - - if (output?.shortCircuit === true) { meta.shortCircuited = true; } - - return output; - }, - 'name', - { __proto__: null, value: nextHookName }, - ); -} - -/** - * @type {AsyncLoaderHookWorker} - * Worker instance used to run async loader hooks in a separate thread. This is a singleton for each - * non-loader-hook worker thread (i.e. the main thread and any worker threads that are not - * loader hook workers themselves). - */ -let asyncLoaderHookWorker; -/** - * Get the AsyncLoaderHookWorker instance. If it is not defined, then create a new one. - * @returns {AsyncLoaderHookWorker} - */ -function getAsyncLoaderHookWorker() { - asyncLoaderHookWorker ??= new AsyncLoaderHookWorker(); - return asyncLoaderHookWorker; -} - -/** - * @implements {AsyncLoaderHooks} - * Instances of this class are created in the non-loader-hook thread and communicate with the worker thread - * spawned to run the async loader hooks. - */ -class AsyncLoaderHooksProxiedToLoaderHookWorker { - - isForAsyncLoaderHookWorker = false; - - /** - * Instantiate a module loader that uses user-provided custom loader hooks. - */ - constructor() { - getAsyncLoaderHookWorker(); - } - - /** - * Register some loader specifier. - * @param {string} originalSpecifier The specified URL path of the loader to - * be registered. - * @param {string} parentURL The parent URL from where the loader will be - * registered if using it package name as specifier - * @param {any} [data] Arbitrary data to be passed from the custom loader - * (user-land) to the worker. - * @param {any[]} [transferList] Objects in `data` that are changing ownership - * @param {boolean} [isInternal] For internal loaders that should not be publicly exposed. - * @returns {{ format: string, url: URL['href'] }} - */ - register(originalSpecifier, parentURL, data, transferList, isInternal) { - return asyncLoaderHookWorker.makeSyncRequest('register', transferList, originalSpecifier, parentURL, - data, isInternal); - } - - /** - * Resolve the location of the module. - * @param {string} originalSpecifier The specified URL path of the module to - * be resolved. - * @param {string} [parentURL] The URL path of the module's parent. - * @param {ImportAttributes} importAttributes Attributes from the import - * statement or expression. - * @returns {{ format: string, url: URL['href'] }} - */ - resolve(originalSpecifier, parentURL, importAttributes) { - return asyncLoaderHookWorker.makeAsyncRequest('resolve', undefined, originalSpecifier, parentURL, importAttributes); - } - - resolveSync(originalSpecifier, parentURL, importAttributes) { - // This happens only as a result of `import.meta.resolve` calls, which must be sync per spec. - return asyncLoaderHookWorker.makeSyncRequest('resolve', undefined, originalSpecifier, parentURL, importAttributes); - } - - /** - * Provide source that is understood by one of Node's translators. - * @param {URL['href']} url The URL/path of the module to be loaded - * @param {object} [context] Metadata about the module - * @returns {Promise<{ format: ModuleFormat, source: ModuleSource }>} - */ - load(url, context) { - return asyncLoaderHookWorker.makeAsyncRequest('load', undefined, url, context); - } - loadSync(url, context) { - return asyncLoaderHookWorker.makeSyncRequest('load', undefined, url, context); - } - - waitForLoaderHookInitialization() { - asyncLoaderHookWorker.waitForWorker(); - } -} - -exports.AsyncLoaderHooksProxiedToLoaderHookWorker = AsyncLoaderHooksProxiedToLoaderHookWorker; -exports.AsyncLoaderHooksOnLoaderHookWorker = AsyncLoaderHooksOnLoaderHookWorker; -exports.AsyncLoaderHookWorker = AsyncLoaderHookWorker; diff --git a/lib/internal/modules/esm/load.js b/lib/internal/modules/esm/load.js index 06f60f9f6725..66e7eb6d5ecf 100644 --- a/lib/internal/modules/esm/load.js +++ b/lib/internal/modules/esm/load.js @@ -8,7 +8,7 @@ const { } = require('internal/util'); const { defaultGetFormat } = require('internal/modules/esm/get_format'); -const { validateAttributes, emitImportAssertionWarning } = require('internal/modules/esm/assert'); +const { validateAttributes } = require('internal/modules/esm/assert'); const { loaderMethods } = require('internal/modules/helpers'); const { Buffer: { from: BufferFrom } } = require('buffer'); @@ -51,67 +51,6 @@ function getSourceSync(url, context) { } -/** - * Node.js default load hook. - * @param {string} url - * @param {LoadContext} context - * @returns {LoadReturn} - */ -function defaultLoad(url, context = kEmptyObject) { - let responseURL = url; - let { - importAttributes, - format, - source, - } = context; - - if (importAttributes == null && !('importAttributes' in context) && 'importAssertions' in context) { - emitImportAssertionWarning(); - importAttributes = context.importAssertions; - // Alias `importAssertions` to `importAttributes` - context = { - ...context, - importAttributes, - }; - } - - const urlInstance = new URL(url); - - throwIfUnsupportedURLScheme(urlInstance); - - if (urlInstance.protocol === 'node:') { - source = null; - format ??= 'builtin'; - } else if (format === 'addon') { - // Skip loading addon file content. It must be loaded with dlopen from file system. - source = null; - } else if (format !== 'commonjs') { - if (source == null) { - ({ responseURL, source } = getSourceSync(urlInstance, context)); - context = { __proto__: context, source }; - } - - if (format == null) { - // Now that we have the source for the module, run `defaultGetFormat` to detect its format. - format = defaultGetFormat(urlInstance, context); - - if (format === 'commonjs') { - // For backward compatibility reasons, we need to discard the source in - // order for the CJS loader to re-fetch it. - source = null; - } - } - } - - validateAttributes(url, format, importAttributes); - - return { - __proto__: null, - format, - responseURL, - source, - }; -} /** * @typedef LoadContext * @property {string} [format] A hint (possibly returned from `resolve`) @@ -212,7 +151,6 @@ function throwUnknownModuleFormat(url, format) { module.exports = { - defaultLoad, defaultLoadSync, getSourceSync, throwUnknownModuleFormat, diff --git a/lib/internal/modules/esm/loader.js b/lib/internal/modules/esm/loader.js index 2e0b0fef8e9d..f8e68e7996cb 100644 --- a/lib/internal/modules/esm/loader.js +++ b/lib/internal/modules/esm/loader.js @@ -1,18 +1,9 @@ 'use strict'; const { - ArrayPrototypeJoin, - ArrayPrototypeMap, - ArrayPrototypeReduce, FunctionPrototypeCall, - JSONStringify, ObjectSetPrototypeOf, - Promise, - PromisePrototypeThen, - RegExpPrototypeSymbolReplace, - encodeURIComponent, globalThis, - hardenRegExp, } = primordials; const { LoadCache, ResolveCache } = require('internal/modules/esm/module_map'); @@ -37,17 +28,15 @@ const { ERR_UNKNOWN_MODULE_FORMAT, } = require('internal/errors').codes; const { getOptionValue } = require('internal/options'); -const { isURL, pathToFileURL } = require('internal/url'); +const { pathToFileURL } = require('internal/url'); const { - getDeprecationWarningEmitter, kEmptyObject, } = require('internal/util'); const { compileSourceTextModule, SourceTextModuleTypes: { kUser }, getDefaultConditions, - shouldSpawnLoaderHookWorker, - requestTypes: { kImportInRequiredESM, kRequireInImportedCJS, kImportInImportedESM }, + requestTypes: { kImportInRequiredESM, kImportInImportedESM }, } = require('internal/modules/esm/utils'); /** * @typedef {import('./utils.js').ModuleRequestType} ModuleRequestType @@ -62,7 +51,6 @@ const { kUninstantiated, kErrored, kSourcePhase, - throwIfPromiseRejected, setImportMetaResolveInitializer, } = internalBinding('module_wrap'); const { @@ -83,10 +71,7 @@ let debug = require('internal/util/debuglog').debuglog('esm', (fn) => { debug = fn; }); -const { isPromise } = require('internal/util/types'); - /** - * @typedef {import('./hooks.js').AsyncLoaderHookWorker} AsyncLoaderHookWorker * @typedef {import('./module_job.js').ModuleJobBase} ModuleJobBase * @typedef {import('url').URL} URL */ @@ -123,22 +108,12 @@ const { defaultLoadSync, throwUnknownModuleFormat } = require('internal/modules/ * responseURL?: string, * translatorKey: string, * isResolvedBySyncHooks: boolean, - * isSourceLoadedSynchronously: boolean, * }} TranslateContext */ /** - * @typedef {import('./hooks.js').AsyncLoaderHooks} AsyncLoaderHooks - * @typedef {import('./hooks.js').AsyncLoaderHooksOnLoaderHookWorker} AsyncLoaderHooksOnLoaderHookWorker - * @typedef {import('./hooks.js').AsyncLoaderHooksProxiedToLoaderHookWorker} AsyncLoaderHooksProxiedToLoaderHookWorker - */ - -/** - * This class covers the base machinery of module loading. There are two types of loader hooks: - * 1. Asynchronous loader hooks, which are run in a separate loader hook worker thread. - * This is configured in #asyncLoaderHooks. - * 2. Synchronous loader hooks, which are run in-thread. This is shared with the CJS loader and is - * stored in the cross-module syncResolveHooks and syncLoadHooks arrays. + * This class covers the base machinery of module loading. The `module.registerHooks()` hooks are + * shared with the CJS loader and stored in the cross-module syncResolveHooks and syncLoadHooks arrays. */ class ModuleLoader { /** @@ -156,57 +131,6 @@ class ModuleLoader { */ loadCache = new LoadCache(); - /** - * @see {AsyncLoaderHooks.isForAsyncLoaderHookWorker} - * Shortcut to this.#asyncLoaderHooks.isForAsyncLoaderHookWorker. - */ - isForAsyncLoaderHookWorker = false; - - /** - * Asynchronous loader hooks to pass requests to. - * - * Note that this value _MUST_ be set with `#setAsyncLoaderHooks` - * because it needs to copy `#asyncLoaderHooks.isForAsyncLoaderHookWorker` - * to this property. - * TODO(joyeecheung): this was a legacy of the previous setup of import.meta.resolve - * configuration; put this information in the environment directly instead. - * - * When the ModuleLoader is created on a loader hook thread, this is - * {@link AsyncLoaderHooksOnLoaderHookWorker}, and its methods directly call out - * to loader methods. Otherwise, this is {@link AsyncLoaderHooksProxiedToLoaderHookWorker}, - * and its methods post messages to the loader thread and possibly block on it. - * @see {ModuleLoader.#setAsyncLoaderHooks} - * @type {AsyncLoaderHooks} - */ - #asyncLoaderHooks; - - constructor(asyncLoaderHooks) { - this.#setAsyncLoaderHooks(asyncLoaderHooks); - } - - /** - * Change the currently activate async loader hooks for this module - * loader to be the provided `AsyncLoaderHooks`. - * - * If present, this class customizes its core functionality to the - * `AsyncLoaderHooks` object, including registration, loading, and resolving. - * There are some responsibilities that this class _always_ takes - * care of, like validating outputs, so that the AsyncLoaderHooks object - * does not have to do so. - * - * Calling this function alters how modules are loaded and should be - * invoked with care. - * @param {AsyncLoaderHooks} asyncLoaderHooks - */ - #setAsyncLoaderHooks(asyncLoaderHooks) { - this.#asyncLoaderHooks = asyncLoaderHooks; - if (asyncLoaderHooks) { - this.isForAsyncLoaderHookWorker = asyncLoaderHooks.isForAsyncLoaderHookWorker; - } else { - this.isForAsyncLoaderHookWorker = false; - } - } - /** * * @param {string} source Source code of the module. @@ -290,10 +214,6 @@ class ModuleLoader { if (job !== undefined) { mod[kRequiredModuleSymbol] = job.module; const parentFilename = urlToFilename(parent?.filename); - // This race should only be possible on the loader hook thread. See https://github.com/nodejs/node/issues/59666 - if (!job.module) { - throw new ERR_REQUIRE_ESM_RACE_CONDITION(filename, parentFilename, this.isForAsyncLoaderHookWorker); - } const status = job.module.getStatus(); debug('Module status', job, status); // hasAsyncGraph is available after module been instantiated. @@ -303,13 +223,10 @@ class ModuleLoader { if (status === kEvaluated) { return { wrap: job.module, namespace: job.module.getNamespace() }; } else if (status === kInstantiated) { - // When it's an async job cached by another import request, - // which has finished linking but has not started its - // evaluation because the async run() task would be later - // in line. Then start the evaluation now with runSync(), which - // is guaranteed to finish by the time the other run() get to it, - // and the other task would just get the cached evaluation results, - // similar to what would happen when both are async. + // The job was created by an earlier import() that has finished linking but has + // not started its evaluation, which is deferred to a later microtask. Evaluate + // it now with runSync(); the pending run() will then just find it already + // evaluated, similar to what would happen when both are async. mod[kRequiredModuleSymbol] = job.module; const { namespace } = job.runSync(parent); return { wrap: job.module, namespace: namespace || job.module.getNamespace() }; @@ -317,21 +234,14 @@ class ModuleLoader { // If the module was previously imported and errored, throw the error. throw job.module.getError(); } - // When the cached async job have already encountered a linking - // error that gets wrapped into a rejection, but is still later - // in line to throw on it, just unwrap and throw the linking error - // from require(). - if (job.instantiated) { - throwIfPromiseRejected(job.instantiated); - } if (status !== kEvaluating) { assert(status === kUninstantiated, `Unexpected module status ${status}`); - // If we get here, either there's a race where the job is still being instantiated - // by an in-flight import(), or the cached module previously encountered an + // If we get here, either the job was created by an in-flight import() that has not + // instantiated it yet, or the cached module previously encountered an // instantiation error during a prior load (e.g. due to a mismatched import). // TODO(joyeecheung): the current check is too broad. We should attempt to // get the potential instantiation error and throw it. - throw new ERR_REQUIRE_ESM_RACE_CONDITION(filename, parentFilename, false); + throw new ERR_REQUIRE_ESM_RACE_CONDITION(filename, parentFilename); } let message = `Cannot require() ES Module ${filename} in a cycle.`; if (parentFilename) { @@ -397,10 +307,6 @@ class ModuleLoader { * @param {ModuleJobBase} job The cached module job. */ #checkCachedJobForRequireESM(specifier, url, parentURL, job) { - // This race should only be possible on the loader hook thread. See https://github.com/nodejs/node/issues/59666 - if (!job.module) { - throw new ERR_REQUIRE_ESM_RACE_CONDITION(url, parentURL, this.isForAsyncLoaderHookWorker); - } // This module is being evaluated, which means it's imported in a previous link // in a cycle. if (job.module.getStatus() === kEvaluating) { @@ -422,7 +328,7 @@ class ModuleLoader { * Load a module and translate it into a ModuleWrap for require(esm). * This is run synchronously, and the translator always return a ModuleWrap synchronously. * @param {ResolveResult} resolveResult Result from the resolve step. - * @param {object} loadContext See {@link load} + * @param {{ format: string, importAttributes: object }} loadContext Context for the load step. * @param {string|undefined} parentURL URL of the parent module. Undefined if it's the entry point. * @param {ModuleRequest} request Module request. * @returns {ModuleWrap} @@ -460,8 +366,7 @@ class ModuleLoader { } /** - * Translate a loaded module source into a ModuleWrap. This is run synchronously, - * but the translator may return the ModuleWrap in a Promise. + * Translate a loaded module source into a ModuleWrap synchronously. * @param {string} url URL of the module to be translated. * @param {TranslateContext} translateContext Context for the translator * @param {string|undefined} parentURL URL of the module initiating the module loading for the first time. @@ -479,98 +384,47 @@ class ModuleLoader { const result = FunctionPrototypeCall(translator, this, url, translateContext, parentURL); assert(result instanceof ModuleWrap, `The ${format} module returned is not a ModuleWrap`); - if (format === 'commonjs' || format === 'commonjs-sync' || format === 'require-commonjs') { + if (format === 'commonjs' || format === 'commonjs-sync') { result.isCommonJS = true; } return result; } /** - * Load a module and translate it into a ModuleWrap for require() in imported CJS. - * This is run synchronously, and the translator always return a ModuleWrap synchronously. + * Load a module and translate it into a ModuleWrap for ordinary imported ESM. + * Loading and translation are synchronous; evaluation may be asynchronous. * @param {ResolveResult} resolveResult Result from the resolve step. - * @param {object} loadContext See {@link load} + * @param {{ format: string, importAttributes: object }} loadContext Context for the load step. * @param {string|undefined} parentURL URL of the parent module. Undefined if it's the entry point. * @returns {ModuleWrap} */ - loadAndTranslateForRequireInImportedCJS(resolveResult, loadContext, parentURL) { + loadAndTranslate(resolveResult, loadContext, parentURL) { const { url } = resolveResult; const loadResult = this.#loadSync(url, loadContext); - const formatFromLoad = loadResult.format; - - if (formatFromLoad === 'wasm') { // require(wasm) is not supported. - throw new ERR_UNKNOWN_MODULE_FORMAT(formatFromLoad, url); - } - - if (formatFromLoad === 'module' || formatFromLoad === 'module-typescript') { - if (!getOptionValue('--require-module')) { - throw new ERR_REQUIRE_ESM(url, true); - } - } - - let translatorKey = formatFromLoad; - if (formatFromLoad === 'commonjs') { - translatorKey = 'require-commonjs'; - } - if (formatFromLoad === 'commonjs-typescript') { - translatorKey = 'require-commonjs-typescript'; - } - const translateContext = { ...resolveResult, ...loadResult, - translatorKey, + translatorKey: loadResult.format, __proto__: null, }; - const wrap = this.#translate(url, translateContext, parentURL); - assert(wrap instanceof ModuleWrap, `Translator used for require(${url}) should not be async`); - return wrap; - } - - /** - * Load a module and translate it into a ModuleWrap for ordinary imported ESM. - * This may be run asynchronously if there are asynchronous module loader hooks registered. - * @param {ResolveResult} resolveResult Result from the resolve step. - * @param {object} loadContext See {@link load} - * @param {string|undefined} parentURL URL of the parent module. Undefined if it's the entry point. - * @returns {Promise|ModuleWrap} - */ - loadAndTranslate(resolveResult, loadContext, parentURL) { - const { url } = resolveResult; - const maybePromise = this.load(url, loadContext); - const afterLoad = (loadResult) => { - const translateContext = { - ...resolveResult, - ...loadResult, - translatorKey: loadResult.format, - __proto__: null, - }; - return this.#translate(url, translateContext, parentURL); - }; - if (isPromise(maybePromise)) { - return PromisePrototypeThen(maybePromise, afterLoad); - } - return afterLoad(maybePromise); + return this.#translate(url, translateContext, parentURL); } /** * Given a resolved module request, obtain a ModuleJobBase from it - if it's already cached, * return the cached ModuleJobBase. Otherwise, load its source and translate it into a ModuleWrap first. - * This runs synchronously. On any thread that is not an async loader hook worker thread, - * the module should be linked by the time this returns. Otherwise it may still have - * pending module requests. + * This runs synchronously, so the module should be linked by the time this returns. * @param {string} parentURL See {@link getOrCreateModuleJob} * @param {ResolveResult} resolveResult * @param {ModuleRequest} request Module request. * @param {ModuleRequestType} requestType Type of the module request. - * @returns {ModuleJobBase} The (possibly pending) module job + * @returns {ModuleJobBase} The module job */ #getOrCreateModuleJobAfterResolve(parentURL, resolveResult, request, requestType) { const { url, format } = resolveResult; if (process.env.WATCH_REPORT_DEPENDENCIES && process.send) { - const type = requestType === kRequireInImportedCJS ? 'require' : 'import'; - process.send({ [`watch:${type}`]: [url] }); + process.send({ 'watch:import': [url] }); } // Relay Events from worker to main thread if (process.env.WATCH_REPORT_DEPENDENCIES && !process.send) { @@ -597,34 +451,19 @@ class ModuleLoader { const context = { format, importAttributes, __proto__: null }; - let moduleOrModulePromise; - if (requestType === kRequireInImportedCJS) { - moduleOrModulePromise = this.loadAndTranslateForRequireInImportedCJS(resolveResult, context, parentURL); - } else if (requestType === kImportInRequiredESM) { - moduleOrModulePromise = this.loadAndTranslateForImportInRequiredESM(resolveResult, context, parentURL, request); - } else { - moduleOrModulePromise = this.loadAndTranslate(resolveResult, context, parentURL); - } + const module = requestType === kImportInRequiredESM ? + this.loadAndTranslateForImportInRequiredESM(resolveResult, context, parentURL, request) : + this.loadAndTranslate(resolveResult, context, parentURL); - if (requestType === kImportInRequiredESM || requestType === kRequireInImportedCJS || - !this.isForAsyncLoaderHookWorker) { - assert(moduleOrModulePromise instanceof ModuleWrap, `Expected ModuleWrap for loading ${url}`); - } + assert(module instanceof ModuleWrap, `Expected ModuleWrap for loading ${url}`); const isMain = (parentURL === undefined); const inspectBrk = (isMain && getOptionValue('--inspect-brk')); - // Use ModuleJobSync for kImportInRequiredESM, which must always be linked synchronously, - // and whenever we're on the main thread (not the async loader hook worker), - // except for kRequireInImportedCJS (TODO: consolidate that case too). - // TODO(joyeecheung): use ModuleJobSync for kRequireInImportedCJS too. - const ModuleJobCtor = (requestType === kImportInRequiredESM || - (!this.isForAsyncLoaderHookWorker && requestType !== kRequireInImportedCJS)) ? - ModuleJobSync : ModuleJob; - job = new ModuleJobCtor( + job = new ModuleJobSync( this, url, importAttributes, - moduleOrModulePromise, + module, request.phase, isMain, inspectBrk, @@ -637,35 +476,16 @@ class ModuleLoader { } /** - * Get a (possibly not yet fully linked) module job from the cache, or create one and return its Promise. + * Get a module job from the cache, or create one. Loading and linking are synchronous. * @param {string} [parentURL] The URL of the module where the module request is initiated. * It's undefined if it's from the root module. * @param {ModuleRequest} request Module request. * @param {ModuleRequestType} requestType Type of the module request. - * @returns {Promise|ModuleJobBase} + * @returns {ModuleJobBase} */ getOrCreateModuleJob(parentURL, request, requestType) { - let maybePromise; - if (!this.isForAsyncLoaderHookWorker || - requestType === kRequireInImportedCJS || requestType === kImportInRequiredESM) { - // On the main thread, always resolve synchronously; - // `resolveSync` coordinates with the async loader hook worker if needed. - // On the async loader hook worker, require(esm) and require() in imported CJS - // must still be resolved synchronously. - maybePromise = this.resolveSync(parentURL, request); - assert(!isPromise(maybePromise)); - } else { - maybePromise = this.#resolve(parentURL, request); - } - - const afterResolve = (resolveResult) => { - return this.#getOrCreateModuleJobAfterResolve(parentURL, resolveResult, request, requestType); - }; - - if (isPromise(maybePromise)) { - return PromisePrototypeThen(maybePromise, afterResolve); - } - return afterResolve(maybePromise); + const resolveResult = this.resolveSync(parentURL, request); + return this.#getOrCreateModuleJobAfterResolve(parentURL, resolveResult, request, requestType); } /** @@ -682,15 +502,10 @@ class ModuleLoader { async import(specifier, parentURL, importAttributes, phase = kEvaluationPhase, isEntryPoint = false) { return onImport.tracePromise(async () => { const request = { specifier, phase, attributes: importAttributes, __proto__: null }; - let moduleJob; - try { - moduleJob = await this.getOrCreateModuleJob(parentURL, request); - } catch (e) { - if (e?.code === 'ERR_ASYNC_LOADER_REQUEST_NEVER_SETTLED') { - return new Promise(() => {}); - } - throw e; - } + const moduleJob = this.getOrCreateModuleJob(parentURL, request); + // Loading and linking are synchronous, but import() must still never evaluate the + // module before returning to its caller, so yield once before running the job. + await null; if (phase === kSourcePhase) { const module = await moduleJob.modulePromise; return module.getModuleSourceObject(); @@ -704,44 +519,10 @@ class ModuleLoader { }); } - /** - * @see {@link AsyncLoaderHooks.register} - * @returns {any} - */ - register(specifier, parentURL, data, transferList, isInternal) { - if (!this.#asyncLoaderHooks) { - // On the loader hook worker thread, the #asyncLoaderHooks must already have been initialized - // to be an instance of AsyncLoaderHooksOnLoaderHookWorker, so this branch can only ever - // be hit on a non-loader-hook thread that will talk to the loader hook worker thread. - const { AsyncLoaderHooksProxiedToLoaderHookWorker } = require('internal/modules/esm/hooks'); - this.#setAsyncLoaderHooks(new AsyncLoaderHooksProxiedToLoaderHookWorker()); - } - return this.#asyncLoaderHooks.register(`${specifier}`, `${parentURL}`, data, transferList, isInternal); - } - - /** - * Resolve a module request to a URL identifying the location of the module. Handles customization hooks, - * if any. - * @param {string} [parentURL] The URL of the module where the module request is initiated. - * It's undefined if it's from the root module. - * @param {ModuleRequest} request Module request. - * @returns {Promise|ResolveResult} - */ - #resolve(parentURL, request) { - if (this.isForAsyncLoaderHookWorker) { - const specifier = `${request.specifier}`; - const importAttributes = request.attributes ?? kEmptyObject; - // TODO(joyeecheung): invoke the synchronous hooks in the default step on loader thread. - return this.#asyncLoaderHooks.resolve(specifier, parentURL, importAttributes); - } - - return this.resolveSync(parentURL, request); - } - /** * Either return a cached resolution, or perform the default resolution which is synchronous, and * cache the result. - * @param {string} specifier See {@link resolve}. + * @param {string} specifier The module specifier. * @param {{ parentURL?: string, importAttributes: ImportAttributes, conditions?: string[]}} context * @returns {{ format: string, url: string }} */ @@ -759,37 +540,28 @@ class ModuleLoader { } /** - * This is the default resolve step for module.registerHooks(), which incorporates asynchronous hooks - * from module.register() which are run in a blocking fashion for it to be synchronous. + * This is the default resolve step for module.registerHooks(). * @param {{isResolvedByDefaultResolve: boolean}} out Output object to track whether the default resolve was used * without polluting the user-visible resolve result. - * @param {string|URL} specifier See {@link resolveSync}. + * @param {string|URL} specifier The module specifier. * @param {{ parentURL?: string, importAttributes: ImportAttributes, conditions?: string[]}} context - * See {@link resolveSync}. + * Resolution context. * @returns {{ format: string, url: string }} */ - #resolveAndMaybeBlockOnLoaderThread(out, specifier, context) { - if (this.#asyncLoaderHooks?.resolveSync) { - return this.#asyncLoaderHooks.resolveSync(specifier, context.parentURL, context.importAttributes); - } + #defaultResolve(out, specifier, context) { out.isResolvedByDefaultResolve = true; return this.#cachedDefaultResolve(specifier, context); } /** - * Similar to {@link resolve}, but the results are always synchronously returned. If there are any - * asynchronous resolve hooks from module.register(), it will block until the results are returned - * from the loader thread for this to be synchronous. - * This is here to support `import.meta.resolve()`, `require()` in imported CJS, and - * `module.registerHooks()` hooks. - * @param {string} [parentURL] See {@link resolve}. - * @param {ModuleRequest} request See {@link resolve}. - * @param {boolean} [shouldSkipSyncHooks] Whether to skip the synchronous hooks registered by module.registerHooks(). - * This is used to maintain compatibility for the re-invented require.resolve (in imported CJS customized - * by module.register()`) which invokes the CJS resolution separately from the hook chain. + * Resolve a module request to a URL identifying the location of the module. Handles + * `module.registerHooks()` hooks, if any. + * @param {string} [parentURL] The URL of the module where the module request is initiated. + * It's undefined if it's from the root module. + * @param {ModuleRequest} request Module request. * @returns {ResolveResult} */ - resolveSync(parentURL, request, shouldSkipSyncHooks = false) { + resolveSync(parentURL, request) { const specifier = `${request.specifier}`; const importAttributes = request.attributes ?? kEmptyObject; // Use an output parameter to track the state and avoid polluting the user-visible resolve results. @@ -797,10 +569,10 @@ class ModuleLoader { let result; let isResolvedBySyncHooks = false; - if (!shouldSkipSyncHooks && syncResolveHooks.length) { - // Has module.registerHooks() hooks, chain the asynchronous hooks in the default step. + if (syncResolveHooks.length) { + // Has module.registerHooks() hooks, chain the default resolver in the default step. result = resolveWithSyncHooks(specifier, parentURL, importAttributes, this.#defaultConditions, - this.#resolveAndMaybeBlockOnLoaderThread.bind(this, out)); + this.#defaultResolve.bind(this, out)); // If the default step ran, sync hooks did not short-circuit the resolution. isResolvedBySyncHooks = !out.isResolvedByDefaultResolve; } else { @@ -811,85 +583,34 @@ class ModuleLoader { importAttributes, __proto__: null, }; - result = this.#resolveAndMaybeBlockOnLoaderThread(out, specifier, context); + result = this.#defaultResolve(out, specifier, context); } result.isResolvedBySyncHooks = isResolvedBySyncHooks; return result; } /** - * Provide source that is understood by one of Node's translators. Handles customization hooks, - * if any. - * @typedef {{ - * format: ModuleFormat, - * source: ModuleSource, - * responseURL?: string, - * isSourceLoadedSynchronously: boolean, - * }} LoadResult + * Provide source that is understood by one of Node's translators. Handles + * `module.registerHooks()` hooks, if any. * @param {string} url The URL of the module to be loaded. - * @param {object} context Metadata about the module - * @returns {Promise | LoadResult}} - */ - load(url, context) { - if (this.isForAsyncLoaderHookWorker) { - // TODO(joyeecheung): invoke the synchronous hooks in the default step on loader thread. - return this.#asyncLoaderHooks.load(url, context); - } - return this.#loadSync(url, context); - } - - /** - * This is the default load step for module.registerHooks(), which incorporates asynchronous hooks - * from module.register() which are run in a blocking fashion for it to be synchronous. - * @param {{isSourceLoadedSynchronously: boolean}} out - * Output object to track whether the source was loaded synchronously without polluting - * the user-visible load result. - * @param {string} url See {@link load} - * @param {object} context See {@link load} - * @returns {{ format: ModuleFormat, source: ModuleSource }} - */ - #loadAndMaybeBlockOnLoaderThread(out, url, context) { - if (this.#asyncLoaderHooks?.loadSync) { - out.isSourceLoadedSynchronously = false; - return this.#asyncLoaderHooks.loadSync(url, context); - } - out.isSourceLoadedSynchronously = true; - return defaultLoadSync(url, context); - } - - /** - * Similar to {@link load} but this is always run synchronously. If there are asynchronous hooks - * from module.register(), this blocks on the loader thread for it to return synchronously. - * - * This is here to support `require()` in imported CJS and `module.registerHooks()` hooks. - * @param {string} url See {@link load} - * @param {object} [context] See {@link load} - * @returns {LoadResult} + * @param {{ format: string, importAttributes: object }} context Metadata about the module + * @returns {{ format: ModuleFormat, source: ModuleSource, responseURL?: string }} */ #loadSync(url, context) { - // Use an output parameter to track the state and avoid polluting the user-visible resolve results. - const out = { - isSourceLoadedSynchronously: true, - __proto__: null, - }; - let result; if (syncLoadHooks.length) { - // Has module.registerHooks() hooks, chain the asynchronous hooks in the default step. + // Has module.registerHooks() hooks, chain the default loader in the default step. // TODO(joyeecheung): construct the ModuleLoadContext in the loaders directly instead // of converting them from plain objects in the hooks. - result = loadWithSyncHooks( + return loadWithSyncHooks( url, context.format, context.importAttributes, this.#defaultConditions, - this.#loadAndMaybeBlockOnLoaderThread.bind(this, out), + defaultLoadSync, validateLoadSloppy, ); - } else { - result = this.#loadAndMaybeBlockOnLoaderThread(out, url, context); } - result.isSourceLoadedSynchronously = out.isSourceLoadedSynchronously; - return result; + return defaultLoadSync(url, context); } validateLoadResult(url, format) { @@ -897,60 +618,9 @@ class ModuleLoader { throwUnknownModuleFormat(url, format); } } - - /** - * Block until the async loader hooks have been initialized. - * - * No-op when no hooks have been supplied. - */ - waitForAsyncLoaderHookInitialization() { - this.#asyncLoaderHooks?.waitForLoaderHookInitialization(); - } } ObjectSetPrototypeOf(ModuleLoader.prototype, null); -let emittedLoaderFlagWarning = false; -/** - * A loader instance is used as the main entry point for loading ES modules. Currently, this is a singleton; there is - * only one used for loading the main module and everything in its dependency graph, though separate instances of this - * class might be instantiated as part of bootstrap for other purposes. - * @param {AsyncLoaderHooksOnLoaderHookWorker|undefined} [asyncLoaderHooks] - * Only provided when run on the loader hook thread. - * @returns {ModuleLoader} - */ -function createModuleLoader(asyncLoaderHooks) { - // Don't spawn a new loader hook worker if we are already in a loader hook worker to avoid infinite recursion. - if (shouldSpawnLoaderHookWorker()) { - assert(asyncLoaderHooks === undefined, 'asyncLoaderHooks should only be provided on the loader hook thread itself'); - const userLoaderPaths = getOptionValue('--experimental-loader'); - if (userLoaderPaths.length > 0) { - if (!emittedLoaderFlagWarning) { - const readableURIEncode = (string) => ArrayPrototypeReduce( - [ - [/'/g, '%27'], // We need to URL-encode the single quote as it's the delimiter for the --import flag. - [/%22/g, '"'], // We can decode the double quotes to improve readability. - [/%2F/ig, '/'], // We can decode the slashes to improve readability. - ], - (str, { 0: regex, 1: replacement }) => RegExpPrototypeSymbolReplace(hardenRegExp(regex), str, replacement), - encodeURIComponent(string)); - process.emitWarning( - '`--experimental-loader` may be removed in the future; instead use `register()`:\n' + - `--import 'data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; ${ArrayPrototypeJoin( - ArrayPrototypeMap(userLoaderPaths, (loader) => `register(${readableURIEncode(JSONStringify(loader))}, pathToFileURL("./"))`), - '; ', - )};'`, - 'ExperimentalWarning', - ); - emittedLoaderFlagWarning = true; - } - const { AsyncLoaderHooksProxiedToLoaderHookWorker } = require('internal/modules/esm/hooks'); - asyncLoaderHooks = new AsyncLoaderHooksProxiedToLoaderHookWorker(); - } - } - - return new ModuleLoader(asyncLoaderHooks); -} - let allowImportMetaResolveParentURL; /** * This is only called from the native ImportMetaObjectInitialize function to set up import.meta.resolve @@ -1000,20 +670,12 @@ let cascadedLoader; * require('internal/module/esm/loader').getOrInitializeCascadedLoader() * lazily only right before the loader is actually needed, and don't do it * in the top-level, to avoid circular dependencies. - * @param {AsyncLoaderHooksOnLoaderHookWorker|undefined} [asyncLoaderHooks] - * Only provided when run on the loader hook thread. * @returns {ModuleLoader} */ -function getOrInitializeCascadedLoader(asyncLoaderHooks) { +function getOrInitializeCascadedLoader() { if (!cascadedLoader) { - cascadedLoader = createModuleLoader(asyncLoaderHooks); - // import.meta.resolve is not allowed in the async loader hook worker thread. - // So only set up the import.meta.resolve initializer when we are initializing - // the non-loader-hook-thread cascaded loader. When the native land doesn't see it, - // it knows the loader is running on the loader hook thread. - if (!(asyncLoaderHooks?.isForAsyncLoaderHookWorker)) { - setImportMetaResolveInitializer(createImportMetaResolve.bind(null, cascadedLoader)); - } + cascadedLoader = new ModuleLoader(); + setImportMetaResolveInitializer(createImportMetaResolve.bind(null, cascadedLoader)); } return cascadedLoader; } @@ -1022,60 +684,8 @@ function isCascadedLoaderInitialized() { return cascadedLoader !== undefined; } -/** - * Register a single loader programmatically. - * @param {string|URL} specifier - * @param {string|URL} [parentURL] Base to use when resolving `specifier`; optional if - * `specifier` is absolute. Same as `options.parentUrl`, just inline - * @param {object} [options] Additional options to apply, described below. - * @param {string|URL} [options.parentURL] Base to use when resolving `specifier` - * @param {any} [options.data] Arbitrary data passed to the loader's `initialize` hook - * @param {any[]} [options.transferList] Objects in `data` that are changing ownership - * @returns {void} We want to reserve the return value for potential future extension of the API. - * @example - * ```js - * register('./myLoader.js'); - * register('ts-node/esm', { parentURL: import.meta.url }); - * register('./myLoader.js', { parentURL: import.meta.url }); - * register('ts-node/esm', import.meta.url); - * register('./myLoader.js', import.meta.url); - * register(new URL('./myLoader.js', import.meta.url)); - * register('./myLoader.js', { - * parentURL: import.meta.url, - * data: { banana: 'tasty' }, - * }); - * register('./myLoader.js', { - * parentURL: import.meta.url, - * data: someArrayBuffer, - * transferList: [someArrayBuffer], - * }); - * ``` - */ -const emitRegisterDeprecation = getDeprecationWarningEmitter( - 'DEP0205', - '`module.register()` is deprecated. Use `module.registerHooks()` instead.', - undefined, - false, -); - -function register(specifier, parentURL = undefined, options) { - emitRegisterDeprecation(); - - if (parentURL != null && typeof parentURL === 'object' && !isURL(parentURL)) { - options = parentURL; - parentURL = options.parentURL; - } - getOrInitializeCascadedLoader().register( - specifier, - parentURL ?? 'data:', - options?.data, - options?.transferList, - ); -} - module.exports = { - createModuleLoader, + ModuleLoader, getOrInitializeCascadedLoader, isCascadedLoaderInitialized, - register, }; diff --git a/lib/internal/modules/esm/module_job.js b/lib/internal/modules/esm/module_job.js index c8506b42ff3f..ba300785c9eb 100644 --- a/lib/internal/modules/esm/module_job.js +++ b/lib/internal/modules/esm/module_job.js @@ -11,12 +11,10 @@ const { FunctionPrototype, ObjectAssign, ObjectSetPrototypeOf, - PromisePrototypeThen, PromiseResolve, RegExpPrototypeExec, RegExpPrototypeSymbolReplace, RegExpPrototypeSymbolSplit, - SafePromiseAllReturnArrayLike, SafePromiseAllReturnVoid, SafeSet, StringPrototypeIncludes, @@ -31,15 +29,11 @@ let debug = require('internal/util/debuglog').debuglog('esm', (fn) => { const { ModuleWrap, kErrored, - kEvaluated, - kEvaluating, kDeferPhase, kEvaluationPhase, kInstantiated, - kUninstantiated, } = internalBinding('module_wrap'); const { - getPromiseDetails, privateSymbols: { entry_point_module_private_symbol, module_source_private_symbol: kModuleSource, @@ -49,7 +43,6 @@ const { * @typedef {import('./utils.js').ModuleRequestType} ModuleRequestType */ const { decorateErrorStack } = require('internal/util'); -const { isPromise } = require('internal/util/types'); const { getSourceMapsSupport, } = require('internal/source_map/source_map_cache'); @@ -63,7 +56,6 @@ const { getOptionValue } = require('internal/options'); const noop = FunctionPrototype; const { ERR_REQUIRE_ASYNC_MODULE, - ERR_REQUIRE_ESM_RACE_CONDITION, } = require('internal/errors').codes; const CJSGlobalLike = [ @@ -193,14 +185,7 @@ function findModulesWithTopLevelAwait(root) { if (job.module?.hasTopLevelAwait) { ArrayPrototypePush(found, job.module); } - let linked = job.linked; - if (isPromise(linked)) { - linked = getPromiseDetails(linked)?.[1]; - } - // If `require(esm)` comes from the deprecated async loader hook worker thread, - // linked may be pending at this point. In that case, this branch would be skipped - - // we just allow lossy reporting of TLA locations in an edge case when a deprecated - // feature is used in combination with another experimental flag. + const { linked } = job; if (ArrayIsArray(linked)) { for (let i = 0; i < linked.length; i++) { ArrayPrototypePush(stack, linked[i]); @@ -309,7 +294,7 @@ class ModuleJobBase { * @param {ModuleRequestType} requestType Type of the module request. * @returns {ModuleJobBase[]} */ - syncLink(requestType) { + link(requestType) { // Store itself into the cache first before linking in case there are circular // references in the linking. Track whether we're overwriting an existing entry // so we know whether to remove the temporary entry in the finally block. @@ -323,11 +308,8 @@ class ModuleJobBase { try { for (let idx = 0; idx < moduleRequests.length; idx++) { const request = moduleRequests[idx]; - // TODO(joyeecheung): split this into two iterators, one for resolving and one for loading so - // that hooks can pre-fetch sources off-thread. const job = this.loader.getOrCreateModuleJob(this.url, request, requestType); - debug(`ModuleJobBase.syncLink() ${this.url} -> ${request.specifier}`, job); - assert(!isPromise(job)); + debug(`ModuleJobBase.link() ${this.url} -> ${request.specifier}`, job); assert(job.module instanceof ModuleWrap); if (this.shouldRunModule(request.phase)) { ArrayPrototypePush(evaluationDepJobs, job); @@ -374,9 +356,6 @@ class ModuleJobBase { if (this.phase < phase) { this.phase = phase; this.linked = this.link(requestType); - if (isPromise(this.linked)) { - PromisePrototypeThen(this.linked, undefined, noop); - } } } @@ -395,33 +374,23 @@ class ModuleJob extends ModuleJobBase { * @param {ModuleLoader} loader The ESM loader. * @param {string} url URL of the module to be wrapped in ModuleJob. * @param {ImportAttributes} importAttributes Import attributes from the import statement. - * @param {ModuleWrap|Promise} moduleOrModulePromise Translated ModuleWrap for the module. + * @param {ModuleWrap} module Translated ModuleWrap for the module. * @param {number} phase The phase to load the module to. * @param {boolean} isMain Whether the module is the entry point. * @param {boolean} inspectBrk Whether this module should be evaluated with the * first line paused in the debugger (because --inspect-brk is passed). * @param {ModuleRequestType} requestType Type of the module request. */ - constructor(loader, url, importAttributes = { __proto__: null }, moduleOrModulePromise, + constructor(loader, url, importAttributes = { __proto__: null }, module, phase = kEvaluationPhase, isMain, inspectBrk, requestType) { super(loader, url, importAttributes, phase, isMain, inspectBrk); - // Expose the promise to the ModuleWrap directly for linking below. - if (isPromise(moduleOrModulePromise)) { - this.modulePromise = moduleOrModulePromise; - } else { - this.module = moduleOrModulePromise; - this.modulePromise = PromiseResolve(moduleOrModulePromise); - } + this.module = module; + this.modulePromise = PromiseResolve(module); if (this.shouldLinkModule(this.phase)) { - // Promise for the list of all dependencyJobs. + // The list of all dependencyJobs. this.linked = this.link(requestType); - // This promise is awaited later anyway, so silence - // 'unhandled rejection' warnings. - if (isPromise(this.linked)) { - PromisePrototypeThen(this.linked, undefined, noop); - } } // instantiated == deep dependency jobs wrappers are instantiated, @@ -429,59 +398,6 @@ class ModuleJob extends ModuleJobBase { this.instantiated = undefined; } - /** - * @param {ModuleRequestType} requestType Type of the module request. - * @returns {ModuleJobBase[]|Promise} - */ - link(requestType) { - if (this.loader.isForAsyncLoaderHookWorker) { - return this.#asyncLink(requestType); - } - return this.syncLink(requestType); - } - - /** - * @param {ModuleRequestType} requestType Type of the module request. - * @returns {Promise} - */ - async #asyncLink(requestType) { - assert(this.loader.isForAsyncLoaderHookWorker); - this.module = await this.modulePromise; - assert(this.module instanceof ModuleWrap); - const moduleRequests = this.module.getModuleRequests(); - // Create an ArrayLike to avoid calling into userspace with `.then` - // when returned from the async function. - // Modules should be aligned with the moduleRequests array in order. - const modulePromises = Array(moduleRequests.length); - const evaluationDepJobs = []; - this.commonJsDeps = Array(moduleRequests.length); - for (let idx = 0; idx < moduleRequests.length; idx++) { - const request = moduleRequests[idx]; - // Explicitly keeping track of dependency jobs is needed in order - // to flatten out the dependency graph below in `asyncInstantiate()`, - // so that circular dependencies can't cause a deadlock by two of - // these `link` callbacks depending on each other. - // TODO(joyeecheung): split this into two iterators, one for resolving and one for loading so - // that hooks can pre-fetch sources off-thread. - const dependencyJobPromise = this.loader.getOrCreateModuleJob(this.url, request, requestType); - const modulePromise = PromisePrototypeThen(dependencyJobPromise, (job) => { - debug(`ModuleJob.asyncLink() ${this.url} -> ${request.specifier}`, job); - if (this.shouldRunModule(request.phase)) { - ArrayPrototypePush(evaluationDepJobs, job); - } - return job.modulePromise; - }); - modulePromises[idx] = modulePromise; - } - const modules = await SafePromiseAllReturnArrayLike(modulePromises); - for (let idx = 0; idx < moduleRequests.length; idx++) { - this.commonJsDeps[idx] = modules[idx].isCommonJS; - } - - this.module.link(modules); - return evaluationDepJobs; - } - #instantiate() { if (this.instantiated === undefined) { this.instantiated = this.#asyncInstantiate(); @@ -491,8 +407,6 @@ class ModuleJob extends ModuleJobBase { async #asyncInstantiate() { const jobsInGraph = new SafeSet(); - // TODO(joyeecheung): if it's not on the async loader thread, consider this already - // linked. const addJobsToDependencyGraph = async (moduleJob) => { debug(`async addJobsToDependencyGraph() ${this.url}`, moduleJob); @@ -500,8 +414,7 @@ class ModuleJob extends ModuleJobBase { return; } jobsInGraph.add(moduleJob); - const dependencyJobs = isPromise(moduleJob.linked) ? await moduleJob.linked : moduleJob.linked; - return SafePromiseAllReturnVoid(dependencyJobs, addJobsToDependencyGraph); + return SafePromiseAllReturnVoid(moduleJob.linked, addJobsToDependencyGraph); }; await addJobsToDependencyGraph(this); @@ -520,46 +433,6 @@ class ModuleJob extends ModuleJobBase { } } - runSync(parent) { - assert(this.shouldRunModule(this.phase)); - assert(this.module instanceof ModuleWrap); - let status = this.module.getStatus(); - - debug('ModuleJob.runSync()', status, this.module); - if (status === kUninstantiated) { - // TODO(joyeecheung): Reject graphs with top-level await _before_ instantiation, so that - // the async graph error supersedes instantiation (mismatch export) errors in the graph. - // FIXME(joyeecheung): this cannot fully handle < kInstantiated. Make the linking - // fully synchronous instead. - if (this.module.getModuleRequests().length === 0) { - this.module.link([]); - } - this.module.instantiate(); - status = this.module.getStatus(); - } - if (status === kInstantiated || status === kErrored) { - this.throwIfAsyncGraph(parent); - if (status === kInstantiated) { - setHasStartedUserESMExecution(); - const namespace = this.module.evaluateSync(); - return { __proto__: null, module: this.module, namespace }; - } - throw this.module.getError(); - } else if (status === kEvaluating || status === kEvaluated) { - this.throwIfAsyncGraph(parent); - // kEvaluating can show up when this is being used to deal with CJS <-> CJS cycles. - // Allow it for now, since we only need to ban ESM <-> CJS cycles which would be - // detected earlier during the linking phase, though the CJS handling in the ESM - // loader won't be able to emit warnings on pending circular exports like what - // the CJS loader does. - // TODO(joyeecheung): remove the re-invented require() in the ESM loader and - // always handle CJS using the CJS loader to eliminate the quirks. - return { __proto__: null, module: this.module, namespace: this.module.getNamespace() }; - } - assert(status === kUninstantiated, `Unexpected module status ${status}.`); - throw new ERR_REQUIRE_ESM_RACE_CONDITION(); - } - async run(isEntryPoint = false) { debug('ModuleJob.run()', this.module); assert(this.shouldRunModule(this.phase)); @@ -581,16 +454,12 @@ class ModuleJob extends ModuleJobBase { } /** - * This is a fully synchronous job and does not spawn additional threads in any way. * Loading and linking are always synchronous. Evaluation via runSync() throws on an * asynchronous graph; evaluation via run() falls back to async for top-level await. * It also disallows CJS <-> ESM cycles. * - * Used for all ES module imports on the main thread, regardless of how the import was - * triggered (entry point, import(), require(esm), --import, etc.). - * Modules loaded by require() in imported CJS are handled by ModuleJob with the - * isForRequireInImportedCJS set to true instead. The two currently have different caching behaviors. - * TODO(joyeecheung): consolidate this with the isForRequireInImportedCJS variant of ModuleJob. + * Used for all ES module imports regardless of how the import was triggered + * (entry point, import(), require(esm), --import, etc.). */ class ModuleJobSync extends ModuleJobBase { /** @@ -617,15 +486,6 @@ class ModuleJobSync extends ModuleJobBase { } } - /** - * @param {ModuleRequestType} requestType Type of the module request. - * @returns {ModuleJobBase[]} - */ - link(requestType) { - // Synchronous linking is always used for ModuleJobSync. - return this.syncLink(requestType); - } - get modulePromise() { return PromiseResolve(this.module); } @@ -708,9 +568,6 @@ class ModuleJobSync extends ModuleJobBase { // TODO(joyeecheung): Reject graphs with top-level await _before_ instantiation, so that the // async graph error supersedes instantiation (mismatch export) errors in the graph. this.instantiateSync(); - // On the deprecated async loader hook worker thread, dependencies linked by an - // earlier import may not be walkable synchronously, so double-check with - // V8 now that the graph is instantiated. this.throwIfAsyncGraph(parent); setHasStartedUserESMExecution(); try { diff --git a/lib/internal/modules/esm/module_map.js b/lib/internal/modules/esm/module_map.js index 207515573c79..05a6c3082d28 100644 --- a/lib/internal/modules/esm/module_map.js +++ b/lib/internal/modules/esm/module_map.js @@ -20,7 +20,8 @@ const { validateString } = require('internal/validators'); * Future resolutions of the same input (specifier, parent URL and import attributes) * must return the same result if the first attempt was successful, per * https://tc39.es/ecma262/#sec-HostLoadImportedModule. - * This cache is *not* used when custom loaders are registered. + * Only results of the default resolver are cached; results returned by + * `module.registerHooks()` resolve hooks are not. */ class ResolveCache extends SafeMap { /** diff --git a/lib/internal/modules/esm/shared_constants.js b/lib/internal/modules/esm/shared_constants.js deleted file mode 100644 index 4200bc87367d..000000000000 --- a/lib/internal/modules/esm/shared_constants.js +++ /dev/null @@ -1,25 +0,0 @@ -// This file contains the definition for the constant values that must be -// available to both the main thread and the loader thread. - -'use strict'; - -/* -The shared memory area is divided in 1 32-bit long section. It has to be 32-bit long as -`Atomics.notify` only works with `Int32Array` objects. - ---32-bits-- - ^ - | - | -WORKER_TO_MAIN_THREAD_NOTIFICATION - -WORKER_TO_MAIN_THREAD_NOTIFICATION is only used to send notifications, its value is going to -increase every time the worker sends a notification to the main thread. - -*/ - -module.exports = { - WORKER_TO_MAIN_THREAD_NOTIFICATION: 0, - - SHARED_MEMORY_BYTE_LENGTH: 1 * 4, -}; diff --git a/lib/internal/modules/esm/translators.js b/lib/internal/modules/esm/translators.js index 583b6d1fe91d..100f791b98ee 100644 --- a/lib/internal/modules/esm/translators.js +++ b/lib/internal/modules/esm/translators.js @@ -17,13 +17,9 @@ const { globalThis, } = primordials; -const { - compileFunctionForCJSLoader, -} = internalBinding('contextify'); - const { BuiltinModule } = require('internal/bootstrap/realm'); const assert = require('internal/assert'); -const { dirname, extname } = require('path'); +const { dirname } = require('path'); const { assertBufferSource, loadBuiltinModule, @@ -45,22 +41,20 @@ const { findLongestRegisteredExtension, resolveForCJSWithHooks, loadSourceForCJSWithHooks, - populateCJSExportsFromESM, } = require('internal/modules/cjs/loader'); -const { fileURLToPath, pathToFileURL, URL } = require('internal/url'); +const { fileURLToPath, URL } = require('internal/url'); const { isAnyArrayBuffer } = require('internal/util/types'); let debug = require('internal/util/debuglog').debuglog('esm', (fn) => { debug = fn; }); -const { emitExperimentalWarning, kEmptyObject, setOwnProperty, isWindows } = require('internal/util'); +const { emitExperimentalWarning, setOwnProperty, isWindows } = require('internal/util'); const { ERR_INVALID_RETURN_PROPERTY_VALUE, ERR_UNKNOWN_BUILTIN_MODULE, ERR_WEBASSEMBLY_NOT_SUPPORTED, } = require('internal/errors').codes; -const { maybeCacheSourceMap } = require('internal/source_map/source_map_cache'); const moduleWrap = internalBinding('module_wrap'); -const { ModuleWrap, kEvaluationPhase } = moduleWrap; +const { ModuleWrap } = moduleWrap; const { getSourceSync } = require('internal/modules/esm/load'); @@ -101,108 +95,9 @@ translators.set('module', function moduleStrategy(url, translateContext, parentU return module; }); -const { requestTypes: { kRequireInImportedCJS } } = require('internal/modules/esm/utils'); const kShouldSkipModuleHooks = { __proto__: null, shouldSkipModuleHooks: true }; const kShouldNotSkipModuleHooks = { __proto__: null, shouldSkipModuleHooks: false }; -/** - * This may be eventually removed when module.register() reaches end-of-life. - * - * Loads a CommonJS module via the ESM Loader sync CommonJS translator. - * This translator creates its own version of the `require` function passed into CommonJS modules. - * Any monkey patches applied to the CommonJS Loader will not affect this module. - * Any `require` calls in this module will load all children in the same way. - * @param {import('internal/modules/cjs/loader').Module} module - The module to load. - * @param {string} source - The source code of the module. - * @param {string} url - The URL of the module. - * @param {string} filename - The filename of the module. - * @param {boolean} isMain - Whether the module is the entrypoint - * @param {TranslateContext} translateContext Context for the translator - */ -function loadCJSModuleWithSpecialRequire(module, source, url, filename, isMain, translateContext) { - // Use the full URL as the V8 resource name so that any search params - // (e.g. ?node-test-mock) are preserved in coverage reports. - const compileResult = compileFunctionForCJSLoader(source, url, false /* is_sea_main */, false); - - const { function: compiledWrapper, sourceMapURL, sourceURL } = compileResult; - // Cache the source map for the cjs module if present. - if (sourceMapURL) { - maybeCacheSourceMap(url, source, module, false, sourceURL, sourceMapURL); - } - const cascadedLoader = require('internal/modules/esm/loader').getOrInitializeCascadedLoader(); - const __dirname = dirname(filename); - // eslint-disable-next-line node-core/func-name-matching,func-style - const requireFn = function require(specifier) { - let importAttributes = kEmptyObject; - if (!StringPrototypeStartsWith(specifier, 'node:') && !BuiltinModule.normalizeRequirableId(specifier)) { - // TODO: do not depend on the monkey-patchable CJS loader here. - const path = CJSModule._resolveFilename(specifier, module); - switch (extname(path)) { - case '.json': - importAttributes = { __proto__: null, type: 'json' }; - break; - case '.node': - // If it gets here in the translators, the hooks must have already been invoked - // in the loader. Skip them in the synthetic module evaluation step. - return wrapModuleLoad(specifier, module, false, kShouldSkipModuleHooks); - default: - // fall through - } - specifier = `${pathToFileURL(path)}`; - } - - // NOTE: This re-invented require() is only used on the loader-hook worker thread. - // On the main thread, the authentic require() is used instead (fixed by #60380). - const request = { specifier, attributes: importAttributes, phase: kEvaluationPhase, __proto__: null }; - const job = cascadedLoader.getOrCreateModuleJob(url, request, kRequireInImportedCJS); - job.runSync(module); - let mod = cjsCache.get(job.url); - assert(job.module, `Imported CJS module ${url} failed to load module ${job.url} using require() due to race condition`); - - if (job.module.synthetic) { - assert(mod, `Imported CJS module ${url} failed to load module ${job.url} using require() due to missed cache`); - return mod.exports; - } - - // The module being required is a source text module. - if (!mod) { - mod = cjsEmplaceModuleCacheEntry(job.url); - cjsCache.set(job.url, mod); - } - // The module has been cached by the re-invented require. Update the exports object - // from the namespace object and return the evaluated exports. - if (!mod.loaded) { - debug('populateCJSExportsFromESM from require(esm) in imported CJS', url, mod, job.module); - populateCJSExportsFromESM(mod, job.module, job.module.getNamespace()); - mod.loaded = true; - } - return mod.exports; - }; - setOwnProperty(requireFn, 'resolve', function resolve(specifier) { - if (!StringPrototypeStartsWith(specifier, 'node:')) { - const { - filename, url: resolvedURL, - } = resolveForCJSWithHooks(specifier, module, false, kShouldNotSkipModuleHooks); - if (specifier !== filename) { - specifier = resolvedURL ?? `${pathToFileURL(filename)}`; - } - } - - const request = { specifier, __proto__: null, attributes: kEmptyObject }; - // Skip sync hooks in resolveSync since resolveForCJSWithHooks already ran them above. - const { url: resolvedURL } = cascadedLoader.resolveSync(url, request, /* shouldSkipSyncHooks */ true); - return urlToFilename(resolvedURL); - }); - setOwnProperty(requireFn, 'main', process.mainModule); - - FunctionPrototypeCall(compiledWrapper, module.exports, - module.exports, requireFn, module, filename, __dirname); - setOwnProperty(module, 'loaded', true); -} - -// TODO: can we use a weak map instead? -const cjsCache = new SafeMap(); - /** * Creates a ModuleWrap object for a CommonJS module. * @param {string} url - The URL of the module. @@ -226,7 +121,6 @@ function createCJSModuleWrap(url, translateContext, parentURL) { } const { exportNames, module } = cjsPreparseModuleExports(filename, source, sourceFormat); - cjsCache.set(url, module); const wrapperNames = [...exportNames]; if (!exportNames.has('default')) { @@ -244,19 +138,7 @@ function createCJSModuleWrap(url, translateContext, parentURL) { debug(`Loading CJSModule ${url}`); if (!module.loaded) { - // For backward-compatibility, it's possible for async hooks to return a nullish value for - // CJS source associated with a `file:` URL - that usually means the source is not - // customized (is loaded by default load) or the hook author wants it to be reloaded - // through CJS routine. In this case, the source is obtained by calling the - // Module._load(). - if (translateContext.translatorKey === 'commonjs-sync' || - translateContext.isSourceLoadedSynchronously || - translateContext.source == null) { - loadCJSModuleWithModuleLoad(module, source, url, filename, !!isMain, translateContext); - } else { // CommonJS with source customized by async hooks - // This may be eventually removed when module.register() reaches end-of-life. - loadCJSModuleWithSpecialRequire(module, source, url, filename, !!isMain, translateContext); - } + loadCJSModuleWithModuleLoad(module, source, url, filename, !!isMain, translateContext); } let exports; @@ -298,7 +180,6 @@ function createCJSNoSourceModuleWrap(url, parentURL) { const filename = urlToFilename(url); const module = cjsEmplaceModuleCacheEntry(filename); - cjsCache.set(url, module); if (isMain) { setOwnProperty(process, 'mainModule', module); @@ -333,25 +214,10 @@ translators.set('commonjs-sync', function requireCommonJS(url, translateContext, return createCJSModuleWrap(url, translateContext, parentURL); }); -// Handle CommonJS modules referenced by `require` calls using re-invented require. -// This path is only used by require() from imported CJS customized by the *async* -// loader hooks. -translators.set('require-commonjs', (url, translateContext, parentURL) => { - return createCJSModuleWrap(url, translateContext, parentURL); -}); - -// Handle TypeScript CommonJS modules referenced by `require` calls using re-invented require. -// This path is only used by require() from imported CJS customized by the *async* -// loader hooks. -translators.set('require-commonjs-typescript', (url, translateContext, parentURL) => { - translateContext.source = stripTypeScriptModuleTypes(stringify(translateContext.source), url); - return createCJSModuleWrap(url, translateContext, parentURL); -}); - // This goes through Module._load to accommodate monkey-patchers. /** * Loads a CommonJS module through Module._load to accommodate monkey-patchers. - * If the module was resolved by synchronous hooks (i.e. not by the default resolver), + * If the module was resolved by hooks (i.e. not by the default resolver), * passes the pre-resolved information and source to Module._load to avoid * re-resolving and re-loading. * @param {import('internal/modules/cjs/loader').Module} module - The module to load. @@ -483,7 +349,6 @@ translators.set('builtin', function builtinStrategy(url, translateContext) { // Slice 'node:' scheme const id = StringPrototypeSlice(url, 5); const module = loadBuiltinModule(id, url); - cjsCache.set(url, module); if (!StringPrototypeStartsWith(url, 'node:') || !module) { throw new ERR_UNKNOWN_BUILTIN_MODULE(url); } @@ -545,7 +410,6 @@ translators.set('json', function jsonStrategy(url, translateContext) { if (shouldCheckAndPopulateCJSModuleCache) { CJSModule._cache[modulePath] = module; } - cjsCache.set(url, module); return new ModuleWrap(url, undefined, ['default'], function() { debug(`Parsing JSONModule ${url}`); this.setExport('default', module.exports); diff --git a/lib/internal/modules/esm/utils.js b/lib/internal/modules/esm/utils.js index 8937b8a61298..b8c8b10d3867 100644 --- a/lib/internal/modules/esm/utils.js +++ b/lib/internal/modules/esm/utils.js @@ -296,14 +296,10 @@ async function importModuleDynamicallyCallback(referrerSymbol, specifier, phase, throw new ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING(); } -let _shouldSpawnLoaderHookWorker = true; /** * Initializes handling of ES modules. - * @param {boolean} [shouldSpawnLoaderHookWorker] Whether the custom loader worker - * should be spawned later. */ -function initializeESM(shouldSpawnLoaderHookWorker = true) { - _shouldSpawnLoaderHookWorker = shouldSpawnLoaderHookWorker; +function initializeESM() { initializeDefaultConditions(); // Setup per-realm callbacks that locate data or callbacks that we keep // track of for different ESM modules. @@ -311,15 +307,6 @@ function initializeESM(shouldSpawnLoaderHookWorker = true) { setImportModuleDynamicallyCallback(importModuleDynamicallyCallback); } -/** - * Determine whether the custom loader worker should be spawned when initializing - * the singleton ESM loader. - * @returns {boolean} - */ -function shouldSpawnLoaderHookWorker() { - return _shouldSpawnLoaderHookWorker; -} - const SourceTextModuleTypes = { kInternal: 'internal', // TODO(joyeecheung): support internal ESM. kEmbedder: 'embedder', // Embedder ESM, also used by SEA @@ -391,12 +378,11 @@ function compileSourceTextModule(url, source, type, context = kEmptyObject) { const kImportInImportedESM = Symbol('kImportInImportedESM'); const kImportInRequiredESM = Symbol('kImportInRequiredESM'); -const kRequireInImportedCJS = Symbol('kRequireInImportedCJS'); /** - * @typedef {kImportInImportedESM | kImportInRequiredESM | kRequireInImportedCJS} ModuleRequestType + * @typedef {kImportInImportedESM | kImportInRequiredESM} ModuleRequestType */ -const requestTypes = { kImportInImportedESM, kImportInRequiredESM, kRequireInImportedCJS }; +const requestTypes = { kImportInImportedESM, kImportInRequiredESM }; module.exports = { embedder_module_hdo, @@ -404,7 +390,6 @@ module.exports = { initializeESM, getDefaultConditions, getConditionsSet, - shouldSpawnLoaderHookWorker, compileSourceTextModule, SourceTextModuleTypes, requestTypes, diff --git a/lib/internal/modules/esm/worker.js b/lib/internal/modules/esm/worker.js deleted file mode 100644 index 7d023059a4b0..000000000000 --- a/lib/internal/modules/esm/worker.js +++ /dev/null @@ -1,324 +0,0 @@ -'use strict'; - -const { - AtomicsAdd, - AtomicsNotify, - DataViewPrototypeGetBuffer, - Int32Array, - PromisePrototypeThen, - ReflectApply, - SafeSet, - TypedArrayPrototypeGetBuffer, -} = primordials; -const assert = require('internal/assert'); -const { clearImmediate, setImmediate } = require('timers'); -const { - hasUncaughtExceptionCaptureCallback, -} = require('internal/process/execution'); -const { - isArrayBuffer, - isDataView, - isTypedArray, -} = require('util/types'); - -const { getOptionValue } = require('internal/options'); -const { - loadPreloadModules, - initializeModuleLoaders, - initializeFrozenIntrinsics, -} = require('internal/process/pre_execution'); -const { receiveMessageOnPort } = require('internal/worker/io'); -const { - WORKER_TO_MAIN_THREAD_NOTIFICATION, -} = require('internal/modules/esm/shared_constants'); -const { isMarkedAsUntransferable } = require('internal/buffer'); -const { getCWDURL } = require('internal/util'); -const { isCascadedLoaderInitialized, getOrInitializeCascadedLoader } = require('internal/modules/esm/loader'); -const { AsyncLoaderHooksOnLoaderHookWorker } = require('internal/modules/esm/hooks'); -const { enableWorkerCondition } = require('internal/modules/helpers'); - -/** - * Register asynchronous module loader customization hooks. This should only be run in the loader - * hooks worker. In a non-loader-hooks thread, if any asynchronous loader hook is registered, the - * ModuleLoader#asyncLoaderHooks are initialized to be AsyncLoaderHooksProxiedToLoaderHookWorker - * which posts the messages to the async loader hook worker thread. - * When no asynchronous loader hook is registered, the loader hook worker is not spawned and module - * loading is entiredly done in-thread. - * @returns {Promise} - */ -async function initializeAsyncLoaderHooksOnLoaderHookWorker() { - const customLoaderURLs = getOptionValue('--experimental-loader'); - - // The worker thread spawned for handling asynchronous loader hooks should not - // further spawn other hook threads or there will be an infinite recursion. - const shouldSpawnLoaderHookWorker = false; - // The worker thread for async loader hooks will preload user modules itself in - // initializeAsyncLoaderHooksOnLoaderHookWorker(). - const shouldPreloadModules = false; - initializeModuleLoaders({ shouldSpawnLoaderHookWorker, shouldPreloadModules }); - - assert(!isCascadedLoaderInitialized(), - 'ModuleLoader should be initialized in initializeAsyncLoaderHooksOnLoaderHookWorker()'); - const asyncLoaderHooks = new AsyncLoaderHooksOnLoaderHookWorker(); - getOrInitializeCascadedLoader(asyncLoaderHooks); - - // We need the async loader hooks to be set _before_ we start invoking - // `--require`, otherwise loops can happen because a `--require` script - // might call `register(...)` before we've installed ourselves. These - // global values are magically set in `initializeModuleLoaders` just for us and - // we call them in the correct order. - // N.B. This block appears here specifically in order to ensure that - // `--require` calls occur before `--loader` ones do. - loadPreloadModules(); - initializeFrozenIntrinsics(); - - const parentURL = getCWDURL().href; - for (let i = 0; i < customLoaderURLs.length; i++) { - await asyncLoaderHooks.register(customLoaderURLs[i], parentURL); - } - - return asyncLoaderHooks; -} - -/** - * Transfers an ArrayBuffer, TypedArray, or DataView to a worker thread. - * @param {boolean} hasError - Whether an error occurred during transfer. - * @param {ArrayBuffer[] | TypedArray | DataView} source - The data to transfer. - * @returns {ArrayBuffer[]|undefined} - */ -function transferArrayBuffer(hasError, source) { - if (hasError || source == null) { return; } - let arrayBuffer; - if (isArrayBuffer(source)) { - arrayBuffer = source; - } else if (isTypedArray(source)) { - arrayBuffer = TypedArrayPrototypeGetBuffer(source); - } else if (isDataView(source)) { - arrayBuffer = DataViewPrototypeGetBuffer(source); - } - if (arrayBuffer && !isMarkedAsUntransferable(arrayBuffer)) { - return [arrayBuffer]; - } -} - -/** - * Wraps a message with a status and body, and serializes the body if necessary. - * @param {string} status - The status of the message. - * @param {any} body - The body of the message. - * @returns {{status: string, body: any}} - */ -function wrapMessage(status, body) { - if (status === 'success' || body === null || - (typeof body !== 'object' && - typeof body !== 'function' && - typeof body !== 'symbol')) { - return { status, body }; - } - - let serialized; - let serializationFailed; - try { - const { serializeError } = require('internal/error_serdes'); - serialized = serializeError(body); - } catch { - serializationFailed = true; - } - - return { - status, - body: { - serialized, - serializationFailed, - }, - }; -} - -/** - * Initializes the loader hooks worker thread with customized asynchronous module loading hooks. - * @param {SharedArrayBuffer} lock - The lock used to synchronize communication between the worker and the main thread. - * @param {MessagePort} syncCommPort - The message port used for synchronous communication between the worker and the - * main thread. - * @param {(err: Error, origin?: string) => void} errorHandler - The function to use for uncaught exceptions. - * @returns {Promise} A promise that resolves when the worker thread has been initialized. - */ -async function customizedModuleWorker(lock, syncCommPort, errorHandler) { - let asyncLoaderHooks; - let initializationError; - let hasInitializationError = false; - - { - // If a custom hook is calling `process.exit`, we should wake up the main thread - // so it can detect the exit event. - const { exit } = process; - process.exit = function(code) { - syncCommPort.postMessage(wrapMessage('exit', code ?? process.exitCode)); - AtomicsAdd(lock, WORKER_TO_MAIN_THREAD_NOTIFICATION, 1); - AtomicsNotify(lock, WORKER_TO_MAIN_THREAD_NOTIFICATION); - return ReflectApply(exit, this, arguments); - }; - } - - try { - asyncLoaderHooks = await initializeAsyncLoaderHooksOnLoaderHookWorker(); - } catch (exception) { - // If there was an error while parsing and executing a user loader, for example if because a - // loader contained a syntax error, then we need to send the error to the main thread so it can - // be thrown and printed. - hasInitializationError = true; - initializationError = exception; - } - - syncCommPort.on('message', handleMessage); - - if (hasInitializationError) { - syncCommPort.postMessage(wrapMessage('error', initializationError)); - } else { - syncCommPort.postMessage(wrapMessage('success')); - } - - // We're ready, so unlock the main thread. - AtomicsAdd(lock, WORKER_TO_MAIN_THREAD_NOTIFICATION, 1); - AtomicsNotify(lock, WORKER_TO_MAIN_THREAD_NOTIFICATION); - - let immediate; - /** - * Checks for messages on the syncCommPort and handles them asynchronously. - */ - function checkForMessages() { - immediate = setImmediate(checkForMessages).unref(); - // We need to let the event loop tick a few times to give the main thread a chance to send - // follow-up messages. - const response = receiveMessageOnPort(syncCommPort); - - if (response !== undefined) { - PromisePrototypeThen(handleMessage(response.message), undefined, errorHandler); - } - } - - const unsettledResponsePorts = new SafeSet(); - - process.on('beforeExit', () => { - for (const port of unsettledResponsePorts) { - port.postMessage(wrapMessage('never-settle')); - } - unsettledResponsePorts.clear(); - - AtomicsAdd(lock, WORKER_TO_MAIN_THREAD_NOTIFICATION, 1); - AtomicsNotify(lock, WORKER_TO_MAIN_THREAD_NOTIFICATION); - - // Attach back the event handler. - syncCommPort.on('message', handleMessage); - // Also check synchronously for a message, in case it's already there. - clearImmediate(immediate); - checkForMessages(); - // We don't need the sync check after this tick, as we already have added the event handler. - clearImmediate(immediate); - // Add some work for next tick so the worker cannot exit. - setImmediate(() => {}); - }); - - /** - * Handles incoming messages from the main thread or other workers. - * @param {object} options - The options object. - * @param {string} options.method - The name of the hook. - * @param {Array} options.args - The arguments to pass to the method. - * @param {MessagePort} options.port - The message port to use for communication. - */ - async function handleMessage({ method, args, port }) { - // Each potential exception needs to be caught individually so that the correct error is sent to - // the main thread. - let hasError = false; - let shouldRemoveGlobalErrorHandler = false; - assert(typeof asyncLoaderHooks[method] === 'function', `${method} is not implemented in the loader worker`); - if (port == null && !hasUncaughtExceptionCaptureCallback()) { - // When receiving sync messages, we want to unlock the main thread when there's an exception. - process.on('uncaughtException', errorHandler); - shouldRemoveGlobalErrorHandler = true; - } - - // We are about to yield the execution with `await ReflectApply` below. In case the code - // following the `await` never runs, we remove the message handler so the `beforeExit` event - // can be triggered. - syncCommPort.off('message', handleMessage); - - // We keep checking for new messages to not miss any. - clearImmediate(immediate); - immediate = setImmediate(checkForMessages).unref(); - - unsettledResponsePorts.add(port ?? syncCommPort); - - let response; - try { - response = await ReflectApply(asyncLoaderHooks[method], asyncLoaderHooks, args); - } catch (exception) { - hasError = true; - response = exception; - } - - unsettledResponsePorts.delete(port ?? syncCommPort); - - // Send the method response (or exception) to the main thread. - try { - (port ?? syncCommPort).postMessage( - wrapMessage(hasError ? 'error' : 'success', response), - transferArrayBuffer(hasError, response?.source), - ); - } catch (exception) { - // Or send the exception thrown when trying to send the response. - (port ?? syncCommPort).postMessage(wrapMessage('error', exception)); - } - - if (shouldRemoveGlobalErrorHandler) { - process.off('uncaughtException', errorHandler); - } - - syncCommPort.off('message', handleMessage); - // We keep checking for new messages to not miss any. - clearImmediate(immediate); - immediate = setImmediate(checkForMessages).unref(); - // To prevent the main thread from terminating before this function completes after unlocking, - // the following process is executed at the end of the function. - AtomicsAdd(lock, WORKER_TO_MAIN_THREAD_NOTIFICATION, 1); - AtomicsNotify(lock, WORKER_TO_MAIN_THREAD_NOTIFICATION); - } -} - -/** - * Initializes a worker thread for a module with customized hooks. - * ! Run everything possible within this function so errors get reported. - * @param {{lock: SharedArrayBuffer, workerCondition: boolean}} workerData - The lock used to - * synchronize with the main thread, and whether the spawning thread is a Web Worker. - * @param {MessagePort} syncCommPort - The communication port used to communicate with the main thread. - * @returns {object} - */ -module.exports = function setupModuleWorker(workerData, syncCommPort) { - const lock = new Int32Array(workerData.lock); - if (workerData.workerCondition) { - enableWorkerCondition(); - } - - /** - * Handles errors that occur in the worker thread. - * @param {Error} err - The error that occurred. - * @param {string} [origin] - The origin of the error. - */ - function errorHandler(err, origin = 'unhandledRejection') { - AtomicsAdd(lock, WORKER_TO_MAIN_THREAD_NOTIFICATION, 1); - AtomicsNotify(lock, WORKER_TO_MAIN_THREAD_NOTIFICATION); - process.off('uncaughtException', errorHandler); - if (hasUncaughtExceptionCaptureCallback()) { - process._fatalException(err); - return; - } - internalBinding('errors').triggerUncaughtException( - err, - origin === 'unhandledRejection', - ); - } - - return PromisePrototypeThen( - customizedModuleWorker(lock, syncCommPort, errorHandler), - undefined, - errorHandler, - ); -}; diff --git a/lib/internal/modules/package_json_reader.js b/lib/internal/modules/package_json_reader.js index 2ef5d1ebed39..f5fa643ac6c3 100644 --- a/lib/internal/modules/package_json_reader.js +++ b/lib/internal/modules/package_json_reader.js @@ -412,11 +412,9 @@ function findPackageJSON(specifier, base = 'data:') { defaultResolve ??= require('internal/modules/esm/resolve').defaultResolve; try { - // TODO(@JakobJingleheimer): Detect whether findPackageJSON is being used within a loader - // (possibly piggyback on `isForAsyncLoaderHookWorker` from the loader?) and if so: - // - When inside, use the default resolve - // - (I think it's impossible to use the chain because of re-entry & a deadlock from atomics). - // - When outside, use cascadedLoader.resolveSync (not implemented yet, but the pieces exist). + // TODO(@JakobJingleheimer): This always uses the default resolver and so ignores any + // module.registerHooks() resolve hooks. Using cascadedLoader.resolveSync would apply them, + // but that needs to handle re-entry when findPackageJSON is called from inside a hook. resolvedTarget = defaultResolve(specifier, { parentURL: `${parentURL}` }).url; } catch (err) { if (err.code === 'ERR_UNSUPPORTED_DIR_IMPORT') { diff --git a/lib/internal/modules/run_main.js b/lib/internal/modules/run_main.js index b0d7d4845eaf..ec97f46f5a0f 100644 --- a/lib/internal/modules/run_main.js +++ b/lib/internal/modules/run_main.js @@ -51,17 +51,12 @@ function resolveMainPath(main) { * @returns {boolean} */ function shouldUseESMLoader(mainPath) { - /** - * @type {string[]} userLoaders A list of custom loaders registered by the user - * (or an empty list when none have been registered). - */ - const userLoaders = getOptionValue('--experimental-loader'); /** * @type {string[]} userImports A list of preloaded modules registered by the user * (or an empty list when none have been registered). */ const userImports = getOptionValue('--import'); - if (userLoaders.length > 0 || userImports.length > 0) { return true; } + if (userImports.length > 0) { return true; } // Determine the module format of the entry point. if (mainPath && StringPrototypeEndsWith(mainPath, '.mjs')) { return true; } @@ -96,8 +91,6 @@ async function asyncRunEntryPointWithESMLoader(callback) { for (let i = 0; i < userImports.length; i++) { await cascadedLoader.import(userImports[i], parentURL, kEmptyObject); } - } else { - cascadedLoader.waitForAsyncLoaderHookInitialization(); } // Any --import above may have registered a VFS provider, so mount now (no-op // if prepareExecution already mounted in the no-import case). @@ -175,11 +168,10 @@ function executeUserEntryPoint(main = process.argv[1]) { const mainPath = resolvedMain || main; const mainURL = getOptionValue('--entry-url') ? new URL(mainPath, getCWDURL()) : pathToFileURL(mainPath); - // When no async loader hooks or --import are registered, load the entry point + // When no --import preloads are registered, load the entry point // directly. The graph is loaded and linked synchronously, and evaluated // synchronously too unless it contains top-level await. - if (getOptionValue('--experimental-loader').length === 0 && - getOptionValue('--import').length === 0) { + if (getOptionValue('--import').length === 0) { const cascadedLoader = require('internal/modules/esm/loader').getOrInitializeCascadedLoader(); const job = cascadedLoader.importForEntryPoint(mainURL.href); if (job === undefined) { diff --git a/lib/internal/process/pre_execution.js b/lib/internal/process/pre_execution.js index 0f5aaf857aeb..b5933f4e9d61 100644 --- a/lib/internal/process/pre_execution.js +++ b/lib/internal/process/pre_execution.js @@ -51,7 +51,6 @@ function prepareMainThreadExecution(expandArgv1 = false, initializeModules = tru expandArgv1, initializeModules, isMainThread: true, - shouldSpawnLoaderHookWorker: initializeModules, shouldPreloadModules: initializeModules, }); } @@ -61,7 +60,6 @@ function prepareTestRunnerMainExecution(loadUserModules = true) { expandArgv1: false, initializeModules: true, isMainThread: true, - shouldSpawnLoaderHookWorker: loadUserModules, shouldPreloadModules: loadUserModules, }); } @@ -73,7 +71,6 @@ function prepareWorkerThreadExecution() { // Module loader initialization in workers are delayed until the worker thread // is ready for execution. initializeModules: false, - shouldSpawnLoaderHookWorker: false, shouldPreloadModules: false, }); } @@ -84,8 +81,8 @@ function prepareShadowRealmExecution() { patchProcessObject(false); setupDebugEnv(); - // Disable custom loaders in ShadowRealm. - initializeModuleLoaders({ shouldSpawnLoaderHookWorker: false, shouldPreloadModules: false }); + // Preload modules are not supported in ShadowRealm. + initializeModuleLoaders({ shouldPreloadModules: false }); const { privateSymbols: { host_defined_option_symbol, @@ -107,7 +104,7 @@ function prepareShadowRealmExecution() { } function prepareExecution(options) { - const { expandArgv1, initializeModules, isMainThread, shouldSpawnLoaderHookWorker, shouldPreloadModules } = options; + const { expandArgv1, initializeModules, isMainThread, shouldPreloadModules } = options; refreshRuntimeOptions(); @@ -172,7 +169,7 @@ function prepareExecution(options) { setupVmModules(); if (initializeModules) { - initializeModuleLoaders({ shouldSpawnLoaderHookWorker, shouldPreloadModules }); + initializeModuleLoaders({ shouldPreloadModules }); } // Mount here only when there is no --import: those preloads run later (inside @@ -319,15 +316,13 @@ function warnAboutRemovedProxyEnvVars() { } function initializeModuleLoaders(options) { - const { shouldSpawnLoaderHookWorker, shouldPreloadModules } = options; + const { shouldPreloadModules } = options; // Initialize certain special module.Module properties and the CJS conditions. const { initializeCJS } = require('internal/modules/cjs/loader'); initializeCJS(); // Initialize the ESM loader and a few module callbacks. - // If shouldSpawnLoaderHookWorker is true, later when the ESM loader is instantiated on-demand, - // it will spawn a loader worker thread to handle async custom loader hooks. const { initializeESM } = require('internal/modules/esm/utils'); - initializeESM(shouldSpawnLoaderHookWorker); + initializeESM(); const { hasStartedUserCJSExecution, @@ -340,9 +335,7 @@ function initializeModuleLoaders(options) { if (getEmbedderOptions().hasEmbedderPreload) { runEmbedderPreload(); } - // Do not enable preload modules if custom loaders are disabled. - // For example, loader workers are responsible for doing this themselves. - // And preload modules are not supported in ShadowRealm as well. + // Preload modules are not supported in ShadowRealm. if (shouldPreloadModules) { loadPreloadModules(); } diff --git a/lib/internal/test_runner/mock/loader.js b/lib/internal/test_runner/mock/loader.js index d64ffdfa0965..266525c8d52a 100644 --- a/lib/internal/test_runner/mock/loader.js +++ b/lib/internal/test_runner/mock/loader.js @@ -60,12 +60,11 @@ function load(url, context, nextLoad) { // Treat builtins as commonjs because customization hooks do not allow a // core module to be replaced. - // Also collapse 'commonjs-sync' and 'require-commonjs' to 'commonjs'. + // Also collapse 'commonjs-sync' to 'commonjs'. let format = original.format; switch (original.format) { case 'builtin': // Deliberate fallthrough - case 'commonjs-sync': // Deliberate fallthrough - case 'require-commonjs': + case 'commonjs-sync': format = 'commonjs'; break; case 'json': diff --git a/lib/internal/worker.js b/lib/internal/worker.js index 728132f779e3..00b0293e85dc 100644 --- a/lib/internal/worker.js +++ b/lib/internal/worker.js @@ -44,7 +44,6 @@ const { const workerIo = require('internal/worker/io'); const { drainMessagePort, - receiveMessageOnPort, MessageChannel, messageTypes, kPort, @@ -73,7 +72,6 @@ const { const { ownsProcessState, isMainThread, - isInternalThread, resourceLimits: resourceLimitsRaw, threadId, threadName, @@ -94,7 +92,6 @@ const kOnCouldNotSerializeErr = Symbol('kOnCouldNotSerializeErr'); const kOnErrorMessage = Symbol('kOnErrorMessage'); const kParentSideStdio = Symbol('kParentSideStdio'); const kLoopStartTime = Symbol('kLoopStartTime'); -const kIsInternal = Symbol('kIsInternal'); const kIsOnline = Symbol('kIsOnline'); const kWebWorkerData = Symbol('kWebWorkerData'); @@ -203,13 +200,7 @@ class Worker extends EventEmitter { constructor(filename, options = kEmptyObject) { throwIfBuildingSnapshot('Creating workers'); super(); - const isInternal = arguments[2] === kIsInternal; - debug( - `[${threadId}] create new worker`, - filename, - options, - `isInternal: ${isInternal}`, - ); + debug(`[${threadId}] create new worker`, filename, options); if (options.execArgv) validateArray(options.execArgv, 'options.execArgv'); @@ -220,10 +211,7 @@ class Worker extends EventEmitter { } let url, doEval; - if (isInternal) { - doEval = 'internal'; - url = `node:${filename}`; - } else if (options.eval) { + if (options.eval) { if (typeof filename !== 'string') { throw new ERR_INVALID_ARG_VALUE( 'options.eval', @@ -286,7 +274,6 @@ class Worker extends EventEmitter { options.execArgv, parseResourceLimits(options.resourceLimits), !!(options.trackUnmanagedFds ?? true), - isInternal, name); if (this[kHandle].invalidExecArgv) { throw new ERR_WORKER_INVALID_EXEC_ARGV(this[kHandle].invalidExecArgv); @@ -361,7 +348,6 @@ class Worker extends EventEmitter { type: messageTypes.LOAD_SCRIPT, filename, doEval, - isInternal, cwdCounter: cwdCounter || workerIo.sharedCwdCounter, workerData: options.workerData, webWorkerData: options[kWebWorkerData], @@ -642,20 +628,6 @@ class Worker extends EventEmitter { } } -/** - * A worker which has an internal module for entry point (e.g. internal/module/esm/worker). - * Internal workers bypass the permission model. - */ -class InternalWorker extends Worker { - constructor(filename, options) { - super(filename, options, kIsInternal); - } - - receiveMessageSync() { - return receiveMessageOnPort(this[kPublicPort]); - } -} - function pipeWithoutWarning(source, dest) { const sourceMaxListeners = source._maxListeners; const destMaxListeners = dest._maxListeners; @@ -723,7 +695,6 @@ module.exports = { ownsProcessState, kIsOnline, isMainThread, - isInternalThread, SHARE_ENV, resourceLimits: !isMainThread ? makeResourceLimits(resourceLimitsRaw) : {}, @@ -732,7 +703,6 @@ module.exports = { assignEnvironmentData, threadId, threadName, - InternalWorker, Worker, kPublicPort, kWebWorkerData, diff --git a/lib/module.js b/lib/module.js index 1217172afb3c..60ba0803dc55 100644 --- a/lib/module.js +++ b/lib/module.js @@ -6,7 +6,6 @@ const { setSourceMapsSupport, } = require('internal/source_map/source_map_cache'); const { Module } = require('internal/modules/cjs/loader'); -const { register } = require('internal/modules/esm/loader'); const { SourceMap, } = require('internal/source_map/source_map'); @@ -21,7 +20,6 @@ const { } = require('internal/modules/package_json_reader'); const { stripTypeScriptTypes } = require('internal/modules/typescript'); -Module.register = register; Module.constants = constants; Module.enableCompileCache = enableCompileCache; Module.findPackageJSON = findPackageJSON; diff --git a/lib/worker_threads.js b/lib/worker_threads.js index 622bef3f55c1..b6a0887784d1 100644 --- a/lib/worker_threads.js +++ b/lib/worker_threads.js @@ -3,7 +3,6 @@ const { defineLazyProperties } = require('internal/util'); const { - isInternalThread, isMainThread, SHARE_ENV, resourceLimits, @@ -33,7 +32,7 @@ const { } = require('internal/buffer'); module.exports = { - isInternalThread, + isInternalThread: false, isMainThread, MessagePort, MessageChannel, diff --git a/src/module_wrap.cc b/src/module_wrap.cc index 9892f38d96bd..444d31337531 100644 --- a/src/module_wrap.cc +++ b/src/module_wrap.cc @@ -1362,7 +1362,7 @@ static Maybe DefaultImportMetaObjectInitializer(Realm* realm, } // Set a lazy getter of import.meta.resolve - only if the initializer is set, - // which is only the case when run on a non-loader-hook thread. + // which happens once the cascaded loader has been initialized. Local import_meta_resolve_initializer = realm->host_import_meta_resolve_initializer(); if (!import_meta_resolve_initializer.IsEmpty() && diff --git a/src/node_options.cc b/src/node_options.cc index 4ba4801ad1dc..3f86cce88595 100644 --- a/src/node_options.cc +++ b/src/node_options.cc @@ -854,11 +854,6 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() { true); AddOption("--experimental-global-webcrypto", "", NoOp{}, kAllowedInEnvvar); AddOption("--experimental-json-modules", "", NoOp{}, kAllowedInEnvvar); - AddOption("--experimental-loader", - "use the specified module as a custom loader", - &EnvironmentOptions::userland_loaders, - kAllowedInEnvvar); - AddAlias("--loader", "--experimental-loader"); AddOption("--experimental-modules", "", NoOp{}, kAllowedInEnvvar); AddOption("--experimental-wasm-modules", "", NoOp{}, kAllowedInEnvvar); AddOption("--experimental-import-text", diff --git a/src/node_options.h b/src/node_options.h index f8aa58253862..eb8255f70e26 100644 --- a/src/node_options.h +++ b/src/node_options.h @@ -99,11 +99,6 @@ class DebugOptions : public Options { break_first_line = true; } - void DisableWaitOrBreakFirstLine() { - inspect_wait = false; - break_first_line = false; - } - bool wait_for_connect() const { return break_first_line || break_node_first_line || inspect_wait; } @@ -176,7 +171,6 @@ class EnvironmentOptions : public Options { std::vector experimental_test_tag_filter; std::vector coverage_include_pattern; std::vector coverage_exclude_pattern; - std::vector userland_loaders; std::vector watch_mode_paths; std::vector preload_cjs_modules; std::vector preload_esm_modules; diff --git a/src/node_watchdog.cc b/src/node_watchdog.cc index c403f8cb0715..0dca0085e0d3 100644 --- a/src/node_watchdog.cc +++ b/src/node_watchdog.cc @@ -305,7 +305,7 @@ std::string FormatProcessTimeoutDiagnostics(Environment* env) { // Running Workers keep the event loop alive through the Environment's own // task queue handle rather than through a HandleWrap. env->ForEachWorker([&](worker::Worker* worker) { - if (worker->is_internal() || !worker->has_ref() || worker->is_stopped()) { + if (!worker->has_ref() || worker->is_stopped()) { return; } std::string details = SPrintF("thread %d", worker->thread_id()); diff --git a/src/node_worker.cc b/src/node_worker.cc index 308d987680d7..9c8f215e094e 100644 --- a/src/node_worker.cc +++ b/src/node_worker.cc @@ -64,8 +64,7 @@ Worker::Worker(Environment* env, std::shared_ptr per_isolate_opts, std::vector&& exec_argv, std::shared_ptr env_vars, - const SnapshotData* snapshot_data, - const bool is_internal) + const SnapshotData* snapshot_data) : AsyncWrap(env, wrap, AsyncWrap::PROVIDER_WORKER), per_isolate_opts_(per_isolate_opts), exec_argv_(exec_argv), @@ -74,8 +73,7 @@ Worker::Worker(Environment* env, name_(name), env_vars_(env_vars), embedder_preload_(env->embedder_preload()), - snapshot_data_(snapshot_data), - is_internal_(is_internal) { + snapshot_data_(snapshot_data) { Debug(this, "Creating new worker instance with thread id %llu", thread_id_.id); @@ -524,7 +522,6 @@ void Worker::New(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); THROW_IF_INSUFFICIENT_PERMISSIONS( env, permission::PermissionScope::kWorkerThreads, ""); - bool is_internal = args[5]->IsTrue(); Isolate* isolate = args.GetIsolate(); CHECK(args.IsConstructCall()); @@ -549,9 +546,9 @@ void Worker::New(const FunctionCallbackInfo& args) { url.append(value.out(), value.length()); } - if (!args[6]->IsNullOrUndefined()) { + if (!args[5]->IsNullOrUndefined()) { Utf8Value value( - isolate, args[6]->ToString(env->context()).FromMaybe(Local())); + isolate, args[5]->ToString(env->context()).FromMaybe(Local())); name.append(value.out(), value.length()); } @@ -709,16 +706,6 @@ void Worker::New(const FunctionCallbackInfo& args) { // is inherited but the load behavior must not be. per_isolate_opts->per_env->vfs_load = false; - // Internal workers should not wait for inspector frontend to connect or - // break on the first line of internal scripts. Module loader threads are - // essential to load user codes and must not be blocked by the inspector - // for internal scripts. - // Still, `--inspect-node` can break on the first line of internal scripts. - if (is_internal) { - per_isolate_opts->per_env->get_debug_options() - ->DisableWaitOrBreakFirstLine(); - } - const SnapshotData* snapshot_data = env->isolate_data()->snapshot_data(); Worker* worker = new Worker(env, @@ -728,8 +715,7 @@ void Worker::New(const FunctionCallbackInfo& args) { per_isolate_opts, std::move(exec_argv_out), env_vars, - snapshot_data, - is_internal); + snapshot_data); CHECK(args[3]->IsFloat64Array()); Local limit_info = args[3].As(); @@ -1474,13 +1460,6 @@ void IsMainThreadGetter(Local, info.GetReturnValue().Set(Environment::GetCurrent(info)->is_main_thread()); } -void IsInternalThreadGetter(Local, - const v8::PropertyCallbackInfo& info) { - Worker* worker = - Environment::GetCurrent(info)->isolate_data()->worker_context(); - info.GetReturnValue().Set(worker != nullptr && worker->is_internal()); -} - void OwnsProcessStateGetter(Local, const v8::PropertyCallbackInfo& info) { info.GetReturnValue().Set( @@ -1506,7 +1485,6 @@ void CreateWorkerPerIsolateProperties(IsolateData* isolate_data, lazy("threadId", ThreadIdGetter); lazy("threadName", ThreadNameGetter); lazy("isMainThread", IsMainThreadGetter); - lazy("isInternalThread", IsInternalThreadGetter); lazy("ownsProcessState", OwnsProcessStateGetter); lazy("resourceLimits", ResourceLimitsGetter); } @@ -1615,7 +1593,7 @@ void CreateWorkerPerContextProperties(Local target, Local unused, Local context, void* priv) { - // threadId, threadName, isMainThread, isInternalThread, ownsProcessState + // threadId, threadName, isMainThread, ownsProcessState // and resourceLimits are lazy properties of the per-isolate template (see // CreateWorkerPerIsolateProperties). NODE_DEFINE_CONSTANT(target, kMaxYoungGenerationSizeMb); @@ -1630,7 +1608,6 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) { registry->Register(ThreadIdGetter); registry->Register(ThreadNameGetter); registry->Register(IsMainThreadGetter); - registry->Register(IsInternalThreadGetter); registry->Register(OwnsProcessStateGetter); registry->Register(ResourceLimitsGetter); registry->Register(Worker::New); diff --git a/src/node_worker.h b/src/node_worker.h index 03e546534c1b..4261db0c18fc 100644 --- a/src/node_worker.h +++ b/src/node_worker.h @@ -36,8 +36,7 @@ class Worker : public AsyncWrap { std::shared_ptr per_isolate_opts, std::vector&& exec_argv, std::shared_ptr env_vars, - const SnapshotData* snapshot_data, - const bool is_internal); + const SnapshotData* snapshot_data); ~Worker() override; // Run the worker. This is only called from the worker thread. @@ -64,7 +63,6 @@ class Worker : public AsyncWrap { bool is_stopped() const; const SnapshotData* snapshot_data() const { return snapshot_data_; } - bool is_internal() const { return is_internal_; } std::string_view name() const { return name_; } uint64_t thread_id() const { return thread_id_.id; } // Whether the Worker keeps the parent's event loop alive, see @@ -146,7 +144,6 @@ class Worker : public AsyncWrap { Environment* env_ = nullptr; const SnapshotData* snapshot_data_ = nullptr; - const bool is_internal_; friend class WorkerThreadData; }; diff --git a/test/doctool/test-doc-api-json.mjs b/test/doctool/test-doc-api-json.mjs index ba0469da50a1..104ef026c762 100644 --- a/test/doctool/test-doc-api-json.mjs +++ b/test/doctool/test-doc-api-json.mjs @@ -158,5 +158,5 @@ for await (const dirent of await fs.opendir(new URL('../../out/doc/api/', import assert.partialDeepStrictEqual(allExpectedKeys, findAllKeys(json)); } -assert.strictEqual(numberOfDeprecatedSections, 48); // Increase this number every time a new API is deprecated. -assert.strictEqual(numberOfRemovedAPIs, 47); // Increase this number every time a section is marked as removed. +assert.strictEqual(numberOfDeprecatedSections, 47); // Increase this number every time a new API is deprecated. +assert.strictEqual(numberOfRemovedAPIs, 49); // Increase this number every time a section is marked as removed. diff --git a/test/es-module/test-esm-example-loader.mjs b/test/es-module/test-esm-example-loader.mjs deleted file mode 100644 index b85de4e64c4a..000000000000 --- a/test/es-module/test-esm-example-loader.mjs +++ /dev/null @@ -1,6 +0,0 @@ -// Flags: --experimental-loader ./test/fixtures/es-module-loaders/example-loader.mjs -/* eslint-disable node-core/require-common-first, node-core/required-modules */ -import assert from 'assert'; -import ok from '../fixtures/es-modules/test-esm-ok.mjs'; - -assert(ok); diff --git a/test/es-module/test-esm-experimental-warnings.mjs b/test/es-module/test-esm-experimental-warnings.mjs deleted file mode 100644 index 7b3ec4b942ee..000000000000 --- a/test/es-module/test-esm-experimental-warnings.mjs +++ /dev/null @@ -1,50 +0,0 @@ -import { spawnPromisified } from '../common/index.mjs'; -import * as fixtures from '../common/fixtures.mjs'; -import assert from 'node:assert'; -import { execPath } from 'node:process'; -import { describe, it } from 'node:test'; - - -describe('ESM: warn for obsolete hooks provided', { concurrency: !process.env.TEST_PARALLEL }, () => { - it('should not print warnings when no experimental features are enabled or used', async () => { - const { code, signal, stderr } = await spawnPromisified(execPath, [ - '--input-type=module', - '--eval', - `import ${JSON.stringify(fixtures.fileURL('es-module-loaders', 'module-named-exports.mjs'))}`, - ]); - - assert.doesNotMatch( - stderr, - /ExperimentalWarning/, - new Error('No experimental warning(s) should be emitted when no experimental feature is enabled') - ); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - - describe('experimental warnings for enabled experimental feature', () => { - for ( - const [experiment, ...args] of [ - [ - /`--experimental-loader` may be removed in the future/, - '--experimental-loader', - fixtures.fileURL('es-module-loaders', 'hooks-custom.mjs'), - ], - ] - ) { - it(`should print for ${experiment.toString().replaceAll('/', '')}`, async () => { - const { code, signal, stderr } = await spawnPromisified(execPath, [ - ...args, - '--input-type=module', - '--eval', - `import ${JSON.stringify(fixtures.fileURL('es-module-loaders', 'module-named-exports.mjs'))}`, - ]); - - assert.match(stderr, /ExperimentalWarning/); - assert.match(stderr, experiment); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - } - }); -}); diff --git a/test/es-module/test-esm-import-assertion-warning.mjs b/test/es-module/test-esm-import-assertion-warning.mjs deleted file mode 100644 index 2f9b7348a2d9..000000000000 --- a/test/es-module/test-esm-import-assertion-warning.mjs +++ /dev/null @@ -1,42 +0,0 @@ -import { spawnPromisified } from '../common/index.mjs'; -import assert from 'node:assert'; -import { execPath } from 'node:process'; - -await Promise.all([ - // Using importAssertions in the resolve hook should warn but still work. - `data:text/javascript,export ${encodeURIComponent(function resolve() { - return { shortCircuit: true, url: 'data:application/json,1', importAssertions: { type: 'json' } }; - })}`, - // Using importAssertions on the context object of the resolve hook should warn but still work. - `data:text/javascript,export ${encodeURIComponent(function resolve(s, c, n) { - const type = c.importAssertions.type; - return { shortCircuit: true, url: 'data:application/json,1', importAttributes: { type: type ?? 'json' } }; - })}`, - // Setting importAssertions on the context object of the load hook should warn but still work. - `data:text/javascript,export ${encodeURIComponent(function load(u, c, n) { - c.importAssertions = { type: 'json' }; - return n('data:application/json,1', c); - })}`, - // Creating a new context object with importAssertions in the load hook should warn but still work. - `data:text/javascript,export ${encodeURIComponent(function load(u, c, n) { - return n('data:application/json,1', { importAssertions: { type: 'json' } }); - })}`, -].map(async (loaderURL) => { - const { stdout, stderr, code } = await spawnPromisified(execPath, [ - '--input-type=module', - '--eval', ` - import assert from 'node:assert'; - import { register } from 'node:module'; - - register(${JSON.stringify(loaderURL)}); - - assert.deepStrictEqual( - { ...await import('data:') }, - { default: 1 } - );`, - ]); - - assert.match(stderr, /Use `importAttributes` instead of `importAssertions`/); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 0); -})); diff --git a/test/es-module/test-esm-import-defers-evaluation.js b/test/es-module/test-esm-import-defers-evaluation.js new file mode 100644 index 000000000000..0679493fe961 --- /dev/null +++ b/test/es-module/test-esm-import-defers-evaluation.js @@ -0,0 +1,16 @@ +'use strict'; +// Tests that import() never evaluates the module before returning to its +// caller, even though loading and linking are synchronous. +const common = require('../common'); +const assert = require('node:assert'); + +const key = '__esmImportDefersEvaluation'; +const url = `data:text/javascript,globalThis.${key} = true; export default 1;`; + +const promise = import(url); +assert.strictEqual(globalThis[key], undefined); +promise.then(common.mustCall((namespace) => { + assert.strictEqual(globalThis[key], true); + assert.strictEqual(namespace.default, 1); + delete globalThis[key]; +})); diff --git a/test/es-module/test-esm-initialization.mjs b/test/es-module/test-esm-initialization.mjs deleted file mode 100644 index c86d3b439496..000000000000 --- a/test/es-module/test-esm-initialization.mjs +++ /dev/null @@ -1,30 +0,0 @@ -import { spawnPromisified } from '../common/index.mjs'; -import * as fixtures from '../common/fixtures.mjs'; -import assert from 'node:assert'; -import { execPath } from 'node:process'; -import { describe, it } from 'node:test'; - - -describe('ESM: ensure initialization happens only once', { concurrency: !process.env.TEST_PARALLEL }, () => { - it(async () => { - const { code, stderr, stdout } = await spawnPromisified(execPath, [ - '--experimental-import-meta-resolve', - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-passthru.mjs'), - '--no-warnings', - fixtures.path('es-modules', 'runmain.mjs'), - ]); - - assert.strictEqual(stderr, ''); - /** - * resolveHookRunCount = 2: - * 1. fixtures/…/runmain.mjs - * 2. node:module (imported by fixtures/…/runmain.mjs) - * 3. doesnt-matter.mjs (first import.meta.resolve call) - * 4. fixtures/…/runmain.mjs (entry point) - * 5. doesnt-matter.mjs (second import.meta.resolve call) - */ - assert.strictEqual(stdout.match(/resolve passthru/g)?.length, 5); - assert.strictEqual(code, 0); - }); -}); diff --git a/test/es-module/test-esm-loader-chaining.mjs b/test/es-module/test-esm-loader-chaining.mjs deleted file mode 100644 index 6c266b9da3d8..000000000000 --- a/test/es-module/test-esm-loader-chaining.mjs +++ /dev/null @@ -1,507 +0,0 @@ -import { spawnPromisified } from '../common/index.mjs'; -import * as fixtures from '../common/fixtures.mjs'; -import assert from 'node:assert'; -import { execPath } from 'node:process'; -import { describe, it } from 'node:test'; - -const setupArgs = [ - '--no-warnings', - '--input-type=module', - '--eval', -]; -const commonInput = 'import os from "node:os"; console.log(os)'; -const commonArgs = [ - ...setupArgs, - commonInput, -]; - -describe('ESM: loader chaining', { concurrency: !process.env.TEST_PARALLEL }, () => { - it('should load unadulterated source when there are no loaders', async () => { - const { code, stderr, stdout } = await spawnPromisified( - execPath, - [ - ...setupArgs, - 'import fs from "node:fs"; console.log(typeof fs?.constants?.F_OK )', - ], - { encoding: 'utf8' }, - ); - - assert.strictEqual(stderr, ''); - assert.match(stdout, /number/); // node:fs is an object - assert.strictEqual(code, 0); - }); - - it('should load properly different source when only load changes something', async () => { - const { code, stderr, stdout } = await spawnPromisified( - execPath, - [ - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-passthru.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-load-foo-or-42.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-load-passthru.mjs'), - ...commonArgs, - ], - { encoding: 'utf8' }, - ); - - assert.strictEqual(stderr, ''); - assert.match(stdout, /load passthru/); - assert.match(stdout, /resolve passthru/); - assert.match(stdout, /foo/); - assert.strictEqual(code, 0); - }); - - it('should result in proper output from multiple changes in resolve hooks', async () => { - const { code, stderr, stdout } = await spawnPromisified( - execPath, - [ - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-shortcircuit.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-foo.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-42.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-load-foo-or-42.mjs'), - ...commonArgs, - ], - { encoding: 'utf8' }, - ); - - assert.strictEqual(stderr, ''); - assert.match(stdout, /resolve 42/); // It did go thru resolve-42 - assert.match(stdout, /foo/); // LIFO, so resolve-foo won - assert.strictEqual(code, 0); - }); - - it('should respect modified context within resolve chain', async () => { - const { code, stderr, stdout } = await spawnPromisified( - execPath, - [ - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-shortcircuit.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-42.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-load-foo-or-42.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-load-receiving-modified-context.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-load-passing-modified-context.mjs'), - ...commonArgs, - ], - { encoding: 'utf8' }, - ); - - assert.strictEqual(stderr, ''); - assert.match(stdout, /bar/); - assert.strictEqual(code, 0); - }); - - it('should accept only the correct arguments', async () => { - const { stdout } = await spawnPromisified( - execPath, - [ - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-log-args.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-with-too-many-args.mjs'), - ...commonArgs, - ], - { encoding: 'utf8' }, - ); - - assert.match(stdout, /^resolve arg count: 3$/m); - assert.match(stdout, /specifier: 'node:os'/); - assert.match(stdout, /next: \[AsyncFunction: nextResolve\]/); - - assert.match(stdout, /^load arg count: 3$/m); - assert.match(stdout, /url: 'node:os'/); - assert.match(stdout, /next: \[AsyncFunction: nextLoad\]/); - }); - - it('should result in proper output from multiple changes in resolve hooks', async () => { - const { code, stderr, stdout } = await spawnPromisified( - execPath, - [ - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-shortcircuit.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-42.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-foo.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-load-foo-or-42.mjs'), - ...commonArgs, - ], - { encoding: 'utf8' }, - ); - - assert.strictEqual(stderr, ''); - assert.match(stdout, /resolve foo/); // It did go thru resolve-foo - assert.match(stdout, /42/); // LIFO, so resolve-42 won - assert.strictEqual(code, 0); - }); - - it('should provide the correct "next" fn when multiple calls to next within same loader', async () => { - const { code, stderr, stdout } = await spawnPromisified( - execPath, - [ - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-shortcircuit.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-foo.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-multiple-next-calls.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-load-foo-or-42.mjs'), - ...commonArgs, - ], - { encoding: 'utf8' }, - ); - - const countFoos = stdout.match(/resolve foo/g)?.length; - - assert.strictEqual(stderr, ''); - assert.strictEqual(countFoos, 2); - assert.strictEqual(code, 0); - }); - - it('should use the correct `name` for next\'s function', async () => { - const { code, stderr, stdout } = await spawnPromisified( - execPath, - [ - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-shortcircuit.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-42.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-load-foo-or-42.mjs'), - ...commonArgs, - ], - { encoding: 'utf8' }, - ); - - assert.strictEqual(stderr, ''); - assert.match(stdout, /next: nextResolve/); - assert.strictEqual(code, 0); - }); - - it('should throw for incomplete resolve chain, citing errant loader & hook', async () => { - const { code, stderr, stdout } = await spawnPromisified( - execPath, - [ - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-incomplete.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-passthru.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-load-foo-or-42.mjs'), - ...commonArgs, - ], - { encoding: 'utf8' }, - ); - assert.match(stdout, /resolve passthru/); - assert.match(stderr, /ERR_LOADER_CHAIN_INCOMPLETE/); - assert.match(stderr, /loader-resolve-incomplete\.mjs/); - assert.match(stderr, /'resolve'/); - assert.strictEqual(code, 1); - }); - - it('should NOT throw when nested resolve hook signaled a short circuit', async () => { - const { code, stderr, stdout } = await spawnPromisified( - execPath, - [ - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-shortcircuit.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-next-modified.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-load-foo-or-42.mjs'), - ...commonArgs, - ], - { encoding: 'utf8' }, - ); - - assert.strictEqual(stderr, ''); - assert.strictEqual(stdout.trim(), 'foo'); - assert.strictEqual(code, 0); - }); - - it('should NOT throw when nested load hook signaled a short circuit', async () => { - const { code, stderr, stdout } = await spawnPromisified( - execPath, - [ - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-shortcircuit.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-42.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-load-foo-or-42.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-load-next-modified.mjs'), - ...commonArgs, - ], - { encoding: 'utf8' }, - ); - - assert.strictEqual(stderr, ''); - assert.match(stdout, /421/); - assert.strictEqual(code, 0); - }); - - it('should allow loaders to influence subsequent loader resolutions', async () => { - const { code, stderr } = await spawnPromisified( - execPath, - [ - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-strip-xxx.mjs'), - '--loader', - 'xxx/loader-resolve-strip-yyy.mjs', - ...commonArgs, - ], - { encoding: 'utf8', cwd: fixtures.path('es-module-loaders') }, - ); - - assert.strictEqual(stderr, ''); - assert.strictEqual(code, 0); - }); - - it('should throw when the resolve chain is broken', async () => { - const { code, stderr, stdout } = await spawnPromisified( - execPath, - [ - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-passthru.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-incomplete.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-load-foo-or-42.mjs'), - ...commonArgs, - ], - { encoding: 'utf8' }, - ); - - assert.doesNotMatch(stdout, /resolve passthru/); - assert.match(stderr, /ERR_LOADER_CHAIN_INCOMPLETE/); - assert.match(stderr, /loader-resolve-incomplete\.mjs/); - assert.match(stderr, /'resolve'/); - assert.strictEqual(code, 1); - }); - - it('should throw for incomplete load chain, citing errant loader & hook', async () => { - const { code, stderr, stdout } = await spawnPromisified( - execPath, - [ - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-passthru.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-load-incomplete.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-load-passthru.mjs'), - ...commonArgs, - ], - { encoding: 'utf8' }, - ); - - assert.match(stdout, /load passthru/); - assert.match(stderr, /ERR_LOADER_CHAIN_INCOMPLETE/); - assert.match(stderr, /loader-load-incomplete\.mjs/); - assert.match(stderr, /'load'/); - assert.strictEqual(code, 1); - }); - - it('should throw when the load chain is broken', async () => { - const { code, stderr, stdout } = await spawnPromisified( - execPath, - [ - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-passthru.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-load-passthru.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-load-incomplete.mjs'), - ...commonArgs, - ], - { encoding: 'utf8' }, - ); - - assert.doesNotMatch(stdout, /load passthru/); - assert.match(stderr, /ERR_LOADER_CHAIN_INCOMPLETE/); - assert.match(stderr, /loader-load-incomplete\.mjs/); - assert.match(stderr, /'load'/); - assert.strictEqual(code, 1); - }); - - it('should throw when invalid `specifier` argument passed to `nextResolve`', async () => { - const { code, stderr } = await spawnPromisified( - execPath, - [ - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-passthru.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-bad-next-specifier.mjs'), - ...commonArgs, - ], - { encoding: 'utf8' }, - ); - - assert.strictEqual(code, 1); - assert.match(stderr, /ERR_INVALID_ARG_TYPE/); - assert.match(stderr, /loader-resolve-bad-next-specifier\.mjs/); - assert.match(stderr, /'resolve' hook's nextResolve\(\) specifier/); - }); - - it('should throw when resolve hook is invalid', async () => { - const { code, stderr } = await spawnPromisified( - execPath, - [ - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-passthru.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-null-return.mjs'), - ...commonArgs, - ], - { encoding: 'utf8' }, - ); - - assert.strictEqual(code, 1); - assert.match(stderr, /ERR_INVALID_RETURN_VALUE/); - assert.match(stderr, /loader-resolve-null-return\.mjs/); - assert.match(stderr, /'resolve' hook's nextResolve\(\)/); - assert.match(stderr, /an object/); - assert.match(stderr, /got null/); - }); - - it('should throw when invalid `context` argument passed to `nextResolve`', async () => { - const { code, stderr } = await spawnPromisified( - execPath, - [ - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-passthru.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-bad-next-context.mjs'), - ...commonArgs, - ], - { encoding: 'utf8' }, - ); - - assert.match(stderr, /ERR_INVALID_ARG_TYPE/); - assert.match(stderr, /loader-resolve-bad-next-context\.mjs/); - assert.match(stderr, /'resolve' hook's nextResolve\(\) context/); - assert.strictEqual(code, 1); - }); - - it('should throw when load hook is invalid', async () => { - const { code, stderr } = await spawnPromisified( - execPath, - [ - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-load-passthru.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-load-null-return.mjs'), - ...commonArgs, - ], - { encoding: 'utf8' }, - ); - - assert.strictEqual(code, 1); - assert.match(stderr, /ERR_INVALID_RETURN_VALUE/); - assert.match(stderr, /loader-load-null-return\.mjs/); - assert.match(stderr, /'load' hook's nextLoad\(\)/); - assert.match(stderr, /an object/); - assert.match(stderr, /got null/); - }); - - it('should throw when invalid `url` argument passed to `nextLoad`', async () => { - const { code, stderr } = await spawnPromisified( - execPath, - [ - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-load-passthru.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-load-bad-next-url.mjs'), - ...commonArgs, - ], - { encoding: 'utf8' }, - ); - - assert.match(stderr, /ERR_INVALID_ARG_TYPE/); - assert.match(stderr, /loader-load-bad-next-url\.mjs/); - assert.match(stderr, /'load' hook's nextLoad\(\) url/); - assert.strictEqual(code, 1); - }); - - it('should throw when invalid `url` argument passed to `nextLoad`', async () => { - const { code, stderr } = await spawnPromisified( - execPath, - [ - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-load-passthru.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-load-impersonating-next-url.mjs'), - ...commonArgs, - ], - { encoding: 'utf8' }, - ); - - assert.match(stderr, /ERR_INVALID_ARG_VALUE/); - assert.match(stderr, /loader-load-impersonating-next-url\.mjs/); - assert.match(stderr, /'load' hook's nextLoad\(\) url/); - assert.strictEqual(code, 1); - }); - - it('should throw when invalid `context` argument passed to `nextLoad`', async () => { - const { code, stderr } = await spawnPromisified( - execPath, - [ - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-load-passthru.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-load-bad-next-context.mjs'), - ...commonArgs, - ], - { encoding: 'utf8' }, - ); - assert.match(stderr, /ERR_INVALID_ARG_TYPE/); - assert.match(stderr, /loader-load-bad-next-context\.mjs/); - assert.match(stderr, /'load' hook's nextLoad\(\) context/); - assert.strictEqual(code, 1); - }); - - it('should allow loaders to influence subsequent loader `import()` calls in `resolve`', async () => { - const { code, stderr, stdout } = await spawnPromisified( - execPath, - [ - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-strip-xxx.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-dynamic-import.mjs'), - ...commonArgs, - ], - { encoding: 'utf8' }, - ); - assert.strictEqual(stderr, ''); - assert.match(stdout, /resolve dynamic import/); // It did go thru resolve-dynamic - assert.strictEqual(code, 0); - }); - - it('should allow loaders to influence subsequent loader `import()` calls in `load`', async () => { - const { code, stderr, stdout } = await spawnPromisified( - execPath, - [ - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-strip-xxx.mjs'), - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-load-dynamic-import.mjs'), - ...commonArgs, - ], - { encoding: 'utf8' }, - ); - assert.strictEqual(stderr, ''); - assert.match(stdout, /load dynamic import/); // It did go thru load-dynamic - assert.strictEqual(code, 0); - }); -}); diff --git a/test/es-module/test-esm-loader-custom-condition.mjs b/test/es-module/test-esm-loader-custom-condition.mjs deleted file mode 100644 index 075e4fe2b660..000000000000 --- a/test/es-module/test-esm-loader-custom-condition.mjs +++ /dev/null @@ -1,21 +0,0 @@ -// Flags: --experimental-loader ./test/fixtures/es-module-loaders/loader-with-custom-condition.mjs -import '../common/index.mjs'; -import assert from 'assert'; -import util from 'util'; - -import * as ns from '../fixtures/es-modules/conditional-exports.mjs'; - -assert.deepStrictEqual({ ...ns }, { default: 'from custom condition' }); - -assert.strictEqual( - util.inspect(ns, { showHidden: false }), - "[Module: null prototype] { default: 'from custom condition' }" -); - -assert.strictEqual( - util.inspect(ns, { showHidden: true }), - '[Module: null prototype] {\n' + - " default: 'from custom condition',\n" + - " [Symbol(Symbol.toStringTag)]: 'Module'\n" + - '}' -); diff --git a/test/es-module/test-esm-loader-default-resolver.mjs b/test/es-module/test-esm-loader-default-resolver.mjs index 2a69010e0504..9f836aa56e0f 100644 --- a/test/es-module/test-esm-loader-default-resolver.mjs +++ b/test/es-module/test-esm-loader-default-resolver.mjs @@ -5,51 +5,6 @@ import { execPath } from 'node:process'; import { describe, it } from 'node:test'; describe('default resolver', () => { - // In these tests `byop` is an acronym for "bring your own protocol", and is the - // protocol our byop-dummy-loader.mjs can load - it('should accept foreign schemas without exception (e.g. byop://something/or-other)', async () => { - const { code, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('/es-module-loaders/byop-dummy-loader.mjs'), - '--input-type=module', - '--eval', - "import 'byop://1/index.mjs'", - ]); - assert.strictEqual(code, 0); - assert.strictEqual(stdout.trim(), 'index.mjs!'); - assert.strictEqual(stderr, ''); - }); - - it('should resolve foreign schemas by doing regular url absolutization', async () => { - const { code, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('/es-module-loaders/byop-dummy-loader.mjs'), - '--input-type=module', - '--eval', - "import 'byop://1/index2.mjs'", - ]); - assert.strictEqual(code, 0); - assert.strictEqual(stdout.trim(), '42'); - assert.strictEqual(stderr, ''); - }); - - // In this test, `byoe` is an acronym for "bring your own extension" - it('should accept foreign extensions without exception (e.g. ..//something.byoe)', async () => { - const { code, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('/es-module-loaders/byop-dummy-loader.mjs'), - '--input-type=module', - '--eval', - "import 'byop://1/index.byoe'", - ]); - assert.strictEqual(code, 0); - assert.strictEqual(stdout.trim(), 'index.byoe!'); - assert.strictEqual(stderr, ''); - }); - it('should identify the parent module of an invalid URL host in import specifier', async () => { if (process.platform === 'win32') return; diff --git a/test/es-module/test-esm-loader-dependency.mjs b/test/es-module/test-esm-loader-dependency.mjs deleted file mode 100644 index 54327b478bf4..000000000000 --- a/test/es-module/test-esm-loader-dependency.mjs +++ /dev/null @@ -1,5 +0,0 @@ -// Flags: --experimental-loader ./test/fixtures/es-module-loaders/loader-with-dep.mjs -/* eslint-disable node-core/require-common-first, node-core/required-modules */ -import '../fixtures/es-modules/test-esm-ok.mjs'; - -// We just test that this module doesn't fail loading diff --git a/test/es-module/test-esm-loader-event-loop.mjs b/test/es-module/test-esm-loader-event-loop.mjs deleted file mode 100644 index d94e55148d85..000000000000 --- a/test/es-module/test-esm-loader-event-loop.mjs +++ /dev/null @@ -1,12 +0,0 @@ -// Flags: --experimental-loader ./test/fixtures/es-module-loaders/hooks-custom.mjs -import { mustCall } from '../common/index.mjs'; - -const done = mustCall(); - - -// Test that the process doesn't exit because of a caught exception thrown as part of dynamic import(). -for (let i = 0; i < 10; i++) { - await import('nonexistent/file.mjs').catch(() => {}); -} - -done(); diff --git a/test/es-module/test-esm-loader-http-imports.mjs b/test/es-module/test-esm-loader-http-imports.mjs deleted file mode 100644 index 96bfffe5b13f..000000000000 --- a/test/es-module/test-esm-loader-http-imports.mjs +++ /dev/null @@ -1,91 +0,0 @@ -import { spawnPromisified } from '../common/index.mjs'; -import fixtures from '../common/fixtures.js'; -import assert from 'node:assert'; -import http from 'node:http'; -import path from 'node:path'; -import { execPath } from 'node:process'; -import { promisify } from 'node:util'; -import { describe, it, after } from 'node:test'; - - -const files = { - 'main.mjs': 'export * from "./lib.mjs";', - 'lib.mjs': 'export { sum } from "./sum.mjs";', - 'sum.mjs': 'export function sum(a, b) { return a + b }', - 'console.mjs': ` - import { sum } from './sum.mjs'; - globalThis.sum = sum; - console.log("loaded over http"); - `, -}; - -const requestListener = ({ url }, rsp) => { - const filename = path.basename(url); - const content = files[filename]; - - if (content) { - return rsp - .writeHead(200, { 'Content-Type': 'application/javascript' }) - .end(content); - } - - return rsp - .writeHead(404) - .end(); -}; - -// Leave closing idle connections to the client, so that the loader never -// reuses a keep-alive socket the server has just closed. -const server = http.createServer({ keepAliveTimeout: 0 }, requestListener); - -await promisify(server.listen.bind(server))({ - host: '127.0.0.1', - port: 0, -}); - -const { - address: host, - port, -} = server.address(); - -describe('ESM: http import via loader', { concurrency: !process.env.TEST_PARALLEL }, () => { - it('should load using --import flag', async () => { - // ! MUST NOT use spawnSync to avoid blocking the event loop - const { code, signal, stderr, stdout } = await spawnPromisified( - execPath, - [ - '--no-warnings', - '--loader', - fixtures.fileURL('es-module-loaders', 'http-loader.mjs'), - '--import', `http://${host}:${port}/console.mjs`, - '--eval', - 'console.log(sum(1, 2))', - ] - ); - assert.strictEqual(stderr, ''); - assert.match(stdout, /loaded over http\r?\n3/); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - it('should load using import inside --eval code', async () => { - // ! MUST NOT use spawnSync to avoid blocking the event loop - const { code, signal, stderr, stdout } = await spawnPromisified( - execPath, - [ - '--no-warnings', - '--loader', - fixtures.fileURL('es-module-loaders', 'http-loader.mjs'), - '--input-type=module', - '--eval', - `import * as main from 'http://${host}:${port}/main.mjs'; console.log(main)`, - ] - ); - - assert.strictEqual(stderr, ''); - assert.strictEqual(stdout, '[Module: null prototype] { sum: [Function: sum] }\n'); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - - after(() => server.close()); -}); diff --git a/test/es-module/test-esm-loader-invalid-format.mjs b/test/es-module/test-esm-loader-invalid-format.mjs deleted file mode 100644 index 5a16381e5889..000000000000 --- a/test/es-module/test-esm-loader-invalid-format.mjs +++ /dev/null @@ -1,10 +0,0 @@ -// Flags: --experimental-loader ./test/fixtures/es-module-loaders/loader-invalid-format.mjs -import { expectsError, mustCall } from '../common/index.mjs'; -import assert from 'assert'; - -import('../fixtures/es-modules/test-esm-ok.mjs') -.then(assert.fail, expectsError({ - code: 'ERR_UNKNOWN_MODULE_FORMAT', - message: /Unknown module format: esm/, -})) -.then(mustCall()); diff --git a/test/es-module/test-esm-loader-invalid-url.mjs b/test/es-module/test-esm-loader-invalid-url.mjs deleted file mode 100644 index 1ba7c621f7e9..000000000000 --- a/test/es-module/test-esm-loader-invalid-url.mjs +++ /dev/null @@ -1,10 +0,0 @@ -// Flags: --experimental-loader ./test/fixtures/es-module-loaders/loader-invalid-url.mjs -import { expectsError, mustCall } from '../common/index.mjs'; -import assert from 'assert'; - -import('../fixtures/es-modules/test-esm-ok.mjs') -.then(assert.fail, (error) => { - expectsError({ code: 'ERR_INVALID_RETURN_PROPERTY_VALUE' })(error); - assert.match(error.message, /loader-invalid-url\.mjs/); -}) -.then(mustCall()); diff --git a/test/es-module/test-esm-loader-mock.mjs b/test/es-module/test-esm-loader-mock.mjs deleted file mode 100644 index cdf56d85415b..000000000000 --- a/test/es-module/test-esm-loader-mock.mjs +++ /dev/null @@ -1,42 +0,0 @@ -import '../common/index.mjs'; -import assert from 'node:assert/strict'; -import { mock } from '../fixtures/es-module-loaders/mock.mjs'; - -mock('node:events', { - EventEmitter: 'This is mocked!', -}); - -// This resolves to node:events -// It is intercepted by mock-loader and doesn't return the normal value -assert.deepStrictEqual(await import('events'), Object.defineProperty({ - __proto__: null, - EventEmitter: 'This is mocked!', -}, Symbol.toStringTag, { - enumerable: false, - value: 'Module', -})); - -const mutator = mock('node:events', { - EventEmitter: 'This is mocked v2!', -}); - -// It is intercepted by mock-loader and doesn't return the normal value. -// This is resolved separately from the import above since the specifiers -// are different. -const mockedV2 = await import('node:events'); -assert.deepStrictEqual(mockedV2, Object.defineProperty({ - __proto__: null, - EventEmitter: 'This is mocked v2!', -}, Symbol.toStringTag, { - enumerable: false, - value: 'Module', -})); - -mutator.EventEmitter = 'This is mocked v3!'; -assert.deepStrictEqual(mockedV2, Object.defineProperty({ - __proto__: null, - EventEmitter: 'This is mocked v3!', -}, Symbol.toStringTag, { - enumerable: false, - value: 'Module', -})); diff --git a/test/es-module/test-esm-loader-modulemap.js b/test/es-module/test-esm-loader-modulemap.js index ba0358368ec1..6dada059c4af 100644 --- a/test/es-module/test-esm-loader-modulemap.js +++ b/test/es-module/test-esm-loader-modulemap.js @@ -4,7 +4,7 @@ require('../common'); const assert = require('assert'); -const { createModuleLoader } = require('internal/modules/esm/loader'); +const { ModuleLoader } = require('internal/modules/esm/loader'); const { LoadCache, ResolveCache } = require('internal/modules/esm/module_map'); const { ModuleJob } = require('internal/modules/esm/module_job'); const createDynamicModule = require( @@ -16,7 +16,7 @@ const jsonModuleDataUrl = 'data:application/json,""'; const stubJsModule = createDynamicModule([], ['default'], jsModuleDataUrl); const stubJsonModule = createDynamicModule([], ['default'], jsonModuleDataUrl); -const loader = createModuleLoader(); +const loader = new ModuleLoader(); const jsModuleJob = new ModuleJob(loader, jsModuleDataUrl, {}, stubJsModule.module); const jsonModuleJob = new ModuleJob(loader, jsonModuleDataUrl, { type: 'json' }, stubJsonModule.module); diff --git a/test/es-module/test-esm-loader-not-found.mjs b/test/es-module/test-esm-loader-not-found.mjs deleted file mode 100644 index 2abaf3078d11..000000000000 --- a/test/es-module/test-esm-loader-not-found.mjs +++ /dev/null @@ -1,24 +0,0 @@ -import { spawnPromisified } from '../common/index.mjs'; -import * as fixtures from '../common/fixtures.mjs'; -import assert from 'node:assert'; -import { execPath } from 'node:process'; -import { describe, it } from 'node:test'; - - -describe('ESM: nonexistent loader', () => { - it('should throw', async () => { - const { code, stderr } = await spawnPromisified(execPath, [ - '--experimental-loader', - 'i-dont-exist', - fixtures.path('print-error-message.js'), - ]); - - assert.notStrictEqual(code, 0); - - // Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'i-dont-exist' imported from - assert.match(stderr, /ERR_MODULE_NOT_FOUND/); - assert.match(stderr, /'i-dont-exist'/); - - assert.ok(!stderr.includes('Bad command or file name')); - }); -}); diff --git a/test/es-module/test-esm-loader-programmatically.mjs b/test/es-module/test-esm-loader-programmatically.mjs deleted file mode 100644 index dd25e3f2430e..000000000000 --- a/test/es-module/test-esm-loader-programmatically.mjs +++ /dev/null @@ -1,251 +0,0 @@ -import { spawnPromisified } from '../common/index.mjs'; -import * as fixtures from '../common/fixtures.mjs'; -import assert from 'node:assert'; -import { execPath } from 'node:process'; -import { describe, it } from 'node:test'; - -// This test ensures that the register function can register loaders -// programmatically. - -const commonArgs = [ - '--no-warnings', - '--input-type=module', - '--loader=data:text/javascript,', -]; - -const commonEvals = { - import: (module) => `await import(${JSON.stringify(module)});`, - register: (loader, parentURL = 'file:///') => `register(${JSON.stringify(loader)}, ${JSON.stringify(parentURL)});`, - dynamicImport: (module) => `await import(${JSON.stringify(`data:text/javascript,${encodeURIComponent(module)}`)});`, - staticImport: (module) => `import ${JSON.stringify(`data:text/javascript,${encodeURIComponent(module)}`)};`, -}; - -describe('ESM: programmatically register loaders', { concurrency: !process.env.TEST_PARALLEL }, () => { - it('works with only a dummy CLI argument', async () => { - const parentURL = fixtures.fileURL('es-module-loaders', 'loader-resolve-passthru.mjs'); - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - ...commonArgs, - '--eval', - "import { register } from 'node:module';" + - commonEvals.register(fixtures.fileURL('es-module-loaders', 'loader-resolve-passthru.mjs')) + - commonEvals.register(fixtures.fileURL('es-module-loaders', 'loader-load-passthru.mjs')) + - `register(${JSON.stringify('./loader-resolve-passthru.mjs')}, ${JSON.stringify({ parentURL })});` + - `register(${JSON.stringify('./loader-load-passthru.mjs')}, ${JSON.stringify({ parentURL })});` + - commonEvals.dynamicImport('console.log("Hello from dynamic import");'), - ]); - - assert.strictEqual(stderr, ''); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - - const lines = stdout.split('\n'); - - assert.match(lines[0], /resolve passthru/); - assert.match(lines[1], /resolve passthru/); - assert.match(lines[2], /load passthru/); - assert.match(lines[3], /load passthru/); - assert.match(lines[4], /Hello from dynamic import/); - - assert.strictEqual(lines[5], ''); - }); - - describe('registering via --import', { concurrency: !process.env.TEST_PARALLEL }, () => { - for (const moduleType of ['mjs', 'cjs']) { - it(`should programmatically register a loader from a ${moduleType.toUpperCase()} file`, async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - ...commonArgs, - '--import', fixtures.fileURL('es-module-loaders', `register-loader.${moduleType}`).href, - '--eval', commonEvals.staticImport('console.log("entry point")'), - ]); - - assert.strictEqual(stderr, ''); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - - const [ - passthruStdout, - entryPointStdout, - ] = stdout.split('\n'); - - assert.match(passthruStdout, /resolve passthru/); - assert.match(entryPointStdout, /entry point/); - }); - } - }); - - it('programmatically registered loaders are appended to an existing chaining', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - ...commonArgs, - '--loader', - fixtures.fileURL('es-module-loaders', 'loader-resolve-passthru.mjs'), - '--eval', - "import { register } from 'node:module';" + - commonEvals.register(fixtures.fileURL('es-module-loaders', 'loader-load-passthru.mjs')) + - commonEvals.dynamicImport('console.log("Hello from dynamic import");'), - ]); - - assert.strictEqual(stderr, ''); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - - const lines = stdout.split('\n'); - - assert.match(lines[0], /resolve passthru/); - assert.match(lines[1], /resolve passthru/); - assert.match(lines[2], /load passthru/); - assert.match(lines[3], /Hello from dynamic import/); - - assert.strictEqual(lines[4], ''); - }); - - it('works registering loaders across files', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - ...commonArgs, - '--eval', - commonEvals.import(fixtures.fileURL('es-module-loaders', 'register-programmatically-loader-load.mjs')) + - commonEvals.import(fixtures.fileURL('es-module-loaders', 'register-programmatically-loader-resolve.mjs')) + - commonEvals.dynamicImport('console.log("Hello from dynamic import");'), - ]); - - assert.strictEqual(stderr, ''); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - - const lines = stdout.split('\n'); - - assert.match(lines[0], /resolve passthru/); - assert.match(lines[1], /load passthru/); - assert.match(lines[2], /Hello from dynamic import/); - - assert.strictEqual(lines[3], ''); - }); - - it('works registering loaders across virtual files', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - ...commonArgs, - '--eval', - commonEvals.import(fixtures.fileURL('es-module-loaders', 'register-programmatically-loader-load.mjs')) + - commonEvals.dynamicImport( - commonEvals.import(fixtures.fileURL('es-module-loaders', 'register-programmatically-loader-resolve.mjs')) + - commonEvals.dynamicImport('console.log("Hello from dynamic import");'), - ), - ]); - - assert.strictEqual(stderr, ''); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - - const lines = stdout.split('\n'); - - assert.match(lines[0], /resolve passthru/); - assert.match(lines[1], /load passthru/); - assert.match(lines[2], /Hello from dynamic import/); - - assert.strictEqual(lines[3], ''); - }); - - it('works registering the same loaders more them once', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - ...commonArgs, - '--eval', - "import { register } from 'node:module';" + - commonEvals.register(fixtures.fileURL('es-module-loaders', 'loader-resolve-passthru.mjs')) + - commonEvals.register(fixtures.fileURL('es-module-loaders', 'loader-load-passthru.mjs')) + - commonEvals.register(fixtures.fileURL('es-module-loaders', 'loader-resolve-passthru.mjs')) + - commonEvals.register(fixtures.fileURL('es-module-loaders', 'loader-load-passthru.mjs')) + - commonEvals.dynamicImport('console.log("Hello from dynamic import");'), - ]); - - assert.strictEqual(stderr, ''); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - - const lines = stdout.split('\n'); - - assert.match(lines[0], /resolve passthru/); - assert.match(lines[1], /resolve passthru/); - assert.match(lines[2], /load passthru/); - assert.match(lines[3], /load passthru/); - assert.match(lines[4], /Hello from dynamic import/); - - assert.strictEqual(lines[5], ''); - }); - - it('works registering loaders as package name', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - ...commonArgs, - '--eval', - "import { register } from 'node:module';" + - commonEvals.register('resolve', fixtures.fileURL('es-module-loaders', 'package.json')) + - commonEvals.register('load', fixtures.fileURL('es-module-loaders', 'package.json')) + - commonEvals.dynamicImport('console.log("Hello from dynamic import");'), - ]); - - assert.strictEqual(stderr, ''); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - - const lines = stdout.split('\n'); - - // Resolve occurs twice because it is first used to resolve the `load` loader - // _AND THEN_ the `register` module. - assert.match(lines[0], /resolve passthru/); - assert.match(lines[1], /resolve passthru/); - assert.match(lines[2], /load passthru/); - assert.match(lines[3], /Hello from dynamic import/); - - assert.strictEqual(lines[4], ''); - }); - - it('works without a CLI flag', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--input-type=module', - '--eval', - "import { register } from 'node:module';" + - commonEvals.register(fixtures.fileURL('es-module-loaders', 'loader-load-passthru.mjs')) + - commonEvals.dynamicImport('console.log("Hello from dynamic import");'), - ]); - - assert.strictEqual(stderr, ''); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - - const lines = stdout.split('\n'); - - assert.match(lines[0], /load passthru/); - assert.match(lines[1], /Hello from dynamic import/); - - assert.strictEqual(lines[2], ''); - }); - - it('does not work with a loader specifier that does not exist', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - ...commonArgs, - '--eval', - "import { register } from 'node:module';" + - commonEvals.register('./not-found.mjs', import.meta.url) + - commonEvals.dynamicImport('console.log("Hello from dynamic import");'), - ]); - - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 1); - assert.strictEqual(signal, null); - assert.match(stderr, /ERR_MODULE_NOT_FOUND/); - }); - - it('does not work with a loader that got syntax errors', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - ...commonArgs, - '--eval', - "import { register } from 'node:module';" + - commonEvals.register(fixtures.fileURL('es-module-loaders', 'syntax-error.mjs')) + - commonEvals.dynamicImport('console.log("Hello from dynamic import");'), - ]); - - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 1); - assert.strictEqual(signal, null); - assert.match(stderr, /SyntaxError/); - }); -}); diff --git a/test/es-module/test-esm-loader-resolve-type.mjs b/test/es-module/test-esm-loader-resolve-type.mjs deleted file mode 100644 index 7dcc65a10c09..000000000000 --- a/test/es-module/test-esm-loader-resolve-type.mjs +++ /dev/null @@ -1,48 +0,0 @@ -import { spawnPromisified } from '../common/index.mjs'; -import * as tmpdir from '../common/tmpdir.js'; -import * as fixtures from '../common/fixtures.mjs'; -import assert from 'node:assert'; -import { mkdir, rm, cp } from 'node:fs/promises'; -import { execPath } from 'node:process'; - -tmpdir.refresh(); - -const base = tmpdir.fileURL(`test-esm-loader-resolve-type-${(Math.random() * Date.now()).toFixed(0)}`); -const moduleName = 'module-counter-by-type'; -const moduleURL = new URL(`${base}/node_modules/${moduleName}`); -try { - await mkdir(moduleURL, { recursive: true }); - await cp( - fixtures.path('es-modules', 'module-counter-by-type'), - moduleURL, - { recursive: true } - ); - - const output = await spawnPromisified( - execPath, - [ - '--no-warnings', - '--input-type=module', - '--eval', - `import { getModuleTypeStats } from ${JSON.stringify(fixtures.fileURL('es-module-loaders', 'hook-resolve-type.mjs'))}; - const before = getModuleTypeStats(); - await import(${JSON.stringify(moduleName)}); - const after = getModuleTypeStats(); - console.log(JSON.stringify({ before, after }));`, - ], - { cwd: base }, - ); - - assert.deepStrictEqual(output, { - code: 0, - signal: null, - stderr: '', - stdout: JSON.stringify({ - before: { importedESM: 0, importedCJS: 0 }, - // Dynamic import in the eval script should increment ESM counter but not CJS counter - after: { importedESM: 1, importedCJS: 0 }, - }) + '\n', - }); -} finally { - await rm(base, { recursive: true, force: true }); -} diff --git a/test/es-module/test-esm-loader-spawn-promisified.mjs b/test/es-module/test-esm-loader-spawn-promisified.mjs deleted file mode 100644 index 2f27f7850f64..000000000000 --- a/test/es-module/test-esm-loader-spawn-promisified.mjs +++ /dev/null @@ -1,304 +0,0 @@ -import { spawnPromisified } from '../common/index.mjs'; -import * as fixtures from '../common/fixtures.mjs'; -import assert from 'node:assert'; -import { execPath } from 'node:process'; -import { describe, it } from 'node:test'; - -describe('Loader hooks throwing errors', { concurrency: !process.env.TEST_PARALLEL }, () => { - it('throws on nonexistent modules', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('/es-module-loaders/hooks-custom.mjs'), - '--input-type=module', - '--eval', - 'import "nonexistent/file.mjs"', - ]); - - assert.match(stderr, /ERR_MODULE_NOT_FOUND/); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 1); - assert.strictEqual(signal, null); - }); - - it('throws on unknown extensions', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('/es-module-loaders/hooks-custom.mjs'), - '--input-type=module', - '--eval', - `import ${JSON.stringify(fixtures.fileURL('/es-modules/file.unknown'))}`, - ]); - - assert.match(stderr, /ERR_UNKNOWN_FILE_EXTENSION/); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 1); - assert.strictEqual(signal, null); - }); - - it('throws on invalid return values', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('/es-module-loaders/hooks-custom.mjs'), - '--input-type=module', - '--eval', - 'import "esmHook/badReturnVal.mjs"', - ]); - - assert.match(stderr, /ERR_INVALID_RETURN_VALUE/); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 1); - assert.strictEqual(signal, null); - }); - - it('throws on boolean false', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('/es-module-loaders/hooks-custom.mjs'), - '--input-type=module', - '--eval', - 'import "esmHook/format.false"', - ]); - - assert.match(stderr, /ERR_INVALID_RETURN_PROPERTY_VALUE/); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 1); - assert.strictEqual(signal, null); - }); - it('throws on boolean true', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('/es-module-loaders/hooks-custom.mjs'), - '--input-type=module', - '--eval', - 'import "esmHook/format.true"', - ]); - - assert.match(stderr, /ERR_INVALID_RETURN_PROPERTY_VALUE/); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 1); - assert.strictEqual(signal, null); - }); - - it('throws on invalid returned object', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('/es-module-loaders/hooks-custom.mjs'), - '--input-type=module', - '--eval', - 'import "esmHook/badReturnFormatVal.mjs"', - ]); - - assert.match(stderr, /ERR_INVALID_RETURN_PROPERTY_VALUE/); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 1); - assert.strictEqual(signal, null); - }); - - it('throws on unsupported format', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('/es-module-loaders/hooks-custom.mjs'), - '--input-type=module', - '--eval', - 'import "esmHook/unsupportedReturnFormatVal.mjs"', - ]); - - assert.match(stderr, /ERR_UNKNOWN_MODULE_FORMAT/); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 1); - assert.strictEqual(signal, null); - }); - - it('throws on invalid format property type', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('/es-module-loaders/hooks-custom.mjs'), - '--input-type=module', - '--eval', - 'import "esmHook/badReturnSourceVal.mjs"', - ]); - - assert.match(stderr, /ERR_INVALID_RETURN_PROPERTY_VALUE/); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 1); - assert.strictEqual(signal, null); - }); - - it('rejects dynamic imports for all of the error cases checked above', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('/es-module-loaders/hooks-custom.mjs'), - '--input-type=module', - '--eval', - `import assert from 'node:assert'; - await Promise.allSettled([ - import('nonexistent/file.mjs'), - import(${JSON.stringify(fixtures.fileURL('/es-modules/file.unknown'))}), - import('esmHook/badReturnVal.mjs'), - import('esmHook/format.false'), - import('esmHook/format.true'), - import('esmHook/badReturnFormatVal.mjs'), - import('esmHook/unsupportedReturnFormatVal.mjs'), - import('esmHook/badReturnSourceVal.mjs'), - ]).then((results) => { - assert.strictEqual(results.every((result) => result.status === 'rejected'), true); - })`, - ]); - - assert.strictEqual(stderr, ''); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); -}); - -describe('Loader hooks parsing modules', { concurrency: !process.env.TEST_PARALLEL }, () => { - it('can parse .js files as ESM', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('/es-module-loaders/hooks-custom.mjs'), - '--input-type=module', - '--eval', - `import assert from 'node:assert'; - await import(${JSON.stringify(fixtures.fileURL('/es-module-loaders/js-as-esm.js'))}) - .then((parsedModule) => { - assert.strictEqual(typeof parsedModule, 'object'); - assert.strictEqual(parsedModule.namedExport, 'named-export'); - })`, - ]); - - assert.strictEqual(stderr, ''); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - - it('can define .ext files as ESM', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('/es-module-loaders/hooks-custom.mjs'), - '--input-type=module', - '--eval', - `import assert from 'node:assert'; - await import(${JSON.stringify(fixtures.fileURL('/es-modules/file.ext'))}) - .then((parsedModule) => { - assert.strictEqual(typeof parsedModule, 'object'); - const { default: defaultExport } = parsedModule; - assert.strictEqual(typeof defaultExport, 'function'); - assert.strictEqual(defaultExport.name, 'iAmReal'); - assert.strictEqual(defaultExport(), true); - })`, - ]); - - assert.strictEqual(stderr, ''); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - - it('can predetermine the format in the custom loader resolve hook', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('/es-module-loaders/hooks-custom.mjs'), - '--input-type=module', - '--eval', - `import assert from 'node:assert'; - await import('esmHook/preknownFormat.pre') - .then((parsedModule) => { - assert.strictEqual(typeof parsedModule, 'object'); - assert.strictEqual(parsedModule.default, 'hello world'); - })`, - ]); - - assert.strictEqual(stderr, ''); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - - it('can provide source for a nonexistent file', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('/es-module-loaders/hooks-custom.mjs'), - '--input-type=module', - '--eval', - `import assert from 'node:assert'; - await import('esmHook/virtual.mjs') - .then((parsedModule) => { - assert.strictEqual(typeof parsedModule, 'object'); - assert.strictEqual(typeof parsedModule.default, 'undefined'); - assert.strictEqual(parsedModule.message, 'WOOHOO!'); - })`, - ]); - - assert.strictEqual(stderr, ''); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - - it('ensures that loaders have a separate context from userland', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('/es-module-loaders/hooks-custom.mjs'), - '--input-type=module', - '--eval', - `import assert from 'node:assert'; - await import(${JSON.stringify(fixtures.fileURL('/es-modules/stateful.mjs'))}) - .then(({ default: count }) => { - assert.strictEqual(count(), 1); - });`, - ]); - - assert.strictEqual(stderr, ''); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - - it('ensures that user loaders are not bound to the internal loader', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('/es-module-loaders/loader-this-value-inside-hook-functions.mjs'), - '--input-type=module', - '--eval', - ';', // Actual test is inside the loader module. - ]); - - assert.strictEqual(stderr, ''); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - - it('throw maximum call stack error on the loader', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('/es-module-loaders/hooks-custom.mjs'), - '--input-type=module', - '--eval', - 'await import("esmHook/maximumCallStack.mjs")', - ]); - - assert(stderr.includes('Maximum call stack size exceeded')); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 1); - assert.strictEqual(signal, null); - }); -}); diff --git a/test/es-module/test-esm-loader-stringify-text.mjs b/test/es-module/test-esm-loader-stringify-text.mjs deleted file mode 100644 index 8502375b31aa..000000000000 --- a/test/es-module/test-esm-loader-stringify-text.mjs +++ /dev/null @@ -1,66 +0,0 @@ -// Flags: --experimental-loader ./test/fixtures/es-module-loaders/string-sources.mjs -import { mustCall } from '../common/index.mjs'; -import assert from 'assert'; - -assert.rejects( - import('test:Array'), - { code: 'ERR_INVALID_RETURN_PROPERTY_VALUE' }, -).then(mustCall()); -import('test:ArrayBuffer').then( - mustCall(), -); -import('test:BigInt64Array').then( - mustCall(), -); -import('test:BigUint64Array').then( - mustCall(), -); -import('test:Float32Array').then( - mustCall(), -); -import('test:Float64Array').then( - mustCall(), -); -import('test:Int8Array').then( - mustCall(), -); -import('test:Int16Array').then( - mustCall(), -); -import('test:Int32Array').then( - mustCall(), -); -assert.rejects( - import('test:null'), - { code: 'ERR_INVALID_RETURN_PROPERTY_VALUE' }, -).then(mustCall()); -assert.rejects( - import('test:Object'), - { code: 'ERR_INVALID_RETURN_PROPERTY_VALUE' }, -).then(mustCall()); -import('test:SharedArrayBuffer').then( - mustCall(), -); -import('test:string').then( - mustCall(), -); -assert.rejects( - import('test:String'), - { code: 'ERR_INVALID_RETURN_PROPERTY_VALUE' }, -).then(mustCall()); -import('test:Uint8ClampedArray').then( - mustCall(), -); -import('test:Uint16Array').then( - mustCall(), -); -import('test:Uint32Array').then( - mustCall(), -); -import('test:Uint8Array').then( - mustCall(), -); -assert.rejects( - import('test:undefined'), - { code: 'ERR_INVALID_RETURN_PROPERTY_VALUE' }, -).then(mustCall()); diff --git a/test/es-module/test-esm-loader-thenable.mjs b/test/es-module/test-esm-loader-thenable.mjs deleted file mode 100644 index d26ba7d476a5..000000000000 --- a/test/es-module/test-esm-loader-thenable.mjs +++ /dev/null @@ -1,43 +0,0 @@ -import { spawnPromisified } from '../common/index.mjs'; -import * as fixtures from '../common/fixtures.mjs'; -import assert from 'assert'; -import { execPath } from 'node:process'; -import { describe, it } from 'node:test'; - - -describe('ESM: thenable loader hooks', { concurrency: !process.env.TEST_PARALLEL }, () => { - it('should behave as a normal promise resolution', async () => { - const { code, stderr } = await spawnPromisified(execPath, [ - '--experimental-loader', - fixtures.fileURL('es-module-loaders', 'thenable-load-hook.mjs').href, - fixtures.path('es-modules', 'test-esm-ok.mjs'), - ]); - - assert.strictEqual(code, 0); - assert.ok(!stderr.includes('must not call')); - }); - - it('should crash the node process rejection with an error', async () => { - const { code, stderr } = await spawnPromisified(execPath, [ - '--experimental-loader', - fixtures.fileURL('es-module-loaders', 'thenable-load-hook-rejected.mjs').href, - fixtures.path('es-modules', 'test-esm-ok.mjs'), - ]); - - assert.notStrictEqual(code, 0); - assert.match(stderr, /\sError: must crash the process\r?\n/); - assert.ok(!stderr.includes('must not call')); - }); - - it('should just reject without an error (but NOT crash the node process)', async () => { - const { code, stderr } = await spawnPromisified(execPath, [ - '--experimental-loader', - fixtures.fileURL('es-module-loaders', 'thenable-load-hook-rejected-no-arguments.mjs').href, - fixtures.path('es-modules', 'test-esm-ok.mjs'), - ]); - - assert.notStrictEqual(code, 0); - assert.match(stderr, /\sundefined\r?\n/); - assert.ok(!stderr.includes('must not call')); - }); -}); diff --git a/test/es-module/test-esm-loader-with-source.mjs b/test/es-module/test-esm-loader-with-source.mjs deleted file mode 100644 index 41dca12dfc6b..000000000000 --- a/test/es-module/test-esm-loader-with-source.mjs +++ /dev/null @@ -1,10 +0,0 @@ -// Flags: --experimental-loader ./test/fixtures/es-module-loaders/preset-cjs-source.mjs -import '../common/index.mjs'; -import * as fixtures from '../common/fixtures.mjs'; -import assert from 'assert'; - -const { default: existingFileSource } = await import(fixtures.fileURL('es-modules', 'cjs-file.cjs')); -const { default: noSuchFileSource } = await import(new URL('./no-such-file.cjs', import.meta.url)); - -assert.strictEqual(existingFileSource, 'no .cjs file was read to get this source'); -assert.strictEqual(noSuchFileSource, 'no .cjs file was read to get this source'); diff --git a/test/es-module/test-esm-loader-with-syntax-error.mjs b/test/es-module/test-esm-loader-with-syntax-error.mjs deleted file mode 100644 index fe41b293a3fc..000000000000 --- a/test/es-module/test-esm-loader-with-syntax-error.mjs +++ /dev/null @@ -1,20 +0,0 @@ -import { spawnPromisified } from '../common/index.mjs'; -import * as fixtures from '../common/fixtures.mjs'; -import assert from 'node:assert'; -import { execPath } from 'node:process'; -import { describe, it } from 'node:test'; - - -describe('ESM: loader with syntax error', { concurrency: !process.env.TEST_PARALLEL }, () => { - it('should crash the node process', async () => { - const { code, stderr } = await spawnPromisified(execPath, [ - '--experimental-loader', - fixtures.fileURL('es-module-loaders', 'syntax-error.mjs').href, - fixtures.path('print-error-message.js'), - ]); - - assert.match(stderr, /SyntaxError/); - assert.ok(!stderr.includes('Bad command or file name')); - assert.notStrictEqual(code, 0); - }); -}); diff --git a/test/es-module/test-esm-loader.mjs b/test/es-module/test-esm-loader.mjs deleted file mode 100644 index de3481432481..000000000000 --- a/test/es-module/test-esm-loader.mjs +++ /dev/null @@ -1,88 +0,0 @@ -// Flags: --experimental-loader ./test/fixtures/es-module-loaders/hooks-custom.mjs -import '../common/index.mjs'; -import assert from 'assert'; - - -await assert.rejects( - import('nonexistent/file.mjs'), - { code: 'ERR_MODULE_NOT_FOUND' }, -); - -await assert.rejects( - import('../fixtures/es-modules/file.unknown'), - { code: 'ERR_UNKNOWN_FILE_EXTENSION' }, -); - -await assert.rejects( - import('esmHook/badReturnVal.mjs'), - { code: 'ERR_INVALID_RETURN_VALUE' }, -); - -// Nullish values are allowed; booleans are not -await assert.rejects( - import('esmHook/format.false'), - { code: 'ERR_INVALID_RETURN_PROPERTY_VALUE' }, -); -await assert.rejects( - import('esmHook/format.true'), - { code: 'ERR_INVALID_RETURN_PROPERTY_VALUE' }, -); - -await assert.rejects( - import('esmHook/badReturnFormatVal.mjs'), - { code: 'ERR_INVALID_RETURN_PROPERTY_VALUE' }, -); - -await assert.rejects( - import('esmHook/unsupportedReturnFormatVal.mjs'), - { code: 'ERR_UNKNOWN_MODULE_FORMAT' }, -); - -await assert.rejects( - import('esmHook/badReturnSourceVal.mjs'), - { code: 'ERR_INVALID_RETURN_PROPERTY_VALUE' }, -); - -await assert.rejects(import('esmHook/commonJsNullSource.mjs'), { - code: 'ERR_INVALID_RETURN_PROPERTY_VALUE', - message: /"source".*'load'.*got type bigint/, -}); - -await import('../fixtures/es-module-loaders/js-as-esm.js') -.then((parsedModule) => { - assert.strictEqual(typeof parsedModule, 'object'); - assert.strictEqual(parsedModule.namedExport, 'named-export'); -}); - -// The custom loader tells node .ext files are MJS -await import('../fixtures/es-modules/file.ext') -.then((parsedModule) => { - assert.strictEqual(typeof parsedModule, 'object'); - const { default: defaultExport } = parsedModule; - assert.strictEqual(typeof defaultExport, 'function'); - assert.strictEqual(defaultExport.name, 'iAmReal'); - assert.strictEqual(defaultExport(), true); -}); - -// The custom loader's resolve hook predetermines the format -await import('esmHook/preknownFormat.pre') -.then((parsedModule) => { - assert.strictEqual(typeof parsedModule, 'object'); - assert.strictEqual(parsedModule.default, 'hello world'); -}); - -// The custom loader provides source even though file does not actually exist -await import('esmHook/virtual.mjs') -.then((parsedModule) => { - assert.strictEqual(typeof parsedModule, 'object'); - assert.strictEqual(typeof parsedModule.default, 'undefined'); - assert.strictEqual(parsedModule.message, 'WOOHOO!'); -}); - -// Ensure custom loaders have separate context from userland -// hooks-custom.mjs also increments count (which starts at 0) -// if both share context, count here would be 2 -await import('../fixtures/es-modules/stateful.mjs') -.then(({ default: count }) => { - assert.strictEqual(count(), 1); -}); diff --git a/test/es-module/test-esm-named-exports.mjs b/test/es-module/test-esm-named-exports.mjs deleted file mode 100644 index bbe9c96b92d9..000000000000 --- a/test/es-module/test-esm-named-exports.mjs +++ /dev/null @@ -1,9 +0,0 @@ -// Flags: --import ./test/fixtures/es-module-loaders/builtin-named-exports.mjs -import '../common/index.mjs'; -import { readFile, __fromLoader } from 'fs'; -import assert from 'assert'; -import ok from '../fixtures/es-modules/test-esm-ok.mjs'; - -assert(ok); -assert(readFile); -assert(__fromLoader); diff --git a/test/es-module/test-esm-preserve-symlinks-not-found-plain.mjs b/test/es-module/test-esm-preserve-symlinks-not-found-plain.mjs deleted file mode 100644 index 7a538e44c072..000000000000 --- a/test/es-module/test-esm-preserve-symlinks-not-found-plain.mjs +++ /dev/null @@ -1,3 +0,0 @@ -// Flags: --experimental-loader ./test/fixtures/es-module-loaders/not-found-assert-loader.mjs -/* eslint-disable node-core/require-common-first, node-core/required-modules */ -import './not-found.js'; diff --git a/test/es-module/test-esm-preserve-symlinks-not-found.mjs b/test/es-module/test-esm-preserve-symlinks-not-found.mjs deleted file mode 100644 index 722dc467d192..000000000000 --- a/test/es-module/test-esm-preserve-symlinks-not-found.mjs +++ /dev/null @@ -1,3 +0,0 @@ -// Flags: --experimental-loader ./test/fixtures/es-module-loaders/not-found-assert-loader.mjs -/* eslint-disable node-core/require-common-first, node-core/required-modules */ -import './not-found.mjs'; diff --git a/test/es-module/test-esm-register-deprecation.mjs b/test/es-module/test-esm-register-deprecation.mjs deleted file mode 100644 index 171e5971a091..000000000000 --- a/test/es-module/test-esm-register-deprecation.mjs +++ /dev/null @@ -1,60 +0,0 @@ -import { spawnPromisified } from '../common/index.mjs'; -import * as fixtures from '../common/fixtures.mjs'; - -import assert from 'node:assert'; -import { execPath } from 'node:process'; -import { describe, it } from 'node:test'; - -const urlToRegister = fixtures.fileURL('es-module-loaders', 'loader-resolve-passthru.mjs'); -const urlToRegisterEscaped = JSON.stringify(urlToRegister.href); - - -describe('module.register() deprecation (DEP0205)', { concurrency: !process.env.TEST_PARALLEL }, () => { - it('emits DEP0205 when module.register() is called', async () => { - const { code, stderr } = await spawnPromisified(execPath, [ - '--input-type=module', - '--eval', - `import { register } from 'node:module'; register(${urlToRegisterEscaped});`, - ]); - - assert.match(stderr, /\[DEP0205\]/); - assert.strictEqual(code, 0); - }); - - it('only emits the warning once per process', async () => { - const { code, stderr } = await spawnPromisified(execPath, [ - '--input-type=module', - '--eval', - `import { register } from 'node:module'; - register(${urlToRegisterEscaped}); - register(${urlToRegisterEscaped});`, - ]); - - assert.strictEqual(stderr.split('[DEP0205]').length - 1, 1); - assert.strictEqual(code, 0); - }); - - it('does not emit when --no-deprecation is set', async () => { - const { code, stderr } = await spawnPromisified(execPath, [ - '--no-deprecation', - '--input-type=module', - '--eval', - `import { register } from 'node:module'; register(${urlToRegisterEscaped});`, - ]); - - assert.doesNotMatch(stderr, /DEP0205/); - assert.strictEqual(code, 0); - }); - - it('throws when --throw-deprecation is set', async () => { - const { code, stderr } = await spawnPromisified(execPath, [ - '--throw-deprecation', - '--input-type=module', - '--eval', - `import { register } from 'node:module'; register(${urlToRegisterEscaped});`, - ]); - - assert.match(stderr, /DEP0205/); - assert.notStrictEqual(code, 0); - }); -}); diff --git a/test/es-module/test-esm-require-after-failed-import.js b/test/es-module/test-esm-require-after-failed-import.js new file mode 100644 index 000000000000..6815243212f9 --- /dev/null +++ b/test/es-module/test-esm-require-after-failed-import.js @@ -0,0 +1,15 @@ +'use strict'; +// Tests that require() of an ES module whose earlier import() failed during +// instantiation throws ERR_REQUIRE_ESM_RACE_CONDITION. +const common = require('../common'); +const assert = require('node:assert'); +const fixtures = require('../common/fixtures'); + +const url = fixtures.fileURL('es-modules/require-after-failed-import/mismatched.mjs'); + +assert.rejects(import(url), { name: 'SyntaxError' }).then(common.mustCall(() => { + assert.throws( + () => require(fixtures.path('es-modules/require-after-failed-import/mismatched.mjs')), + { code: 'ERR_REQUIRE_ESM_RACE_CONDITION' }, + ); +})); diff --git a/test/es-module/test-esm-shared-loader-dep.mjs b/test/es-module/test-esm-shared-loader-dep.mjs deleted file mode 100644 index e0015bd0c755..000000000000 --- a/test/es-module/test-esm-shared-loader-dep.mjs +++ /dev/null @@ -1,10 +0,0 @@ -// Flags: --experimental-loader ./test/fixtures/es-module-loaders/loader-shared-dep.mjs -import { createRequire } from '../common/index.mjs'; - -import assert from 'assert'; -import '../fixtures/es-modules/test-esm-ok.mjs'; - -const require = createRequire(import.meta.url); -const dep = require('../fixtures/es-module-loaders/loader-dep.js'); - -assert.strictEqual(dep.format, 'module'); diff --git a/test/es-module/test-loaders-unknown-builtin-module.mjs b/test/es-module/test-loaders-unknown-builtin-module.mjs deleted file mode 100644 index c97e52d1e63d..000000000000 --- a/test/es-module/test-loaders-unknown-builtin-module.mjs +++ /dev/null @@ -1,12 +0,0 @@ -// Flags: --experimental-loader ./test/fixtures/es-module-loaders/loader-unknown-builtin-module.mjs -import { expectsError, mustCall } from '../common/index.mjs'; -import assert from 'assert'; - -const unknownBuiltinModule = 'node:unknown-builtin-module'; - -import(unknownBuiltinModule) -.then(assert.fail, expectsError({ - code: 'ERR_UNKNOWN_BUILTIN_MODULE', - message: `No such built-in module: ${unknownBuiltinModule}`, -})) -.then(mustCall()); diff --git a/test/es-module/test-loaders-workers-spawned.mjs b/test/es-module/test-loaders-workers-spawned.mjs deleted file mode 100644 index 68c1b863f7ca..000000000000 --- a/test/es-module/test-loaders-workers-spawned.mjs +++ /dev/null @@ -1,83 +0,0 @@ -import { spawnPromisified } from '../common/index.mjs'; -import * as fixtures from '../common/fixtures.mjs'; -import assert from 'node:assert'; -import { execPath } from 'node:process'; -import { describe, it } from 'node:test'; - -describe('Worker threads do not spawn infinitely', { concurrency: !process.env.TEST_PARALLEL }, () => { - it('should not trigger an infinite loop when using a loader exports no recognized hooks', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('empty.js'), - '--eval', - 'setTimeout(() => console.log("hello"),99)', - ]); - - assert.strictEqual(stderr, ''); - assert.match(stdout, /^hello\r?\n$/); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - - it('should support a CommonJS entry point and a loader that imports a CommonJS module', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/loader-with-dep.mjs'), - fixtures.path('print-delayed.js'), - ]); - - assert.strictEqual(stderr, ''); - assert.match(stdout, /^delayed\r?\n$/); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - - it('should support --require and --import along with using a loader written in CJS and CJS entry point', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--eval', - 'setTimeout(() => console.log("D"),99)', - '--import', - fixtures.fileURL('printC.js'), - '--experimental-loader', - fixtures.fileURL('printB.js'), - '--require', - fixtures.path('printA.js'), - ]); - - assert.strictEqual(stderr, ''); - // We are validating that: - // 1. the `--require` flag is run first from the main thread (and A is printed). - // 2. the `--require` flag is then run on the loader thread (and A is printed). - // 3. the `--loader` module is executed (and B is printed). - // 4. the `--import` module is evaluated once, on the main thread (and C is printed). - // 5. the user code is finally executed (and D is printed). - // The worker code should always run before the --import, but the console.log might arrive late. - assert.match(stdout, /^A\r?\n(A\r?\nB\r?\nC|A\r?\nC\r?\nB|C\r?\nA\r?\nB)\r?\nD\r?\n$/); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - - it('should support --require and --import along with using a loader written in ESM and ESM entry point', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--require', - fixtures.path('printA.js'), - '--experimental-loader', - 'data:text/javascript,console.log("B")', - '--import', - fixtures.fileURL('printC.js'), - '--input-type=module', - '--eval', - 'setTimeout(() => console.log("D"),99)', - ]); - - assert.strictEqual(stderr, ''); - // The worker code should always run before the --import, but the console.log might arrive late. - assert.match(stdout, /^A\r?\n(A\r?\nB\r?\nC|A\r?\nC\r?\nB|C\r?\nA\r?\nB)\r?\nD\r?\n$/); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); -}); diff --git a/test/es-module/test-require-esm-from-imported-cjs.js b/test/es-module/test-require-esm-from-imported-cjs.js index bbce967a4b7e..b643923313db 100644 --- a/test/es-module/test-require-esm-from-imported-cjs.js +++ b/test/es-module/test-require-esm-from-imported-cjs.js @@ -20,17 +20,3 @@ spawnSyncAndAssert( stdout: / default: { hello: 'world' }/, } ); - -spawnSyncAndAssert( - process.execPath, - [ - '--experimental-require-module', - '--import', - fixtures.fileURL('es-modules', 'require-esm-in-cjs-cache', 'instrument.js'), - fixtures.path('es-modules', 'require-esm-in-cjs-cache', 'app.cjs'), - ], - { - trim: true, - stdout: / default: { hello: 'world' }/, - } -); diff --git a/test/eslint.config_partial.mjs b/test/eslint.config_partial.mjs index 5800eef9407a..0ef7d93df29d 100644 --- a/test/eslint.config_partial.mjs +++ b/test/eslint.config_partial.mjs @@ -217,7 +217,6 @@ export default [ }, { files: [ - 'test/es-module/test-esm-example-loader.js', 'test/es-module/test-esm-type-flag.js', 'test/es-module/test-esm-type-flag-alias.js', 'test/es-module/test-require-module-detect-entry-point.js', diff --git a/test/fixtures/es-module-loaders/builtin-named-exports-loader.mjs b/test/fixtures/es-module-loaders/builtin-named-exports-loader.mjs deleted file mode 100644 index f1b770d04f4f..000000000000 --- a/test/fixtures/es-module-loaders/builtin-named-exports-loader.mjs +++ /dev/null @@ -1,59 +0,0 @@ -import module from 'node:module'; -import { readFileSync } from 'node:fs'; - -/** @type {string} */ -let GET_BUILTIN; -export function initialize(data) { - GET_BUILTIN = data.GET_BUILTIN; -} - -export async function resolve(specifier, context, next) { - const def = await next(specifier, context); - - if (def.url.startsWith('node:')) { - return { - shortCircuit: true, - url: `custom-${def.url}`, - importAttributes: context.importAttributes, - }; - } - return def; -} - -export function load(url, context, next) { - if (url.startsWith('custom-node:')) { - const urlObj = new URL(url); - return { - shortCircuit: true, - source: generateBuiltinModule(urlObj.pathname), - format: 'commonjs', - }; - } else if (context.format === undefined || context.format === null || context.format === 'commonjs') { - return { - shortCircuit: true, - source: readFileSync(new URL(url)), - format: 'commonjs', - }; - } - return next(url); -} - -function generateBuiltinModule(builtinName) { - const builtinInstance = module._load(builtinName); - const builtinExports = [ - ...Object.keys(builtinInstance), - ]; - return `\ -const $builtinInstance = ${GET_BUILTIN}(${JSON.stringify(builtinName)}); - -module.exports = $builtinInstance; -module.exports.__fromLoader = true; - -// We need this for CJS-module-lexer can parse the exported names. -${ - builtinExports - .map(name => `exports.${name} = $builtinInstance.${name};`) - .join('\n') -} -`; -} diff --git a/test/fixtures/es-module-loaders/builtin-named-exports.mjs b/test/fixtures/es-module-loaders/builtin-named-exports.mjs deleted file mode 100644 index 123b12c26bf0..000000000000 --- a/test/fixtures/es-module-loaders/builtin-named-exports.mjs +++ /dev/null @@ -1,17 +0,0 @@ -import * as fixtures from '../../common/fixtures.mjs'; -import { createRequire, register } from 'node:module'; - -const require = createRequire(import.meta.url); - -const GET_BUILTIN = `$__get_builtin_hole_${Date.now()}`; -Object.defineProperty(globalThis, GET_BUILTIN, { - value: builtinName => require(builtinName), - enumerable: false, - configurable: false, -}); - -register(fixtures.fileURL('es-module-loaders/builtin-named-exports-loader.mjs'), { - data: { - GET_BUILTIN, - }, -}); diff --git a/test/fixtures/es-module-loaders/byop-dummy-loader.mjs b/test/fixtures/es-module-loaders/byop-dummy-loader.mjs deleted file mode 100644 index 17a3430b44e3..000000000000 --- a/test/fixtures/es-module-loaders/byop-dummy-loader.mjs +++ /dev/null @@ -1,30 +0,0 @@ -export function load(url, context, nextLoad) { - switch (url) { - case 'byop://1/index.mjs': - return { - source: 'console.log("index.mjs!")', - format: 'module', - shortCircuit: true, - }; - case 'byop://1/index2.mjs': - return { - source: 'import c from "./sub.mjs"; console.log(c);', - format: 'module', - shortCircuit: true, - }; - case 'byop://1/sub.mjs': - return { - source: 'export default 42', - format: 'module', - shortCircuit: true, - }; - case 'byop://1/index.byoe': - return { - source: 'console.log("index.byoe!")', - format: 'module', - shortCircuit: true, - }; - default: - return nextLoad(url, context); - } -} diff --git a/test/fixtures/es-module-loaders/example-loader.mjs b/test/fixtures/es-module-loaders/example-loader.mjs deleted file mode 100644 index 77d44d55f915..000000000000 --- a/test/fixtures/es-module-loaders/example-loader.mjs +++ /dev/null @@ -1,46 +0,0 @@ -import { URL } from 'url'; -import path from 'path'; -import process from 'process'; -import { builtinModules } from 'module'; - -const JS_EXTENSIONS = new Set(['.js', '.mjs']); - -const baseURL = new URL('file://'); -baseURL.pathname = process.cwd() + '/'; - -export function resolve(specifier, { parentURL = baseURL }, next) { - if (builtinModules.includes(specifier)) { - return { - shortCircuit: true, - url: 'node:' + specifier - }; - } - if (/^\.{1,2}[/]/.test(specifier) !== true && !specifier.startsWith('file:')) { - // For node_modules support: - // return next(specifier); - throw new Error( - `imports must be URLs or begin with './', or '../'; '${specifier}' does not`); - } - const resolved = new URL(specifier, parentURL); - return { - shortCircuit: true, - url: resolved.href - }; -} - -export function getFormat(url, context, defaultGetFormat) { - if (url.startsWith('node:') && builtinModules.includes(url.slice(5))) { - return { - format: 'builtin' - }; - } - const { pathname } = new URL(url); - const ext = path.extname(pathname); - if (!JS_EXTENSIONS.has(ext)) { - throw new Error( - `Cannot load file with non-JavaScript file extension ${ext}.`); - } - return { - format: 'module' - }; -} diff --git a/test/fixtures/es-module-loaders/hook-resolve-type-loader.mjs b/test/fixtures/es-module-loaders/hook-resolve-type-loader.mjs deleted file mode 100644 index c410401876e4..000000000000 --- a/test/fixtures/es-module-loaders/hook-resolve-type-loader.mjs +++ /dev/null @@ -1,25 +0,0 @@ -/** @type {Uint8Array} */ -let data; -/** @type {number} */ -let ESM_MODULE_INDEX; -/** @type {number} */ -let CJS_MODULE_INDEX; - -export function initialize({ sab, ESM_MODULE_INDEX:e, CJS_MODULE_INDEX:c }) { - data = new Uint8Array(sab); - ESM_MODULE_INDEX = e; - CJS_MODULE_INDEX = c; -} - -export async function resolve(specifier, context, next) { - const nextResult = await next(specifier, context); - const { format } = nextResult; - - if (format === 'module' || specifier.endsWith('.mjs')) { - Atomics.add(data, ESM_MODULE_INDEX, 1); - } else if (format == null || format === 'commonjs') { - Atomics.add(data, CJS_MODULE_INDEX, 1); - } - - return nextResult; -} diff --git a/test/fixtures/es-module-loaders/hook-resolve-type.mjs b/test/fixtures/es-module-loaders/hook-resolve-type.mjs deleted file mode 100644 index 7324a08e84b6..000000000000 --- a/test/fixtures/es-module-loaders/hook-resolve-type.mjs +++ /dev/null @@ -1,18 +0,0 @@ -import * as fixtures from '../../common/fixtures.mjs'; -import { register } from 'node:module'; - -const sab = new SharedArrayBuffer(2); -const data = new Uint8Array(sab); - -const ESM_MODULE_INDEX = 0 -const CJS_MODULE_INDEX = 1 - -export function getModuleTypeStats() { - const importedESM = Atomics.load(data, ESM_MODULE_INDEX); - const importedCJS = Atomics.load(data, CJS_MODULE_INDEX); - return { importedESM, importedCJS }; -} - -register(fixtures.fileURL('es-module-loaders/hook-resolve-type-loader.mjs'), { - data: { sab, ESM_MODULE_INDEX, CJS_MODULE_INDEX }, -}); diff --git a/test/fixtures/es-module-loaders/hooks-custom.mjs b/test/fixtures/es-module-loaders/hooks-custom.mjs deleted file mode 100644 index 5109d20f4d37..000000000000 --- a/test/fixtures/es-module-loaders/hooks-custom.mjs +++ /dev/null @@ -1,116 +0,0 @@ -import { pathToFileURL } from 'node:url'; -import count from '../es-modules/stateful.mjs'; - - -// Arbitrary instance of manipulating a module's internal state -// used to assert node-land and user-land have different contexts -count(); - -export function resolve(specifier, { importAttributes }, next) { - let format = ''; - - if (specifier === 'esmHook/format.false') { - format = false; - } - if (specifier === 'esmHook/format.true') { - format = true; - } - if (specifier === 'esmHook/preknownFormat.pre') { - format = 'module'; - } - - if (specifier.startsWith('esmHook')) { - return { - format, - shortCircuit: true, - url: pathToFileURL(specifier).href, - importAttributes, - }; - } - - return next(specifier); -} - -/** - * @param {string} url A fully resolved file url. - * @param {object} context Additional info. - * @param {function} next for now, next is defaultLoad a wrapper for - * defaultGetFormat + defaultGetSource - * @returns {{ format: string, source: (string|SharedArrayBuffer|Uint8Array) }} - */ -export function load(url, context, next) { - // Load all .js files as ESM, regardless of package scope - if (url.endsWith('.js')) { - return next(url, { - ...context, - format: 'module', - }); - } - - if (url.endsWith('.ext')) { - return next(url, { - ...context, - format: 'module', - }); - } - - if (url.endsWith('esmHook/badReturnVal.mjs')) { - return 'export function returnShouldBeObject() {}'; - } - - if (url.endsWith('esmHook/badReturnFormatVal.mjs')) { - return { - format: Array(0), - shortCircuit: true, - source: '', - }; - } - if (url.endsWith('esmHook/unsupportedReturnFormatVal.mjs')) { - return { - format: 'foo', // Not one of the allowable inputs: no translator named 'foo' - shortCircuit: true, - source: '', - }; - } - - if (url.endsWith('esmHook/badReturnSourceVal.mjs')) { - return { - format: 'module', - shortCircuit: true, - source: Array(0), - }; - } - - if (url.endsWith('esmHook/preknownFormat.pre')) { - return { - format: context.format, - shortCircuit: true, - source: `const msg = 'hello world'; export default msg;` - }; - } - - if (url.endsWith('esmHook/virtual.mjs')) { - return { - format: 'module', - shortCircuit: true, - source: `export const message = 'Woohoo!'.toUpperCase();`, - }; - } - - if (url.endsWith('esmHook/commonJsNullSource.mjs')) { - return { - format: 'commonjs', - shortCircuit: true, - source: 1n, - }; - } - - if (url.endsWith('esmHook/maximumCallStack.mjs')) { - function recurse() { - recurse(); - } - recurse(); - } - - return next(url); -} diff --git a/test/fixtures/es-module-loaders/hooks-initialize-port.mjs b/test/fixtures/es-module-loaders/hooks-initialize-port.mjs deleted file mode 100644 index cefe8854297c..000000000000 --- a/test/fixtures/es-module-loaders/hooks-initialize-port.mjs +++ /dev/null @@ -1,16 +0,0 @@ -let thePort = null; - -export async function initialize(port) { - port.postMessage('initialize'); - thePort = port; -} - -export async function resolve(specifier, context, next) { - if (specifier === 'node:fs' || specifier.includes('loader')) { - return next(specifier); - } - - thePort.postMessage(`resolve ${specifier}`); - - return next(specifier); -} diff --git a/test/fixtures/es-module-loaders/hooks-initialize.mjs b/test/fixtures/es-module-loaders/hooks-initialize.mjs deleted file mode 100644 index 7622d982a9d7..000000000000 --- a/test/fixtures/es-module-loaders/hooks-initialize.mjs +++ /dev/null @@ -1,7 +0,0 @@ -import { writeFileSync } from 'node:fs'; - -let counter = 0; - -export async function initialize() { - writeFileSync(1, `hooks initialize ${++counter}\n`); -} diff --git a/test/fixtures/es-module-loaders/hooks-input.mjs b/test/fixtures/es-module-loaders/hooks-input.mjs deleted file mode 100644 index e4eb159b3987..000000000000 --- a/test/fixtures/es-module-loaders/hooks-input.mjs +++ /dev/null @@ -1,94 +0,0 @@ -// This is expected to be used by test-esm-loader-hooks.mjs via: -// node --loader ./test/fixtures/es-module-loaders/hooks-input.mjs ./test/fixtures/es-modules/json-modules.mjs - -import assert from 'assert'; -import { writeSync } from 'fs'; -import { readFile } from 'fs/promises'; -import { fileURLToPath } from 'url'; - - -let resolveCalls = 0; -let loadCalls = 0; - -export async function resolve(specifier, context, next) { - resolveCalls++; - let url; - - if (resolveCalls === 1) { - url = new URL(specifier).href; - assert.match(specifier, /json-modules\.mjs$/); - - if (!(/\[eval\d*\]$/).test(context.parentURL)) { - assert.strictEqual(context.parentURL, undefined); - } - - assert.deepStrictEqual(context.importAttributes, {}); - } else if (resolveCalls === 2) { - url = new URL(specifier, context.parentURL).href; - assert.match(specifier, /experimental\.json$/); - assert.match(context.parentURL, /json-modules\.mjs$/); - assert.deepStrictEqual(context.importAttributes, { - type: 'json', - }); - } else { - throw new Error(`Unexpected resolve call: ${specifier}`); - } - - // Ensure `context` has all and only the properties it's supposed to - assert.deepStrictEqual(Reflect.ownKeys(context), [ - 'conditions', - 'importAttributes', - 'parentURL', - 'importAssertions', - ]); - assert.ok(Array.isArray(context.conditions)); - assert.strictEqual(typeof next, 'function'); - - const returnValue = { - url, - format: 'test', - shortCircuit: true, - } - - writeSync(1, JSON.stringify(returnValue) + '\n'); // For the test to validate when it parses stdout - - return returnValue; -} - -export async function load(url, context, next) { - loadCalls++; - const source = await readFile(fileURLToPath(url)); - let format; - - if (loadCalls === 1) { - assert.match(url, /json-modules\.mjs$/); - assert.deepStrictEqual(context.importAttributes, {}); - format = 'module'; - } else if (loadCalls === 2) { - assert.match(url, /experimental\.json$/); - assert.deepStrictEqual(context.importAttributes, { - type: 'json', - }); - format = 'json'; - } - - assert.ok(new URL(url)); - // Ensure `context` has all and only the properties it's supposed to - assert.deepStrictEqual(Reflect.ownKeys(context), [ - 'format', - 'importAttributes', - 'importAssertions', - ]); - assert.strictEqual(context.format, 'test'); - assert.strictEqual(typeof next, 'function'); - - const returnValue = { - source, - format, - shortCircuit: true, - }; - - writeSync(1, JSON.stringify(returnValue) + '\n'); // For the test to validate when it parses stdout - - return returnValue; -} diff --git a/test/fixtures/es-module-loaders/http-loader.mjs b/test/fixtures/es-module-loaders/http-loader.mjs deleted file mode 100644 index 4fe8fcb47974..000000000000 --- a/test/fixtures/es-module-loaders/http-loader.mjs +++ /dev/null @@ -1,22 +0,0 @@ -import { get } from 'http'; - -export function load(url, context, nextLoad) { - if (url.startsWith('http://')) { - return new Promise((resolve, reject) => { - get(url, (rsp) => { - let data = ''; - rsp.on('data', (chunk) => data += chunk); - rsp.on('end', () => { - resolve({ - format: 'module', - shortCircuit: true, - source: data, - }); - }); - }) - .on('error', reject); - }); - } - - return nextLoad(url); -} diff --git a/test/fixtures/es-module-loaders/js-as-esm.js b/test/fixtures/es-module-loaders/js-as-esm.js deleted file mode 100644 index b4d2741b2fc5..000000000000 --- a/test/fixtures/es-module-loaders/js-as-esm.js +++ /dev/null @@ -1 +0,0 @@ -export const namedExport = 'named-export'; diff --git a/test/fixtures/es-module-loaders/loader-delayed-async-load.mjs b/test/fixtures/es-module-loaders/loader-delayed-async-load.mjs deleted file mode 100644 index 33bae9bdafd9..000000000000 --- a/test/fixtures/es-module-loaders/loader-delayed-async-load.mjs +++ /dev/null @@ -1,3 +0,0 @@ -export function load(a, b, c) { - return new Promise(d => setTimeout(() => d(c(a, b)), 99)); -} diff --git a/test/fixtures/es-module-loaders/loader-dep.js b/test/fixtures/es-module-loaders/loader-dep.js deleted file mode 100644 index c8154ac5dbe6..000000000000 --- a/test/fixtures/es-module-loaders/loader-dep.js +++ /dev/null @@ -1 +0,0 @@ -exports.format = 'module'; diff --git a/test/fixtures/es-module-loaders/loader-edge-cases.mjs b/test/fixtures/es-module-loaders/loader-edge-cases.mjs deleted file mode 100644 index 19af0e2bc078..000000000000 --- a/test/fixtures/es-module-loaders/loader-edge-cases.mjs +++ /dev/null @@ -1,13 +0,0 @@ -import { strictEqual } from "node:assert"; -import { isMainThread, workerData, parentPort } from "node:worker_threads"; - -strictEqual(isMainThread, false); - -// We want to make sure that internals are not leaked on the public module: -strictEqual(workerData, null); -strictEqual(parentPort, null); - -// We don't want `import.meta.resolve` being available from loaders -// as the sync implementation is not compatible with calling async -// functions on the same thread. -strictEqual(typeof import.meta.resolve, 'undefined'); diff --git a/test/fixtures/es-module-loaders/loader-exit-on-load.mjs b/test/fixtures/es-module-loaders/loader-exit-on-load.mjs deleted file mode 100644 index 4ee413fa46ce..000000000000 --- a/test/fixtures/es-module-loaders/loader-exit-on-load.mjs +++ /dev/null @@ -1,4 +0,0 @@ -export function load(a, b, next) { - if (a === 'data:exit') process.exit(42); - return next(a, b); -} diff --git a/test/fixtures/es-module-loaders/loader-exit-on-resolve.mjs b/test/fixtures/es-module-loaders/loader-exit-on-resolve.mjs deleted file mode 100644 index cffa4bcc486a..000000000000 --- a/test/fixtures/es-module-loaders/loader-exit-on-resolve.mjs +++ /dev/null @@ -1,4 +0,0 @@ -export function resolve(a, b, next) { - if (a === 'exit:') process.exit(42); - return next(a, b); -} diff --git a/test/fixtures/es-module-loaders/loader-exit-top-level.mjs b/test/fixtures/es-module-loaders/loader-exit-top-level.mjs deleted file mode 100644 index 6427ca06037f..000000000000 --- a/test/fixtures/es-module-loaders/loader-exit-top-level.mjs +++ /dev/null @@ -1 +0,0 @@ -process.exit(42); diff --git a/test/fixtures/es-module-loaders/loader-globalpreload-and-initialize.mjs b/test/fixtures/es-module-loaders/loader-globalpreload-and-initialize.mjs deleted file mode 100644 index 7055b90f4e75..000000000000 --- a/test/fixtures/es-module-loaders/loader-globalpreload-and-initialize.mjs +++ /dev/null @@ -1,2 +0,0 @@ -export function globalPreload() {} -export function initialize() {} diff --git a/test/fixtures/es-module-loaders/loader-globalpreload-only.mjs b/test/fixtures/es-module-loaders/loader-globalpreload-only.mjs deleted file mode 100644 index 6b7f59d63198..000000000000 --- a/test/fixtures/es-module-loaders/loader-globalpreload-only.mjs +++ /dev/null @@ -1 +0,0 @@ -export function globalPreload() {} diff --git a/test/fixtures/es-module-loaders/loader-globalpreload-with-return.mjs b/test/fixtures/es-module-loaders/loader-globalpreload-with-return.mjs deleted file mode 100644 index f54cf72ce5b9..000000000000 --- a/test/fixtures/es-module-loaders/loader-globalpreload-with-return.mjs +++ /dev/null @@ -1,3 +0,0 @@ -export function globalPreload() { - return ''; -} diff --git a/test/fixtures/es-module-loaders/loader-initialize-exit.mjs b/test/fixtures/es-module-loaders/loader-initialize-exit.mjs deleted file mode 100644 index 53cb4fa26f01..000000000000 --- a/test/fixtures/es-module-loaders/loader-initialize-exit.mjs +++ /dev/null @@ -1,3 +0,0 @@ -export function initialize() { - process.exit(42); -} diff --git a/test/fixtures/es-module-loaders/loader-initialize-never-settling.mjs b/test/fixtures/es-module-loaders/loader-initialize-never-settling.mjs deleted file mode 100644 index 80033b663a5a..000000000000 --- a/test/fixtures/es-module-loaders/loader-initialize-never-settling.mjs +++ /dev/null @@ -1,3 +0,0 @@ -export function initialize() { - return new Promise(() => {}); -} diff --git a/test/fixtures/es-module-loaders/loader-initialize-rejecting.mjs b/test/fixtures/es-module-loaders/loader-initialize-rejecting.mjs deleted file mode 100644 index 899accc15114..000000000000 --- a/test/fixtures/es-module-loaders/loader-initialize-rejecting.mjs +++ /dev/null @@ -1,3 +0,0 @@ -export function initialize() { - return Promise.reject(); -} diff --git a/test/fixtures/es-module-loaders/loader-initialize-throw-null.mjs b/test/fixtures/es-module-loaders/loader-initialize-throw-null.mjs deleted file mode 100644 index 3d834b5c23cf..000000000000 --- a/test/fixtures/es-module-loaders/loader-initialize-throw-null.mjs +++ /dev/null @@ -1,3 +0,0 @@ -export function initialize() { - throw null; -} diff --git a/test/fixtures/es-module-loaders/loader-invalid-format.mjs b/test/fixtures/es-module-loaders/loader-invalid-format.mjs deleted file mode 100644 index dc61a792b2be..000000000000 --- a/test/fixtures/es-module-loaders/loader-invalid-format.mjs +++ /dev/null @@ -1,20 +0,0 @@ -export async function resolve(specifier, { parentURL, importAttributes }, next) { - if (parentURL && specifier === '../fixtures/es-modules/test-esm-ok.mjs') { - return { - shortCircuit: true, - url: 'file:///asdf', - }; - } - return next(specifier); -} - -export async function load(url, context, next) { - if (url === 'file:///asdf') { - return { - format: 'esm', - shortCircuit: true, - source: '', - } - } - return next(url); -} diff --git a/test/fixtures/es-module-loaders/loader-invalid-url.mjs b/test/fixtures/es-module-loaders/loader-invalid-url.mjs deleted file mode 100644 index aac2b16b6f58..000000000000 --- a/test/fixtures/es-module-loaders/loader-invalid-url.mjs +++ /dev/null @@ -1,10 +0,0 @@ -export async function resolve(specifier, { parentURL, importAttributes }, next) { - if (parentURL && specifier === '../fixtures/es-modules/test-esm-ok.mjs') { - return { - shortCircuit: true, - url: specifier, - importAttributes, - }; - } - return next(specifier); -} diff --git a/test/fixtures/es-module-loaders/loader-load-bad-next-context.mjs b/test/fixtures/es-module-loaders/loader-load-bad-next-context.mjs deleted file mode 100644 index fe38da04f9ff..000000000000 --- a/test/fixtures/es-module-loaders/loader-load-bad-next-context.mjs +++ /dev/null @@ -1,3 +0,0 @@ -export async function load(url, context, next) { - return next(url, []); -} diff --git a/test/fixtures/es-module-loaders/loader-load-bad-next-url.mjs b/test/fixtures/es-module-loaders/loader-load-bad-next-url.mjs deleted file mode 100644 index c6a4c7504d43..000000000000 --- a/test/fixtures/es-module-loaders/loader-load-bad-next-url.mjs +++ /dev/null @@ -1,3 +0,0 @@ -export async function load(url, context, next) { - return next([]); -} diff --git a/test/fixtures/es-module-loaders/loader-load-commonjs-with-source.mjs b/test/fixtures/es-module-loaders/loader-load-commonjs-with-source.mjs deleted file mode 100644 index 3f431e630cdd..000000000000 --- a/test/fixtures/es-module-loaders/loader-load-commonjs-with-source.mjs +++ /dev/null @@ -1,13 +0,0 @@ -import { readFile } from 'node:fs/promises'; -import { fileURLToPath } from 'node:url'; - -export async function load(url, context, nextLoad) { - const result = await nextLoad(url, context); - if (url.endsWith('/common/index.js')) { - result.source = '"use strict";module.exports=require("node:module").createRequire(' + - JSON.stringify(url) + ')(' + JSON.stringify(fileURLToPath(url)) + ');\n'; - } else if (url.startsWith('file:') && (context.format == null || context.format === 'commonjs')) { - result.source = await readFile(new URL(url)); - } - return result; -} diff --git a/test/fixtures/es-module-loaders/loader-load-dynamic-import.mjs b/test/fixtures/es-module-loaders/loader-load-dynamic-import.mjs deleted file mode 100644 index 96af5507d172..000000000000 --- a/test/fixtures/es-module-loaders/loader-load-dynamic-import.mjs +++ /dev/null @@ -1,14 +0,0 @@ -import { writeSync } from 'node:fs'; - - -export async function load(url, context, next) { - if (url === 'node:fs' || url.includes('loader')) { - return next(url); - } - - // Here for asserting dynamic import - await import('xxx/loader-load-passthru.mjs'); - - writeSync(1, 'load dynamic import' + '\n'); // Signal that this specific hook ran - return next(url, context); -} diff --git a/test/fixtures/es-module-loaders/loader-load-foo-or-42.mjs b/test/fixtures/es-module-loaders/loader-load-foo-or-42.mjs deleted file mode 100644 index 285b81a910ef..000000000000 --- a/test/fixtures/es-module-loaders/loader-load-foo-or-42.mjs +++ /dev/null @@ -1,19 +0,0 @@ -export async function load(url, context, next) { - // This check is needed to make sure that we don't prevent the - // resolution from follow-up loaders. It wouldn't be a problem - // in real life because loaders aren't supposed to break the - // resolution, but the ones used in our tests do, for convenience. - if (url === 'node:fs' || url.includes('loader')) { - return next(url); - } - - const val = url.includes('42') - ? '42' - : '"foo"'; - - return { - format: 'module', - shortCircuit: true, - source: `export default ${val}`, - }; -} diff --git a/test/fixtures/es-module-loaders/loader-load-impersonating-next-url.mjs b/test/fixtures/es-module-loaders/loader-load-impersonating-next-url.mjs deleted file mode 100644 index 1028e093e468..000000000000 --- a/test/fixtures/es-module-loaders/loader-load-impersonating-next-url.mjs +++ /dev/null @@ -1,3 +0,0 @@ -export async function load(url, context, next) { - return next('not/a/url'); -} diff --git a/test/fixtures/es-module-loaders/loader-load-incomplete.mjs b/test/fixtures/es-module-loaders/loader-load-incomplete.mjs deleted file mode 100644 index bf6d5ea254dc..000000000000 --- a/test/fixtures/es-module-loaders/loader-load-incomplete.mjs +++ /dev/null @@ -1,14 +0,0 @@ -export async function load(url, context, next) { - // This check is needed to make sure that we don't prevent the - // resolution from follow-up loaders. It wouldn't be a problem - // in real life because loaders aren't supposed to break the - // resolution, but the ones used in our tests do, for convenience. - if (url === 'node:fs' || url.includes('loader')) { - return next(url); - } - - return { - format: 'module', - source: 'export default 42', - }; -} diff --git a/test/fixtures/es-module-loaders/loader-load-next-modified.mjs b/test/fixtures/es-module-loaders/loader-load-next-modified.mjs deleted file mode 100644 index 401b52971311..000000000000 --- a/test/fixtures/es-module-loaders/loader-load-next-modified.mjs +++ /dev/null @@ -1,11 +0,0 @@ -export async function load(url, context, next) { - const { - format, - source, - } = await next(url); - - return { - format, - source: source + 1, - }; -} diff --git a/test/fixtures/es-module-loaders/loader-load-null-return.mjs b/test/fixtures/es-module-loaders/loader-load-null-return.mjs deleted file mode 100644 index 252eec4ebc28..000000000000 --- a/test/fixtures/es-module-loaders/loader-load-null-return.mjs +++ /dev/null @@ -1,3 +0,0 @@ -export async function load(specifier, context, next) { - return null; -} diff --git a/test/fixtures/es-module-loaders/loader-load-passing-modified-context.mjs b/test/fixtures/es-module-loaders/loader-load-passing-modified-context.mjs deleted file mode 100644 index 7676be766af5..000000000000 --- a/test/fixtures/es-module-loaders/loader-load-passing-modified-context.mjs +++ /dev/null @@ -1,6 +0,0 @@ -export async function load(url, context, next) { - return next(url, { - ...context, - foo: 'bar', - }); -} diff --git a/test/fixtures/es-module-loaders/loader-load-passthru.mjs b/test/fixtures/es-module-loaders/loader-load-passthru.mjs deleted file mode 100644 index 72ff6b56122d..000000000000 --- a/test/fixtures/es-module-loaders/loader-load-passthru.mjs +++ /dev/null @@ -1,14 +0,0 @@ -import { writeSync } from 'node:fs'; - -export async function load(url, context, next) { - // This check is needed to make sure that we don't prevent the - // resolution from follow-up loaders. It wouldn't be a problem - // in real life because loaders aren't supposed to break the - // resolution, but the ones used in our tests do, for convenience. - if (url === 'node:fs' || url.includes('loader')) { - return next(url); - } - - writeSync(1, 'load passthru' + '\n'); // Signal that this specific hook ran - return next(url); -} diff --git a/test/fixtures/es-module-loaders/loader-load-receiving-modified-context.mjs b/test/fixtures/es-module-loaders/loader-load-receiving-modified-context.mjs deleted file mode 100644 index 6e5a1ee71936..000000000000 --- a/test/fixtures/es-module-loaders/loader-load-receiving-modified-context.mjs +++ /dev/null @@ -1,7 +0,0 @@ -import { writeSync } from 'node:fs'; - - -export async function load(url, context, next) { - writeSync(1, context.foo + '\n'); // Expose actual value the hook was called with - return next(url, context); -} diff --git a/test/fixtures/es-module-loaders/loader-load-source-maps.mjs b/test/fixtures/es-module-loaders/loader-load-source-maps.mjs deleted file mode 100644 index bf5ef6b91028..000000000000 --- a/test/fixtures/es-module-loaders/loader-load-source-maps.mjs +++ /dev/null @@ -1,9 +0,0 @@ -import { readFile } from 'node:fs/promises'; - -export async function load(url, context, nextLoad) { - const resolved = await nextLoad(url, context); - if (context.format === 'commonjs') { - resolved.source = await readFile(new URL(url)); - } - return resolved; -} diff --git a/test/fixtures/es-module-loaders/loader-log-args.mjs b/test/fixtures/es-module-loaders/loader-log-args.mjs deleted file mode 100644 index b56fddbce585..000000000000 --- a/test/fixtures/es-module-loaders/loader-log-args.mjs +++ /dev/null @@ -1,31 +0,0 @@ -import { writeSync } from 'node:fs'; -import { inspect } from 'node:util' - -export async function resolve(...args) { - writeSync(1, `resolve arg count: ${args.length}\n`); - writeSync(1, inspect({ - specifier: args[0], - context: args[1], - next: args[2], - }) + '\n'); - - return { - shortCircuit: true, - url: args[0], - }; -} - -export async function load(...args) { - writeSync(1, `load arg count: ${args.length}\n`); - writeSync(1, inspect({ - url: args[0], - context: args[1], - next: args[2], - }) + '\n'); - - return { - format: 'module', - source: '', - shortCircuit: true, - }; -} diff --git a/test/fixtures/es-module-loaders/loader-mixed-opt-in.mjs b/test/fixtures/es-module-loaders/loader-mixed-opt-in.mjs deleted file mode 100644 index 69b17a88249b..000000000000 --- a/test/fixtures/es-module-loaders/loader-mixed-opt-in.mjs +++ /dev/null @@ -1,28 +0,0 @@ -import { createRequire } from 'module'; - -const require = createRequire(import.meta.url); -const fixtures = require('../../common/fixtures.js'); - -const fixturesPath = fixtures.path('empty.js'); - -export function resolve(s, c, n) { - if (s.endsWith('entry-point')) { - return { - shortCircuit: true, - url: 'file:///c:/virtual-entry-point', - format: 'commonjs', - }; - } - return n(s, c); -} - -export async function load(u, c, n) { - if (u === 'file:///c:/virtual-entry-point') { - return { - shortCircuit: true, - source: `"use strict";require(${JSON.stringify(fixturesPath)});console.log("Hello");`, - format: 'commonjs', - }; - } - return n(u, c); -} diff --git a/test/fixtures/es-module-loaders/loader-resolve-42.mjs b/test/fixtures/es-module-loaders/loader-resolve-42.mjs deleted file mode 100644 index b571a27a9787..000000000000 --- a/test/fixtures/es-module-loaders/loader-resolve-42.mjs +++ /dev/null @@ -1,17 +0,0 @@ -import { writeSync } from 'node:fs'; - - -export async function resolve(specifier, context, next) { - // This check is needed to make sure that we don't prevent the - // resolution from follow-up loaders. It wouldn't be a problem - // in real life because loaders aren't supposed to break the - // resolution, but the ones used in our tests do, for convenience. - if (specifier === 'node:fs' || specifier.includes('loader')) { - return next(specifier); - } - - writeSync(1, 'resolve 42' + '\n'); // Signal that this specific hook ran - writeSync(1, `next: ${next.name}\n`); // Expose actual value the hook was called with - - return next('file:///42.mjs'); -} diff --git a/test/fixtures/es-module-loaders/loader-resolve-bad-next-context.mjs b/test/fixtures/es-module-loaders/loader-resolve-bad-next-context.mjs deleted file mode 100644 index 881f5875dd02..000000000000 --- a/test/fixtures/es-module-loaders/loader-resolve-bad-next-context.mjs +++ /dev/null @@ -1,3 +0,0 @@ -export async function resolve(specifier, context, next) { - return next(specifier, []); -} diff --git a/test/fixtures/es-module-loaders/loader-resolve-bad-next-specifier.mjs b/test/fixtures/es-module-loaders/loader-resolve-bad-next-specifier.mjs deleted file mode 100644 index 66c941754f90..000000000000 --- a/test/fixtures/es-module-loaders/loader-resolve-bad-next-specifier.mjs +++ /dev/null @@ -1,3 +0,0 @@ -export async function resolve(specifier, context, next) { - return next([]); -} diff --git a/test/fixtures/es-module-loaders/loader-resolve-dynamic-import.mjs b/test/fixtures/es-module-loaders/loader-resolve-dynamic-import.mjs deleted file mode 100644 index edc2303ed9aa..000000000000 --- a/test/fixtures/es-module-loaders/loader-resolve-dynamic-import.mjs +++ /dev/null @@ -1,14 +0,0 @@ -import { writeSync } from 'node:fs'; - - -export async function resolve(specifier, context, next) { - if (specifier === 'node:fs' || specifier.includes('loader')) { - return next(specifier); - } - - // Here for asserting dynamic import - await import('xxx/loader-resolve-passthru.mjs'); - - writeSync(1, 'resolve dynamic import' + '\n'); // Signal that this specific hook ran - return next(specifier); -} diff --git a/test/fixtures/es-module-loaders/loader-resolve-foo.mjs b/test/fixtures/es-module-loaders/loader-resolve-foo.mjs deleted file mode 100644 index b5e5a419b99d..000000000000 --- a/test/fixtures/es-module-loaders/loader-resolve-foo.mjs +++ /dev/null @@ -1,15 +0,0 @@ -import { writeSync } from 'node:fs'; - - -export async function resolve(specifier, context, next) { - // This check is needed to make sure that we don't prevent the - // resolution from follow-up loaders. It wouldn't be a problem - // in real life because loaders aren't supposed to break the - // resolution, but the ones used in our tests do, for convenience. - if (specifier === 'node:fs' || specifier.includes('loader')) { - return next(specifier); - } - - writeSync(1, 'resolve foo' + '\n'); // Signal that this specific hook ran - return next('file:///foo.mjs'); -} diff --git a/test/fixtures/es-module-loaders/loader-resolve-incomplete.mjs b/test/fixtures/es-module-loaders/loader-resolve-incomplete.mjs deleted file mode 100644 index cb37a4352718..000000000000 --- a/test/fixtures/es-module-loaders/loader-resolve-incomplete.mjs +++ /dev/null @@ -1,13 +0,0 @@ -export async function resolve(specifier, context, next) { - // This check is needed to make sure that we don't prevent the - // resolution from follow-up loaders. It wouldn't be a problem - // in real life because loaders aren't supposed to break the - // resolution, but the ones used in our tests do, for convenience. - if (specifier === 'node:fs' || specifier.includes('loader')) { - return next(specifier); - } - - return { - url: 'file:///incomplete-resolve-chain.js', - }; -} diff --git a/test/fixtures/es-module-loaders/loader-resolve-multiple-next-calls.mjs b/test/fixtures/es-module-loaders/loader-resolve-multiple-next-calls.mjs deleted file mode 100644 index 91dbec251ede..000000000000 --- a/test/fixtures/es-module-loaders/loader-resolve-multiple-next-calls.mjs +++ /dev/null @@ -1,9 +0,0 @@ -export async function resolve(specifier, context, next) { - const { url: first } = await next(specifier); - const { url: second } = await next(specifier); - - return { - format: 'module', - url: first, - }; -} diff --git a/test/fixtures/es-module-loaders/loader-resolve-next-modified.mjs b/test/fixtures/es-module-loaders/loader-resolve-next-modified.mjs deleted file mode 100644 index a6b8e85455d3..000000000000 --- a/test/fixtures/es-module-loaders/loader-resolve-next-modified.mjs +++ /dev/null @@ -1,19 +0,0 @@ -export async function resolve(url, context, next) { - // This check is needed to make sure that we don't prevent the - // resolution from follow-up loaders. It wouldn't be a problem - // in real life because loaders aren't supposed to break the - // resolution, but the ones used in our tests do, for convenience. - if (url === 'node:fs' || url.includes('loader')) { - return next(url); - } - - const { - format, - url: nextUrl, - } = await next(url, context); - - return { - format, - url: `${nextUrl}?foo`, - }; -} diff --git a/test/fixtures/es-module-loaders/loader-resolve-null-return.mjs b/test/fixtures/es-module-loaders/loader-resolve-null-return.mjs deleted file mode 100644 index 16c9826a8ba5..000000000000 --- a/test/fixtures/es-module-loaders/loader-resolve-null-return.mjs +++ /dev/null @@ -1,3 +0,0 @@ -export async function resolve(specifier, context, next) { - return null; -} diff --git a/test/fixtures/es-module-loaders/loader-resolve-passing-modified-context.mjs b/test/fixtures/es-module-loaders/loader-resolve-passing-modified-context.mjs deleted file mode 100644 index 6a92a6cd8f6a..000000000000 --- a/test/fixtures/es-module-loaders/loader-resolve-passing-modified-context.mjs +++ /dev/null @@ -1,6 +0,0 @@ -export async function resolve(specifier, context, next) { - return next(specifier, { - ...context, - foo: 'bar', - }); -} diff --git a/test/fixtures/es-module-loaders/loader-resolve-passthru.mjs b/test/fixtures/es-module-loaders/loader-resolve-passthru.mjs deleted file mode 100644 index f388f0db273d..000000000000 --- a/test/fixtures/es-module-loaders/loader-resolve-passthru.mjs +++ /dev/null @@ -1,14 +0,0 @@ -import { writeSync } from 'node:fs'; - -export async function resolve(specifier, context, next) { - // This check is needed to make sure that we don't prevent the - // resolution from follow-up loaders. It wouldn't be a problem - // in real life because loaders aren't supposed to break the - // resolution, but the ones used in our tests do, for convenience. - if (specifier === 'node:fs' || specifier.includes('loader')) { - return next(specifier); - } - - writeSync(1, 'resolve passthru' + '\n'); // Signal that this specific hook ran - return next(specifier); -} diff --git a/test/fixtures/es-module-loaders/loader-resolve-receiving-modified-context.mjs b/test/fixtures/es-module-loaders/loader-resolve-receiving-modified-context.mjs deleted file mode 100644 index f6a8fdad53ec..000000000000 --- a/test/fixtures/es-module-loaders/loader-resolve-receiving-modified-context.mjs +++ /dev/null @@ -1,7 +0,0 @@ -import { writeSync } from 'node:fs'; - - -export async function resolve(specifier, context, next) { - writeSync(1, context.foo + '\n'); // Expose actual value the hook was called with - return next(specifier, context); -} diff --git a/test/fixtures/es-module-loaders/loader-resolve-shortcircuit.mjs b/test/fixtures/es-module-loaders/loader-resolve-shortcircuit.mjs deleted file mode 100644 index 00c18e75e2c7..000000000000 --- a/test/fixtures/es-module-loaders/loader-resolve-shortcircuit.mjs +++ /dev/null @@ -1,14 +0,0 @@ -export async function resolve(specifier, context, next) { - // This check is needed to make sure that we don't prevent the - // resolution from follow-up loaders. It wouldn't be a problem - // in real life because loaders aren't supposed to break the - // resolution, but the ones used in our tests do, for convenience. - if (specifier === 'node:fs' || specifier.includes('loader')) { - return next(specifier); - } - - return { - shortCircuit: true, - url: specifier, - } -} diff --git a/test/fixtures/es-module-loaders/loader-resolve-strip-xxx.mjs b/test/fixtures/es-module-loaders/loader-resolve-strip-xxx.mjs deleted file mode 100644 index 996dfd041c0d..000000000000 --- a/test/fixtures/es-module-loaders/loader-resolve-strip-xxx.mjs +++ /dev/null @@ -1,10 +0,0 @@ -import { writeSync } from 'node:fs'; -import { inspect } from 'node:util'; - -export async function resolve(specifier, context, nextResolve) { - if (specifier.startsWith('node:')) { - return nextResolve(specifier); - } - writeSync(1, `loader-a ${inspect({specifier})}\n`); - return nextResolve(specifier.replace(/^xxx\//, `./`)); -} diff --git a/test/fixtures/es-module-loaders/loader-resolve-strip-yyy.mjs b/test/fixtures/es-module-loaders/loader-resolve-strip-yyy.mjs deleted file mode 100644 index 7746469bf817..000000000000 --- a/test/fixtures/es-module-loaders/loader-resolve-strip-yyy.mjs +++ /dev/null @@ -1,7 +0,0 @@ -import { writeSync } from 'node:fs'; -import { inspect } from 'node:util'; - -export async function resolve(specifier, context, nextResolve) { - writeSync(1, `loader-b ${inspect({specifier})}\n`); - return nextResolve(specifier.replace(/^yyy\//, `./`)); -} diff --git a/test/fixtures/es-module-loaders/loader-shared-dep.mjs b/test/fixtures/es-module-loaders/loader-shared-dep.mjs deleted file mode 100644 index d41c1ae40339..000000000000 --- a/test/fixtures/es-module-loaders/loader-shared-dep.mjs +++ /dev/null @@ -1,11 +0,0 @@ -import assert from 'assert'; - -import { createRequire } from '../../common/index.mjs'; - -const require = createRequire(import.meta.url); -const dep = require('./loader-dep.js'); - -export function resolve(specifier, context, next) { - assert.strictEqual(dep.format, 'module'); - return next(specifier); -} diff --git a/test/fixtures/es-module-loaders/loader-this-value-inside-hook-functions.mjs b/test/fixtures/es-module-loaders/loader-this-value-inside-hook-functions.mjs deleted file mode 100644 index 2be18c4969ef..000000000000 --- a/test/fixtures/es-module-loaders/loader-this-value-inside-hook-functions.mjs +++ /dev/null @@ -1,21 +0,0 @@ -export function initialize() { - if (this != null) { - throw new Error('hook function must not be bound to loader instance'); - } -} - -export function resolve(url, _, next) { - if (this != null) { - throw new Error('hook function must not be bound to loader instance'); - } - - return next(url); -} - -export function load(url, _, next) { - if (this != null) { - throw new Error('hook function must not be bound to loader instance'); - } - - return next(url); -} diff --git a/test/fixtures/es-module-loaders/loader-throw-bigint.mjs b/test/fixtures/es-module-loaders/loader-throw-bigint.mjs deleted file mode 100644 index 700b0387fa7f..000000000000 --- a/test/fixtures/es-module-loaders/loader-throw-bigint.mjs +++ /dev/null @@ -1 +0,0 @@ -throw 1n; diff --git a/test/fixtures/es-module-loaders/loader-throw-boolean.mjs b/test/fixtures/es-module-loaders/loader-throw-boolean.mjs deleted file mode 100644 index 874fbd92e5fa..000000000000 --- a/test/fixtures/es-module-loaders/loader-throw-boolean.mjs +++ /dev/null @@ -1 +0,0 @@ -throw true; diff --git a/test/fixtures/es-module-loaders/loader-throw-empty-object.mjs b/test/fixtures/es-module-loaders/loader-throw-empty-object.mjs deleted file mode 100644 index 778886fcb90a..000000000000 --- a/test/fixtures/es-module-loaders/loader-throw-empty-object.mjs +++ /dev/null @@ -1 +0,0 @@ -throw {}; diff --git a/test/fixtures/es-module-loaders/loader-throw-error.mjs b/test/fixtures/es-module-loaders/loader-throw-error.mjs deleted file mode 100644 index 816051b6c783..000000000000 --- a/test/fixtures/es-module-loaders/loader-throw-error.mjs +++ /dev/null @@ -1 +0,0 @@ -throw new Error('error message'); diff --git a/test/fixtures/es-module-loaders/loader-throw-function.mjs b/test/fixtures/es-module-loaders/loader-throw-function.mjs deleted file mode 100644 index 35503d670976..000000000000 --- a/test/fixtures/es-module-loaders/loader-throw-function.mjs +++ /dev/null @@ -1 +0,0 @@ -throw function fnName() {}; diff --git a/test/fixtures/es-module-loaders/loader-throw-null.mjs b/test/fixtures/es-module-loaders/loader-throw-null.mjs deleted file mode 100644 index 37d3d14b8bfe..000000000000 --- a/test/fixtures/es-module-loaders/loader-throw-null.mjs +++ /dev/null @@ -1 +0,0 @@ -throw null; diff --git a/test/fixtures/es-module-loaders/loader-throw-number.mjs b/test/fixtures/es-module-loaders/loader-throw-number.mjs deleted file mode 100644 index 6dbc3994c7ac..000000000000 --- a/test/fixtures/es-module-loaders/loader-throw-number.mjs +++ /dev/null @@ -1 +0,0 @@ -throw 1; diff --git a/test/fixtures/es-module-loaders/loader-throw-object.mjs b/test/fixtures/es-module-loaders/loader-throw-object.mjs deleted file mode 100644 index fa7c9b7d43af..000000000000 --- a/test/fixtures/es-module-loaders/loader-throw-object.mjs +++ /dev/null @@ -1 +0,0 @@ -throw { fn() {}, symbol: Symbol('symbol'), u: undefined }; diff --git a/test/fixtures/es-module-loaders/loader-throw-string.mjs b/test/fixtures/es-module-loaders/loader-throw-string.mjs deleted file mode 100644 index 613a4321f50d..000000000000 --- a/test/fixtures/es-module-loaders/loader-throw-string.mjs +++ /dev/null @@ -1 +0,0 @@ -throw 'literal string'; diff --git a/test/fixtures/es-module-loaders/loader-throw-symbol.mjs b/test/fixtures/es-module-loaders/loader-throw-symbol.mjs deleted file mode 100644 index 05876e0b4ada..000000000000 --- a/test/fixtures/es-module-loaders/loader-throw-symbol.mjs +++ /dev/null @@ -1 +0,0 @@ -throw Symbol('symbol descriptor'); diff --git a/test/fixtures/es-module-loaders/loader-throw-undefined.mjs b/test/fixtures/es-module-loaders/loader-throw-undefined.mjs deleted file mode 100644 index 38ecbdff9801..000000000000 --- a/test/fixtures/es-module-loaders/loader-throw-undefined.mjs +++ /dev/null @@ -1 +0,0 @@ -throw undefined; diff --git a/test/fixtures/es-module-loaders/loader-unknown-builtin-module.mjs b/test/fixtures/es-module-loaders/loader-unknown-builtin-module.mjs deleted file mode 100644 index 65e1adf909d0..000000000000 --- a/test/fixtures/es-module-loaders/loader-unknown-builtin-module.mjs +++ /dev/null @@ -1,7 +0,0 @@ -export function resolve(specifier, context, next) { - if (specifier === 'unknown-builtin-module') return { - url: 'node:unknown-builtin-module' - }; - - return next(specifier); -} diff --git a/test/fixtures/es-module-loaders/loader-with-custom-condition.mjs b/test/fixtures/es-module-loaders/loader-with-custom-condition.mjs deleted file mode 100644 index 90c55fa576bb..000000000000 --- a/test/fixtures/es-module-loaders/loader-with-custom-condition.mjs +++ /dev/null @@ -1,17 +0,0 @@ -import { ok, deepStrictEqual } from 'assert'; - -export async function resolve(specifier, context, defaultResolve) { - ok(Array.isArray(context.conditions), 'loader receives conditions array'); - - deepStrictEqual([...context.conditions].sort(), [ - 'import', - 'module-sync', - 'node', - 'node-addons', - ]); - - return defaultResolve(specifier, { - ...context, - conditions: ['custom-condition', ...context.conditions], - }); -} diff --git a/test/fixtures/es-module-loaders/loader-with-dep.mjs b/test/fixtures/es-module-loaders/loader-with-dep.mjs deleted file mode 100644 index 625341eaed7e..000000000000 --- a/test/fixtures/es-module-loaders/loader-with-dep.mjs +++ /dev/null @@ -1,11 +0,0 @@ -import {createRequire} from '../../common/index.mjs'; - -const require = createRequire(import.meta.url); -const dep = require('./loader-dep.js'); - -export async function resolve(specifier, { parentURL, importAttributes }, defaultResolve) { - return { - url: (await defaultResolve(specifier, { parentURL, importAttributes }, defaultResolve)).url, - format: dep.format - }; -} diff --git a/test/fixtures/es-module-loaders/loader-with-too-many-args.mjs b/test/fixtures/es-module-loaders/loader-with-too-many-args.mjs deleted file mode 100644 index 95f40ec15d20..000000000000 --- a/test/fixtures/es-module-loaders/loader-with-too-many-args.mjs +++ /dev/null @@ -1,7 +0,0 @@ -export async function resolve(specifier, context, next) { - return next(specifier, context, 'resolve-extra-arg'); -} - -export async function load(url, context, next) { - return next(url, context, 'load-extra-arg'); -} diff --git a/test/fixtures/es-module-loaders/loader-worker-spawn.mjs b/test/fixtures/es-module-loaders/loader-worker-spawn.mjs deleted file mode 100644 index 2860d116e647..000000000000 --- a/test/fixtures/es-module-loaders/loader-worker-spawn.mjs +++ /dev/null @@ -1,7 +0,0 @@ -import { Worker } from 'worker_threads'; -import { createRequire } from 'module'; - -const require = createRequire(import.meta.url); -const fixtures = require('../../common/fixtures.js'); - -new Worker(fixtures.path('empty.js')).unref(); diff --git a/test/fixtures/es-module-loaders/missing-dynamic-instantiate-hook.mjs b/test/fixtures/es-module-loaders/missing-dynamic-instantiate-hook.mjs deleted file mode 100644 index 5d61d81bc31e..000000000000 --- a/test/fixtures/es-module-loaders/missing-dynamic-instantiate-hook.mjs +++ /dev/null @@ -1,17 +0,0 @@ -export function resolve(specifier, context, next) { - if (specifier === 'test') { - return { - url: 'file://' - }; - } - return next(specifier); -} - -export function getFormat(url, context, defaultGetFormat) { - if (url === 'file://') { - return { - format: 'dynamic' - } - } - return defaultGetFormat(url, context, defaultGetFormat); -} diff --git a/test/fixtures/es-module-loaders/mock-loader.mjs b/test/fixtures/es-module-loaders/mock-loader.mjs deleted file mode 100644 index 3bb349b53853..000000000000 --- a/test/fixtures/es-module-loaders/mock-loader.mjs +++ /dev/null @@ -1,133 +0,0 @@ -import { receiveMessageOnPort } from 'node:worker_threads'; -const mockedModuleExports = new Map(); -let currentMockVersion = 0; - -// These hooks enable code running on the application thread to -// swap module resolution results for mocking purposes. It uses this instead -// of import.meta so that CommonJS can still use the functionality. -// -// It does so by allowing non-mocked modules to live in normal URL cache -// locations but creates 'mock-facade:' URL cache location for every time a -// module location is mocked. Since a single URL can be mocked multiple -// times but it cannot be removed from the cache, `mock-facade:` URLs have a -// form of mock-facade:$VERSION:$REPLACING_URL with the parameters being URL -// percent encoded every time a module is resolved. So if a module for -// 'file:///app.js' is mocked it might look like -// 'mock-facade:12:file%3A%2F%2F%2Fapp.js'. This encoding is done to prevent -// problems like mocking URLs with special URL characters like '#' or '?' from -// accidentally being picked up as part of the 'mock-facade:' URL containing -// the mocked URL. -// -// NOTE: due to ESM spec, once a specifier has been resolved in a source text -// it cannot be changed. So things like the following DO NOT WORK: -// -// ```mjs -// import mock from 'test-esm-loader-mock'; // See test-esm-loader-mock.mjs -// mock('file:///app.js', {x:1}); -// const namespace1 = await import('file:///app.js'); -// namespace1.x; // 1 -// mock('file:///app.js', {x:2}); -// const namespace2 = await import('file:///app.js'); -// namespace2.x; // STILL 1, because this source text already set the specifier -// // for 'file:///app.js', a different specifier that resolves -// // to that could still get a new namespace though -// assert(namespace1 === namespace2); -// ``` - -/** - * @param param0 message from the application context - */ -function onPreloadPortMessage({ - mockVersion, resolved, exports -}) { - currentMockVersion = mockVersion; - mockedModuleExports.set(resolved, exports); -} - -/** @type {URL['href']} */ -let mainImportURL; -/** @type {MessagePort} */ -let preloadPort; -export async function initialize(data) { - ({ mainImportURL, port: preloadPort } = data); - - data.port.on('message', onPreloadPortMessage); -} - -/** - * Because Node.js internals use a separate MessagePort for cross-thread - * communication, there could be some messages pending that we should handle - * before continuing. - */ -function doDrainPort() { - let msg; - while (msg = receiveMessageOnPort(preloadPort)) { - onPreloadPortMessage(msg.message); - } -} - -// Rewrites node: loading to mock-facade: so that it can be intercepted -export async function resolve(specifier, context, defaultResolve) { - doDrainPort(); - const def = await defaultResolve(specifier, context); - if (context.parentURL?.startsWith('mock-facade:')) { - // Do nothing, let it get the "real" module - } else if (mockedModuleExports.has(def.url)) { - return { - shortCircuit: true, - url: `mock-facade:${currentMockVersion}:${encodeURIComponent(def.url)}` - }; - }; - return { - shortCircuit: true, - url: def.url, - }; -} - -export async function load(url, context, defaultLoad) { - doDrainPort(); - /** - * Mocked fake module, not going to be handled in default way so it - * generates the source text, then short circuits - */ - if (url.startsWith('mock-facade:')) { - const encodedTargetURL = url.slice(url.lastIndexOf(':') + 1); - return { - shortCircuit: true, - source: generateModule(encodedTargetURL), - format: 'module', - }; - } - return defaultLoad(url, context); -} - -/** - * Generate the source code for a mocked module. - * @param {string} encodedTargetURL the module being mocked - * @returns {string} - */ -function generateModule(encodedTargetURL) { - const exports = mockedModuleExports.get( - decodeURIComponent(encodedTargetURL) - ); - let body = [ - `import { mockedModules } from ${JSON.stringify(mainImportURL)};`, - 'export {};', - 'let mapping = {__proto__: null};', - `const mock = mockedModules.get(${JSON.stringify(encodedTargetURL)});`, - ]; - for (const [i, name] of Object.entries(exports)) { - let key = JSON.stringify(name); - body.push(`var _${i} = mock.namespace[${key}];`); - body.push(`Object.defineProperty(mapping, ${key}, { enumerable: true, set(v) {_${i} = v;}, get() {return _${i};} });`); - body.push(`export {_${i} as ${name}};`); - } - body.push(`mock.listeners.push(${ - () => { - for (var k in mapping) { - mapping[k] = mock.namespace[k]; - } - } - });`); - return body.join('\n'); -} diff --git a/test/fixtures/es-module-loaders/mock.mjs b/test/fixtures/es-module-loaders/mock.mjs deleted file mode 100644 index cb167f1d5204..000000000000 --- a/test/fixtures/es-module-loaders/mock.mjs +++ /dev/null @@ -1,70 +0,0 @@ -import { register } from 'node:module'; -import { MessageChannel } from 'node:worker_threads'; - - -const { port1, port2 } = new MessageChannel(); - -register('./mock-loader.mjs', import.meta.url, { - data: { - port: port2, - mainImportURL: import.meta.url, - }, - transferList: [port2], -}); - -/** - * This is the Map that saves *all* the mocked URL -> replacement Module - * mappings - * @type {Map} - */ -export const mockedModules = new Map(); -let mockVersion = 0; - -/** - * @param {string} resolved an absolute URL HREF string - * @param {object} replacementProperties an object to pick properties from - * to act as a module namespace - * @returns {object} a mutator object that can update the module namespace - * since we can't do something like old Object.observe - */ -export function mock(resolved, replacementProperties) { - const exportNames = Object.keys(replacementProperties); - const namespace = { __proto__: null }; - /** - * @type {Array<(name: string)=>void>} functions to call whenever an - * export name is updated - */ - const listeners = []; - for (const name of exportNames) { - let currentValueForPropertyName = replacementProperties[name]; - Object.defineProperty(namespace, name, { - __proto__: null, - enumerable: true, - get() { - return currentValueForPropertyName; - }, - set(v) { - currentValueForPropertyName = v; - for (const fn of listeners) { - try { - fn(name); - } catch { - /* noop */ - } - } - }, - }); - } - mockedModules.set(encodeURIComponent(resolved), { - namespace, - listeners, - }); - mockVersion++; - // Inform the loader that the `resolved` URL should now use the specific - // `mockVersion` and has export names of `exportNames` - // - // This allows the loader to generate a fake module for that version - // and names the next time it resolves a specifier to equal `resolved` - port1.postMessage({ mockVersion, resolved, exports: exportNames }); - return namespace; -} diff --git a/test/fixtures/es-module-loaders/never-settling-resolve-step/import.meta.never-resolve.mjs b/test/fixtures/es-module-loaders/never-settling-resolve-step/import.meta.never-resolve.mjs deleted file mode 100644 index 2bc389cc3e1d..000000000000 --- a/test/fixtures/es-module-loaders/never-settling-resolve-step/import.meta.never-resolve.mjs +++ /dev/null @@ -1,8 +0,0 @@ -import assert from 'node:assert'; -console.log('should be output'); - -assert.throws(() => { - import.meta.resolve('never-settle-resolve'); -}, { - code: 'ERR_ASYNC_LOADER_REQUEST_NEVER_SETTLED' -}); diff --git a/test/fixtures/es-module-loaders/never-settling-resolve-step/loader.mjs b/test/fixtures/es-module-loaders/never-settling-resolve-step/loader.mjs deleted file mode 100644 index c180583b4c74..000000000000 --- a/test/fixtures/es-module-loaders/never-settling-resolve-step/loader.mjs +++ /dev/null @@ -1,10 +0,0 @@ -export function resolve(specifier, context, next) { - if (specifier === 'never-settle-resolve') return new Promise(() => {}); - if (specifier === 'never-settle-load') return { __proto__: null, shortCircuit: true, url: 'never-settle:///' }; - return next(specifier, context); -} - -export function load(url, context, next) { - if (url === 'never-settle:///') return new Promise(() => {}); - return next(url, context); -} diff --git a/test/fixtures/es-module-loaders/never-settling-resolve-step/never-load.cjs b/test/fixtures/es-module-loaders/never-settling-resolve-step/never-load.cjs deleted file mode 100644 index cc81affe7d0d..000000000000 --- a/test/fixtures/es-module-loaders/never-settling-resolve-step/never-load.cjs +++ /dev/null @@ -1,7 +0,0 @@ -'use strict'; - -const neverSettlingDynamicImport = import('never-settle-load'); - -console.log('should be output'); - -neverSettlingDynamicImport.then(() => process.exit(1)); diff --git a/test/fixtures/es-module-loaders/never-settling-resolve-step/never-load.mjs b/test/fixtures/es-module-loaders/never-settling-resolve-step/never-load.mjs deleted file mode 100644 index 3dbf801c7605..000000000000 --- a/test/fixtures/es-module-loaders/never-settling-resolve-step/never-load.mjs +++ /dev/null @@ -1,5 +0,0 @@ -const neverSettlingDynamicImport = import('never-settle-load'); - -console.log('should be output'); - -await neverSettlingDynamicImport; diff --git a/test/fixtures/es-module-loaders/never-settling-resolve-step/never-resolve.cjs b/test/fixtures/es-module-loaders/never-settling-resolve-step/never-resolve.cjs deleted file mode 100644 index bf6387560b47..000000000000 --- a/test/fixtures/es-module-loaders/never-settling-resolve-step/never-resolve.cjs +++ /dev/null @@ -1,7 +0,0 @@ -'use strict'; - -const neverSettlingDynamicImport = import('never-settle-resolve'); - -console.log('should be output'); - -neverSettlingDynamicImport.then(() => process.exit(1)); diff --git a/test/fixtures/es-module-loaders/never-settling-resolve-step/never-resolve.mjs b/test/fixtures/es-module-loaders/never-settling-resolve-step/never-resolve.mjs deleted file mode 100644 index 806a131928c3..000000000000 --- a/test/fixtures/es-module-loaders/never-settling-resolve-step/never-resolve.mjs +++ /dev/null @@ -1,5 +0,0 @@ -const neverSettlingDynamicImport = import('never-settle-resolve'); - -console.log('should be output'); - -await neverSettlingDynamicImport; diff --git a/test/fixtures/es-module-loaders/never-settling-resolve-step/race.cjs b/test/fixtures/es-module-loaders/never-settling-resolve-step/race.cjs deleted file mode 100644 index 41884ab9d2e7..000000000000 --- a/test/fixtures/es-module-loaders/never-settling-resolve-step/race.cjs +++ /dev/null @@ -1,7 +0,0 @@ -'use strict'; - -Promise.race([ - import('never-settle-resolve'), - import('never-settle-load'), - import('node:process'), -]).then(result => console.log(result.default === process)); diff --git a/test/fixtures/es-module-loaders/never-settling-resolve-step/race.mjs b/test/fixtures/es-module-loaders/never-settling-resolve-step/race.mjs deleted file mode 100644 index 8213bbe59d44..000000000000 --- a/test/fixtures/es-module-loaders/never-settling-resolve-step/race.mjs +++ /dev/null @@ -1,7 +0,0 @@ -const result = await Promise.race([ - import('never-settle-resolve'), - import('never-settle-load'), - import('node:process'), -]); - -console.log(result.default === process); diff --git a/test/fixtures/es-module-loaders/node_modules/load/index.mjs b/test/fixtures/es-module-loaders/node_modules/load/index.mjs deleted file mode 100644 index db98e8d9f878..000000000000 --- a/test/fixtures/es-module-loaders/node_modules/load/index.mjs +++ /dev/null @@ -1 +0,0 @@ -export * from '../../loader-load-passthru.mjs' \ No newline at end of file diff --git a/test/fixtures/es-module-loaders/node_modules/load/package.json b/test/fixtures/es-module-loaders/node_modules/load/package.json deleted file mode 100644 index b6629e247888..000000000000 --- a/test/fixtures/es-module-loaders/node_modules/load/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "exports": "./index.mjs" -} diff --git a/test/fixtures/es-module-loaders/node_modules/resolve/index.mjs b/test/fixtures/es-module-loaders/node_modules/resolve/index.mjs deleted file mode 100644 index b3769d961359..000000000000 --- a/test/fixtures/es-module-loaders/node_modules/resolve/index.mjs +++ /dev/null @@ -1 +0,0 @@ -export * from '../../loader-resolve-passthru.mjs' \ No newline at end of file diff --git a/test/fixtures/es-module-loaders/node_modules/resolve/package.json b/test/fixtures/es-module-loaders/node_modules/resolve/package.json deleted file mode 100644 index b6629e247888..000000000000 --- a/test/fixtures/es-module-loaders/node_modules/resolve/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "exports": "./index.mjs" -} diff --git a/test/fixtures/es-module-loaders/not-found-assert-loader.mjs b/test/fixtures/es-module-loaders/not-found-assert-loader.mjs deleted file mode 100644 index bf66efbd0810..000000000000 --- a/test/fixtures/es-module-loaders/not-found-assert-loader.mjs +++ /dev/null @@ -1,16 +0,0 @@ -import assert from 'node:assert'; - -// A loader that asserts that the defaultResolve will throw "not found" -// (skipping the top-level main of course, and the built-in ones needed for run-worker). -let mainLoad = true; -export async function resolve(specifier, { importAttributes }, next) { - if (mainLoad || specifier === 'path' || specifier === 'worker_threads') { - mainLoad = false; - return next(specifier); - } - await assert.rejects(next(specifier), { code: 'ERR_MODULE_NOT_FOUND' }); - return { - url: 'node:fs', - importAttributes, - }; -} diff --git a/test/fixtures/es-module-loaders/preset-cjs-source.mjs b/test/fixtures/es-module-loaders/preset-cjs-source.mjs deleted file mode 100644 index 2cfa851a23f8..000000000000 --- a/test/fixtures/es-module-loaders/preset-cjs-source.mjs +++ /dev/null @@ -1,21 +0,0 @@ -export function resolve(specifier, context, next) { - if (specifier.endsWith('/no-such-file.cjs')) { - // Shortcut to avoid ERR_MODULE_NOT_FOUND for non-existing file, but keep the url for the load hook. - return { - shortCircuit: true, - url: specifier, - }; - } - return next(specifier); -} - -export function load(href, context, next) { - if (href.endsWith('.cjs')) { - return { - format: 'commonjs', - shortCircuit: true, - source: 'module.exports = "no .cjs file was read to get this source";', - }; - } - return next(href); -} diff --git a/test/fixtures/es-module-loaders/register-hooks-with-current-cwd-parent-url.mjs b/test/fixtures/es-module-loaders/register-hooks-with-current-cwd-parent-url.mjs deleted file mode 100644 index 580d092ab565..000000000000 --- a/test/fixtures/es-module-loaders/register-hooks-with-current-cwd-parent-url.mjs +++ /dev/null @@ -1,3 +0,0 @@ -import { register } from 'node:module'; -import { pathToFileURL } from 'node:url'; -register('./hooks-initialize.mjs', pathToFileURL('./')); diff --git a/test/fixtures/es-module-loaders/register-loader-with-url-parenturl.mjs b/test/fixtures/es-module-loaders/register-loader-with-url-parenturl.mjs deleted file mode 100644 index 1d82d2abc557..000000000000 --- a/test/fixtures/es-module-loaders/register-loader-with-url-parenturl.mjs +++ /dev/null @@ -1,11 +0,0 @@ -import {register} from 'node:module'; -import fixtures from '../../common/fixtures.js'; - -register( - fixtures.fileURL('es-module-loaders/loader-load-foo-or-42.mjs'), - new URL('data:'), -); - -import('node:os').then((result) => { - console.log(JSON.stringify(result)); -}); diff --git a/test/fixtures/es-module-loaders/register-loader.cjs b/test/fixtures/es-module-loaders/register-loader.cjs deleted file mode 100644 index 9e18dfa77e28..000000000000 --- a/test/fixtures/es-module-loaders/register-loader.cjs +++ /dev/null @@ -1,4 +0,0 @@ -const { register } = require('node:module'); -const fixtures = require('../../common/fixtures.js'); - -register(fixtures.fileURL('es-module-loaders', 'loader-resolve-passthru.mjs')); diff --git a/test/fixtures/es-module-loaders/register-loader.mjs b/test/fixtures/es-module-loaders/register-loader.mjs deleted file mode 100644 index f3cb2de8da3d..000000000000 --- a/test/fixtures/es-module-loaders/register-loader.mjs +++ /dev/null @@ -1,4 +0,0 @@ -import { register } from 'node:module'; -import fixtures from '../../common/fixtures.js'; - -register(fixtures.fileURL('es-module-loaders', 'loader-resolve-passthru.mjs')); diff --git a/test/fixtures/es-module-loaders/register-programmatically-loader-load.mjs b/test/fixtures/es-module-loaders/register-programmatically-loader-load.mjs deleted file mode 100644 index fe22dc7203e9..000000000000 --- a/test/fixtures/es-module-loaders/register-programmatically-loader-load.mjs +++ /dev/null @@ -1,4 +0,0 @@ -import * as fixtures from '../../common/fixtures.mjs'; -import { register } from 'node:module'; - -register(fixtures.fileURL('es-module-loaders', 'loader-load-passthru.mjs')); diff --git a/test/fixtures/es-module-loaders/register-programmatically-loader-resolve.mjs b/test/fixtures/es-module-loaders/register-programmatically-loader-resolve.mjs deleted file mode 100644 index 3ae8841da834..000000000000 --- a/test/fixtures/es-module-loaders/register-programmatically-loader-resolve.mjs +++ /dev/null @@ -1,3 +0,0 @@ -import { register } from 'node:module'; - -register('./loader-resolve-passthru.mjs', import.meta.url); diff --git a/test/fixtures/es-module-loaders/string-sources.mjs b/test/fixtures/es-module-loaders/string-sources.mjs deleted file mode 100644 index 39ad32c465fa..000000000000 --- a/test/fixtures/es-module-loaders/string-sources.mjs +++ /dev/null @@ -1,43 +0,0 @@ -const SOURCES = { - __proto__: null, - 'test:Array': ['1', '2'], // both `1,2` and `12` are valid ESM - 'test:ArrayBuffer': new ArrayBuffer(0), - 'test:BigInt64Array': new BigInt64Array(0), - 'test:BigUint64Array': new BigUint64Array(0), - 'test:Float32Array': new Float32Array(0), - 'test:Float64Array': new Float64Array(0), - 'test:Int8Array': new Int8Array(0), - 'test:Int16Array': new Int16Array(0), - 'test:Int32Array': new Int32Array(0), - 'test:null': null, - 'test:Object': {}, - 'test:SharedArrayBuffer': new SharedArrayBuffer(0), - 'test:string': '', - 'test:String': new String(''), - 'test:Uint8Array': new Uint8Array(0), - 'test:Uint8ClampedArray': new Uint8ClampedArray(0), - 'test:Uint16Array': new Uint16Array(0), - 'test:Uint32Array': new Uint32Array(0), - 'test:undefined': undefined, -} -export function resolve(specifier, context, next) { - if (specifier.startsWith('test:')) { - return { - importAttributes: context.importAttributes, - shortCircuit: true, - url: specifier, - }; - } - return next(specifier); -} - -export function load(href, context, next) { - if (href.startsWith('test:')) { - return { - format: 'module', - shortCircuit: true, - source: SOURCES[href], - }; - } - return next(href); -} diff --git a/test/fixtures/es-module-loaders/thenable-load-hook-rejected-no-arguments.mjs b/test/fixtures/es-module-loaders/thenable-load-hook-rejected-no-arguments.mjs deleted file mode 100644 index 5e008edecf7c..000000000000 --- a/test/fixtures/es-module-loaders/thenable-load-hook-rejected-no-arguments.mjs +++ /dev/null @@ -1,10 +0,0 @@ -export function load () { - let thenAlreadyAccessed = false; - return { - get then() { - if (thenAlreadyAccessed) throw new Error('must not call'); - thenAlreadyAccessed = true; - return (_, reject) => reject(); - } - }; -} diff --git a/test/fixtures/es-module-loaders/thenable-load-hook-rejected.mjs b/test/fixtures/es-module-loaders/thenable-load-hook-rejected.mjs deleted file mode 100644 index 926f6d0bebd6..000000000000 --- a/test/fixtures/es-module-loaders/thenable-load-hook-rejected.mjs +++ /dev/null @@ -1,10 +0,0 @@ -export function load () { - let thenAlreadyAccessed = false; - return { - get then() { - if (thenAlreadyAccessed) throw new Error('must not call'); - thenAlreadyAccessed = true; - return (_, reject) => reject(new Error('must crash the process')); - } - }; -} diff --git a/test/fixtures/es-module-loaders/thenable-load-hook.mjs b/test/fixtures/es-module-loaders/thenable-load-hook.mjs deleted file mode 100644 index 661f6e21ac69..000000000000 --- a/test/fixtures/es-module-loaders/thenable-load-hook.mjs +++ /dev/null @@ -1,10 +0,0 @@ -export function load(url, context, next) { - let thenAlreadyAccessed = false; - return { - get then() { - if (thenAlreadyAccessed) throw new Error('must not call'); - thenAlreadyAccessed = true; - return (resolve) => resolve(next(url, context)); - } - }; -} diff --git a/test/fixtures/es-modules/require-after-failed-import/dep.mjs b/test/fixtures/es-modules/require-after-failed-import/dep.mjs new file mode 100644 index 000000000000..efeee5db16c0 --- /dev/null +++ b/test/fixtures/es-modules/require-after-failed-import/dep.mjs @@ -0,0 +1 @@ +export const value = 1; diff --git a/test/fixtures/es-modules/require-after-failed-import/mismatched.mjs b/test/fixtures/es-modules/require-after-failed-import/mismatched.mjs new file mode 100644 index 000000000000..9e3f57a169a0 --- /dev/null +++ b/test/fixtures/es-modules/require-after-failed-import/mismatched.mjs @@ -0,0 +1,2 @@ +import { missing } from './dep.mjs'; +export { missing }; diff --git a/test/fixtures/es-modules/require-esm-in-cjs-cache/hooks.js b/test/fixtures/es-modules/require-esm-in-cjs-cache/hooks.js deleted file mode 100644 index f33a40fba773..000000000000 --- a/test/fixtures/es-modules/require-esm-in-cjs-cache/hooks.js +++ /dev/null @@ -1,12 +0,0 @@ -import { readFileSync } from 'node:fs'; -import { fileURLToPath } from 'node:url'; - -export async function load(url, context, nextLoad) { - const result = await nextLoad(url, context); - - if (result.format === 'commonjs' && !result.source) { - result.source = readFileSync(fileURLToPath(url), 'utf8'); - } - - return result; -} diff --git a/test/fixtures/es-modules/require-esm-in-cjs-cache/instrument.js b/test/fixtures/es-modules/require-esm-in-cjs-cache/instrument.js deleted file mode 100644 index 1c99b7e9c759..000000000000 --- a/test/fixtures/es-modules/require-esm-in-cjs-cache/instrument.js +++ /dev/null @@ -1,3 +0,0 @@ -import * as mod from 'node:module'; - -mod.register(new URL('hooks.js', import.meta.url).toString()); diff --git a/test/fixtures/es-modules/require-esm-throws-with-loaders.js b/test/fixtures/es-modules/require-esm-throws-with-loaders.js deleted file mode 100644 index 79c5cf96c141..000000000000 --- a/test/fixtures/es-modules/require-esm-throws-with-loaders.js +++ /dev/null @@ -1,8 +0,0 @@ -'use strict'; -const { readFile, __fromLoader } = require('fs'); -const assert = require('assert'); - -assert.throws(() => require('./test-esm-ok.mjs'), { code: 'ERR_REQUIRE_ESM' }); - -assert(readFile); -assert(__fromLoader); diff --git a/test/fixtures/es-modules/worker-condition/hooks-worker.mjs b/test/fixtures/es-modules/worker-condition/hooks-worker.mjs deleted file mode 100644 index ee9cd45bd6c5..000000000000 --- a/test/fixtures/es-modules/worker-condition/hooks-worker.mjs +++ /dev/null @@ -1,6 +0,0 @@ -import { register } from 'node:module'; - -register('./hooks.mjs', import.meta.url); - -const { default: value } = await import('pkg'); -postMessage(value); diff --git a/test/fixtures/es-modules/worker-condition/hooks.mjs b/test/fixtures/es-modules/worker-condition/hooks.mjs deleted file mode 100644 index 8505bafc98db..000000000000 --- a/test/fixtures/es-modules/worker-condition/hooks.mjs +++ /dev/null @@ -1,6 +0,0 @@ -export async function resolve(specifier, context, nextResolve) { - if (specifier === 'pkg' && !context.conditions.includes('worker')) { - throw new Error(`missing worker condition: ${context.conditions}`); - } - return nextResolve(specifier, context); -} diff --git a/test/fixtures/loader-is-internal-thread.js b/test/fixtures/loader-is-internal-thread.js deleted file mode 100644 index d97328bc2075..000000000000 --- a/test/fixtures/loader-is-internal-thread.js +++ /dev/null @@ -1,3 +0,0 @@ -const { isInternalThread } = require('node:worker_threads'); - -console.log(`isInternalThread: ${isInternalThread}`); \ No newline at end of file diff --git a/test/fixtures/module-hooks/get-stats.js b/test/fixtures/module-hooks/get-stats.js deleted file mode 100644 index fa5869a455ce..000000000000 --- a/test/fixtures/module-hooks/get-stats.js +++ /dev/null @@ -1,20 +0,0 @@ -'use strict'; - -const path = require('path'); - -// Adapted from https://github.com/watson/module-details-from-path/blob/master/index.js -// used by require-in-the-middle to check the logic is still compatible with our new hooks. -exports.getStats = function getStats(filepath) { - const segments = filepath.split(path.sep); - const index = segments.lastIndexOf('node_modules'); - if (index === -1) return {}; - if (!segments[index + 1]) return {}; - const scoped = segments[index + 1][0] === '@'; - const name = scoped ? segments[index + 1] + '/' + segments[index + 2] : segments[index + 1]; - const offset = scoped ? 3 : 2; - return { - name: name, - basedir: segments.slice(0, index + offset).join(path.sep), - path: segments.slice(index + offset).join(path.sep) - } -}; diff --git a/test/fixtures/module-hooks/logger-async-hooks.mjs b/test/fixtures/module-hooks/logger-async-hooks.mjs deleted file mode 100644 index f26d0ce2ae3e..000000000000 --- a/test/fixtures/module-hooks/logger-async-hooks.mjs +++ /dev/null @@ -1,19 +0,0 @@ -import fs from 'node:fs'; - -export async function resolve(specifier, context, nextResolve) { - fs.writeSync(1, `resolve ${specifier} from ${context.parentURL}\n`); - const result = await nextResolve(specifier, context); - result.shortCircuit = true; - return result; -} - -export async function load(url, context, nextLoad) { - fs.writeSync(1, `load ${url}\n`); - const result = await nextLoad(url, context); - result.shortCircuit = true; - // Handle the async loader hook quirk where source can be nullish for CommonJS. - // If this returns that nullish value verbatim, the `require` in imported - // CJS won't get the hook invoked. - result.source ??= fs.readFileSync(new URL(url), 'utf8'); - return result; -} diff --git a/test/fixtures/module-hooks/register-loader-in-sequence.mjs b/test/fixtures/module-hooks/register-loader-in-sequence.mjs deleted file mode 100644 index c129fcfc1809..000000000000 --- a/test/fixtures/module-hooks/register-loader-in-sequence.mjs +++ /dev/null @@ -1,11 +0,0 @@ -import {register} from 'node:module'; -import fixtures from '../../common/fixtures.js'; - -console.log('result 1', register( - fixtures.fileURL('es-module-loaders/hooks-initialize.mjs') -)); -console.log('result 2', register( - fixtures.fileURL('es-module-loaders/hooks-initialize.mjs') -)); - -await import('node:os'); diff --git a/test/fixtures/module-hooks/register-loader-initialize-never-settling.mjs b/test/fixtures/module-hooks/register-loader-initialize-never-settling.mjs deleted file mode 100644 index 13091effa970..000000000000 --- a/test/fixtures/module-hooks/register-loader-initialize-never-settling.mjs +++ /dev/null @@ -1,8 +0,0 @@ -import {register} from 'node:module'; -import fixtures from '../../common/fixtures.js'; - -try { - register(fixtures.fileURL('es-module-loaders/loader-initialize-never-settling.mjs')); -} catch (e) { - console.log('caught', e.code); -} diff --git a/test/fixtures/module-hooks/register-loader-with-cjs.cjs b/test/fixtures/module-hooks/register-loader-with-cjs.cjs deleted file mode 100644 index 2cd6c666bd14..000000000000 --- a/test/fixtures/module-hooks/register-loader-with-cjs.cjs +++ /dev/null @@ -1,14 +0,0 @@ -'use strict'; -const {register} = require('node:module'); -const fixtures = require('../../common/fixtures.js'); - -register( - fixtures.fileURL('es-module-loaders/hooks-initialize.mjs'), -); -register( - fixtures.fileURL('es-module-loaders/loader-load-foo-or-42.mjs'), -); - -import('node:os').then((result) => { - console.log(JSON.stringify(result)); -}); diff --git a/test/fixtures/module-hooks/register-loader-with-ports.mjs b/test/fixtures/module-hooks/register-loader-with-ports.mjs deleted file mode 100644 index 45e125f96a20..000000000000 --- a/test/fixtures/module-hooks/register-loader-with-ports.mjs +++ /dev/null @@ -1,22 +0,0 @@ -import {MessageChannel} from 'node:worker_threads'; -import {register} from 'node:module'; -import {once} from 'node:events'; -import fixtures from '../../common/fixtures.js'; - -const {port1, port2} = new MessageChannel(); -port1.on('message', (msg) => { - console.log('message', msg); -}); -const result = register( - fixtures.fileURL('es-module-loaders/hooks-initialize-port.mjs'), - {data: port2, transferList: [port2]}, -); -console.log('register', result); - -const timeout = setTimeout(() => {}, 2**31 - 1); // to keep the process alive. -await Promise.all([ - once(port1, 'message').then(() => once(port1, 'message')), - import('node:os'), -]); -clearTimeout(timeout); -port1.close(); diff --git a/test/fixtures/module-hooks/require-esm-in-hooks-thread/dep.mjs b/test/fixtures/module-hooks/require-esm-in-hooks-thread/dep.mjs deleted file mode 100644 index 3d34ee667697..000000000000 --- a/test/fixtures/module-hooks/require-esm-in-hooks-thread/dep.mjs +++ /dev/null @@ -1 +0,0 @@ -export const value = 'dep:'; diff --git a/test/fixtures/module-hooks/require-esm-in-hooks-thread/esm-with-import.mjs b/test/fixtures/module-hooks/require-esm-in-hooks-thread/esm-with-import.mjs deleted file mode 100644 index 1aad2b7a98c1..000000000000 --- a/test/fixtures/module-hooks/require-esm-in-hooks-thread/esm-with-import.mjs +++ /dev/null @@ -1,4 +0,0 @@ -import { sep } from 'node:path'; -import { value } from './dep.mjs'; - -export const result = `${value}${typeof sep}`; diff --git a/test/fixtures/module-hooks/require-esm-in-hooks-thread/hooks.mjs b/test/fixtures/module-hooks/require-esm-in-hooks-thread/hooks.mjs deleted file mode 100644 index b80b4ef959b7..000000000000 --- a/test/fixtures/module-hooks/require-esm-in-hooks-thread/hooks.mjs +++ /dev/null @@ -1,9 +0,0 @@ -// Loaded on the module.register() hooks thread. It defines no hooks: require()-ing -// an ES module with imports while the hooks module is being loaded is enough. -import { createRequire } from 'node:module'; - -const require = createRequire(import.meta.url); -const { result } = require('./esm-with-import.mjs'); -if (result !== 'dep:string') { - throw new Error(`Unexpected result: ${result}`); -} diff --git a/test/fixtures/module-hooks/require-resolve-paths-caller.js b/test/fixtures/module-hooks/require-resolve-paths-caller.js deleted file mode 100644 index f18b1f377735..000000000000 --- a/test/fixtures/module-hooks/require-resolve-paths-caller.js +++ /dev/null @@ -1,13 +0,0 @@ -// Fixture CJS file that calls require.resolve with the paths option. -'use strict'; -const path = require('path'); -const fixturesDir = path.resolve(__dirname, '..', '..'); -const nodeModules = path.join(fixturesDir, 'node_modules'); - -// Use the paths option to resolve 'bar' from the fixtures node_modules. -const resolved = require.resolve('bar', { paths: [fixturesDir] }); -const expected = path.join(nodeModules, 'bar.js'); -if (resolved !== expected) { - throw new Error(`Expected ${expected}, got ${resolved}`); -} -module.exports = { resolved }; diff --git a/test/fixtures/module-hooks/sync-and-async/app.js b/test/fixtures/module-hooks/sync-and-async/app.js deleted file mode 100644 index c14477b70f71..000000000000 --- a/test/fixtures/module-hooks/sync-and-async/app.js +++ /dev/null @@ -1,2 +0,0 @@ -console.log('Hello world'); - diff --git a/test/fixtures/module-hooks/sync-and-async/async-customize-loader.js b/test/fixtures/module-hooks/sync-and-async/async-customize-loader.js deleted file mode 100644 index ac961f0b97a3..000000000000 --- a/test/fixtures/module-hooks/sync-and-async/async-customize-loader.js +++ /dev/null @@ -1,10 +0,0 @@ -export async function load(url, context, nextLoad) { - if (url.endsWith('app.js')) { - return { - shortCircuit: true, - format: 'module', - source: 'console.log("customized by async hook");', - }; - } - return nextLoad(url, context); -} diff --git a/test/fixtures/module-hooks/sync-and-async/async-customize.js b/test/fixtures/module-hooks/sync-and-async/async-customize.js deleted file mode 100644 index 78bf7fc5564b..000000000000 --- a/test/fixtures/module-hooks/sync-and-async/async-customize.js +++ /dev/null @@ -1,3 +0,0 @@ -import { register } from 'node:module'; - -register(new URL('async-customize-loader.js', import.meta.url)); diff --git a/test/fixtures/module-hooks/sync-and-async/async-forward-loader.js b/test/fixtures/module-hooks/sync-and-async/async-forward-loader.js deleted file mode 100644 index 4a1ced7e8dbf..000000000000 --- a/test/fixtures/module-hooks/sync-and-async/async-forward-loader.js +++ /dev/null @@ -1,3 +0,0 @@ -export async function load(url, context, nextLoad) { - return nextLoad(url, context); -} diff --git a/test/fixtures/module-hooks/sync-and-async/async-forward.js b/test/fixtures/module-hooks/sync-and-async/async-forward.js deleted file mode 100644 index fd10aa6df898..000000000000 --- a/test/fixtures/module-hooks/sync-and-async/async-forward.js +++ /dev/null @@ -1,3 +0,0 @@ -import { register } from 'node:module'; - -register(new URL('async-forward-loader.js', import.meta.url)); diff --git a/test/fixtures/module-hooks/sync-and-async/sync-customize.js b/test/fixtures/module-hooks/sync-and-async/sync-customize.js deleted file mode 100644 index 90d63db1c7cc..000000000000 --- a/test/fixtures/module-hooks/sync-and-async/sync-customize.js +++ /dev/null @@ -1,14 +0,0 @@ -import { registerHooks } from 'node:module'; - -registerHooks({ - load(url, context, nextLoad) { - if (url.endsWith('app.js')) { - return { - shortCircuit: true, - format: 'module', - source: 'console.log("customized by sync hook")', - }; - } - return nextLoad(url, context); - }, -}); diff --git a/test/fixtures/module-hooks/sync-and-async/sync-forward.js b/test/fixtures/module-hooks/sync-and-async/sync-forward.js deleted file mode 100644 index 2688a240dc88..000000000000 --- a/test/fixtures/module-hooks/sync-and-async/sync-forward.js +++ /dev/null @@ -1,7 +0,0 @@ -import { registerHooks } from 'node:module'; - -registerHooks({ - load(url, context, nextLoad) { - return nextLoad(url, context); - }, -}); diff --git a/test/fixtures/print-delayed.js b/test/fixtures/print-delayed.js deleted file mode 100644 index 42eb45615b2a..000000000000 --- a/test/fixtures/print-delayed.js +++ /dev/null @@ -1,3 +0,0 @@ -setTimeout(() => { - console.log('delayed'); -}, 100); diff --git a/test/module-hooks/test-async-loader-hooks-called-with-expected-args.mjs b/test/module-hooks/test-async-loader-hooks-called-with-expected-args.mjs deleted file mode 100644 index 195bf02bb6fd..000000000000 --- a/test/module-hooks/test-async-loader-hooks-called-with-expected-args.mjs +++ /dev/null @@ -1,28 +0,0 @@ -// Test that loader hooks are called with all expected arguments -import '../common/index.mjs'; -import assert from 'node:assert'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndAssert } from '../common/child_process.js'; - -spawnSyncAndAssert( - execPath, - [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/hooks-input.mjs'), - fixtures.path('es-modules/json-modules.mjs'), - ], - { - stdout(output) { - const lines = output.split('\n'); - assert.match(lines[0], /{"url":"file:\/\/\/.*\/json-modules\.mjs","format":"test","shortCircuit":true}/); - assert.match(lines[1], /{"source":{"type":"Buffer","data":\[.*\]},"format":"module","shortCircuit":true}/); - assert.match(lines[2], /{"url":"file:\/\/\/.*\/experimental\.json","format":"test","shortCircuit":true}/); - assert.match(lines[3], /{"source":{"type":"Buffer","data":\[.*\]},"format":"json","shortCircuit":true}/); - assert.strictEqual(lines.length, 4); - }, - stderr: '', - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-called-with-register.mjs b/test/module-hooks/test-async-loader-hooks-called-with-register.mjs deleted file mode 100644 index d1f9181240d7..000000000000 --- a/test/module-hooks/test-async-loader-hooks-called-with-register.mjs +++ /dev/null @@ -1,31 +0,0 @@ -// Test that loader hooks are called with all expected arguments using register function -import '../common/index.mjs'; -import assert from 'node:assert'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndAssert } from '../common/child_process.js'; - -spawnSyncAndAssert( - execPath, - [ - '--no-warnings', - '--experimental-loader=data:text/javascript,', - '--input-type=module', - '--eval', - "import { register } from 'node:module';" + - `register(${JSON.stringify(fixtures.fileURL('es-module-loaders/hooks-input.mjs'))});` + - `await import(${JSON.stringify(fixtures.fileURL('es-modules/json-modules.mjs'))});`, - ], - { - stdout(output) { - const lines = output.split('\n'); - assert.match(lines[0], /{"url":"file:\/\/\/.*\/json-modules\.mjs","format":"test","shortCircuit":true}/); - assert.match(lines[1], /{"source":{"type":"Buffer","data":\[.*\]},"format":"module","shortCircuit":true}/); - assert.match(lines[2], /{"url":"file:\/\/\/.*\/experimental\.json","format":"test","shortCircuit":true}/); - assert.match(lines[3], /{"source":{"type":"Buffer","data":\[.*\]},"format":"json","shortCircuit":true}/); - assert.strictEqual(lines.length, 4); - }, - stderr: '', - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-globalpreload-no-warning-with-initialize.mjs b/test/module-hooks/test-async-loader-hooks-globalpreload-no-warning-with-initialize.mjs deleted file mode 100644 index 54501748bcea..000000000000 --- a/test/module-hooks/test-async-loader-hooks-globalpreload-no-warning-with-initialize.mjs +++ /dev/null @@ -1,21 +0,0 @@ -// Test that `globalPreload` should not emit warning when `initialize` is supplied - -import '../common/index.mjs'; -import assert from 'node:assert'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndAssert } from '../common/child_process.js'; - -spawnSyncAndAssert( - execPath, - [ - '--experimental-loader', - fixtures.fileURL('es-module-loaders/loader-globalpreload-and-initialize.mjs'), - fixtures.path('empty.js'), - ], - { - stderr(output) { - assert.doesNotMatch(output, /`globalPreload` has been removed; use `initialize` instead/); - }, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-globalpreload-warning.mjs b/test/module-hooks/test-async-loader-hooks-globalpreload-warning.mjs deleted file mode 100644 index 36a2c0089a91..000000000000 --- a/test/module-hooks/test-async-loader-hooks-globalpreload-warning.mjs +++ /dev/null @@ -1,23 +0,0 @@ -// Test that `globalPreload` should emit warning -import '../common/index.mjs'; -import assert from 'node:assert'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndAssert } from '../common/child_process.js'; - -spawnSyncAndAssert( - execPath, - [ - '--experimental-loader', - fixtures.fileURL('es-module-loaders/loader-globalpreload-only.mjs'), - '--experimental-loader', - fixtures.fileURL('es-module-loaders/loader-globalpreload-with-return.mjs'), - fixtures.path('empty.js'), - ], - { - stderr(output) { - const matches = output.match(/`globalPreload` has been removed; use `initialize` instead/g); - assert.strictEqual(matches.length, 1); - }, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-initialize-in-sequence.mjs b/test/module-hooks/test-async-loader-hooks-initialize-in-sequence.mjs deleted file mode 100644 index bc973f587845..000000000000 --- a/test/module-hooks/test-async-loader-hooks-initialize-in-sequence.mjs +++ /dev/null @@ -1,21 +0,0 @@ -// Test that `initialize` should execute in sequence -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndAssert } from '../common/child_process.js'; - -spawnSyncAndAssert( - execPath, - [ - '--no-warnings', - fixtures.path('module-hooks/register-loader-in-sequence.mjs'), - ], - { - stdout: 'hooks initialize 1\n' + - 'result 1 undefined\n' + - 'hooks initialize 2\n' + - 'result 2 undefined', - trim: true, - stderr: '', - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-initialize-invoke.mjs b/test/module-hooks/test-async-loader-hooks-initialize-invoke.mjs deleted file mode 100644 index 72dc87c12e35..000000000000 --- a/test/module-hooks/test-async-loader-hooks-initialize-invoke.mjs +++ /dev/null @@ -1,22 +0,0 @@ -// Test that `initialize` should be invoked correctly -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndAssert } from '../common/child_process.js'; - -spawnSyncAndAssert( - execPath, - [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/hooks-initialize.mjs'), - '--input-type=module', - '--eval', - 'import os from "node:os";', - ], - { - stdout: 'hooks initialize 1', - stderr: '', - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-initialize-never-settling.mjs b/test/module-hooks/test-async-loader-hooks-initialize-never-settling.mjs deleted file mode 100644 index d1c12238ef0c..000000000000 --- a/test/module-hooks/test-async-loader-hooks-initialize-never-settling.mjs +++ /dev/null @@ -1,18 +0,0 @@ -// Test that `initialize` returning never-settling promise should be handled -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndAssert } from '../common/child_process.js'; - -spawnSyncAndAssert( - execPath, - [ - '--no-warnings', - fixtures.path('module-hooks/register-loader-initialize-never-settling.mjs'), - ], - { - stdout: 'caught ERR_ASYNC_LOADER_REQUEST_NEVER_SETTLED', - stderr: '', - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-initialize-process-exit.mjs b/test/module-hooks/test-async-loader-hooks-initialize-process-exit.mjs deleted file mode 100644 index d9476af27e8f..000000000000 --- a/test/module-hooks/test-async-loader-hooks-initialize-process-exit.mjs +++ /dev/null @@ -1,23 +0,0 @@ -// Test that it should be fine to call `process.exit` from a `initialize` hook -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndExit } from '../common/child_process.js'; - -spawnSyncAndExit( - execPath, - [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/loader-initialize-exit.mjs'), - '--input-type=module', - '--eval', - 'import "node:os"', - ], - { - status: 42, - signal: null, - stdout: '', - stderr: '', - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-initialize-rejecting.mjs b/test/module-hooks/test-async-loader-hooks-initialize-rejecting.mjs deleted file mode 100644 index e5d2a62fbc86..000000000000 --- a/test/module-hooks/test-async-loader-hooks-initialize-rejecting.mjs +++ /dev/null @@ -1,24 +0,0 @@ -// Test that `initialize` returning rejecting promise should be handled -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndExit } from '../common/child_process.js'; - -spawnSyncAndExit( - execPath, - [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/loader-initialize-rejecting.mjs'), - '--input-type=module', - '--eval', - 'import "node:os"', - ], - { - status: 1, - signal: null, - stdout: '', - stderr: /undefined$/m, - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-initialize-throw-null.mjs b/test/module-hooks/test-async-loader-hooks-initialize-throw-null.mjs deleted file mode 100644 index 69844e31ae91..000000000000 --- a/test/module-hooks/test-async-loader-hooks-initialize-throw-null.mjs +++ /dev/null @@ -1,24 +0,0 @@ -// Test that `initialize` throwing null should be handled -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndExit } from '../common/child_process.js'; - -spawnSyncAndExit( - execPath, - [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/loader-initialize-throw-null.mjs'), - '--input-type=module', - '--eval', - 'import "node:os"', - ], - { - status: 1, - signal: null, - stdout: '', - stderr: /null$/m, - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-mixed-opt-in.mjs b/test/module-hooks/test-async-loader-hooks-mixed-opt-in.mjs deleted file mode 100644 index 398df25d7bbe..000000000000 --- a/test/module-hooks/test-async-loader-hooks-mixed-opt-in.mjs +++ /dev/null @@ -1,20 +0,0 @@ -// Test that loader should handle mixed of opt-in modules and non-opt-in ones -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndAssert } from '../common/child_process.js'; - -spawnSyncAndAssert( - execPath, - [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/loader-mixed-opt-in.mjs'), - 'entry-point', - ], - { - stdout: 'Hello', - stderr: '', - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-never-settling-import-meta-resolve.mjs b/test/module-hooks/test-async-loader-hooks-never-settling-import-meta-resolve.mjs deleted file mode 100644 index 35a4f7a566e5..000000000000 --- a/test/module-hooks/test-async-loader-hooks-never-settling-import-meta-resolve.mjs +++ /dev/null @@ -1,20 +0,0 @@ -// Test import.meta.resolve of a never-settling resolve should throw in ESM files -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndAssert } from '../common/child_process.js'; - -spawnSyncAndAssert( - execPath, - [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/never-settling-resolve-step/loader.mjs'), - fixtures.path('es-module-loaders/never-settling-resolve-step/import.meta.never-resolve.mjs'), - ], - { - stdout: /^should be output$/, - stderr: '', - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-never-settling-load-cjs.mjs b/test/module-hooks/test-async-loader-hooks-never-settling-load-cjs.mjs deleted file mode 100644 index 05401b9080b3..000000000000 --- a/test/module-hooks/test-async-loader-hooks-never-settling-load-cjs.mjs +++ /dev/null @@ -1,20 +0,0 @@ -// Test never-settling load in CJS files -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndAssert } from '../common/child_process.js'; - -spawnSyncAndAssert( - execPath, - [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/never-settling-resolve-step/loader.mjs'), - fixtures.path('es-module-loaders/never-settling-resolve-step/never-load.cjs'), - ], - { - stdout: /^should be output$/, - stderr: '', - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-never-settling-load-esm-no-warning.mjs b/test/module-hooks/test-async-loader-hooks-never-settling-load-esm-no-warning.mjs deleted file mode 100644 index dbadecc32876..000000000000 --- a/test/module-hooks/test-async-loader-hooks-never-settling-load-esm-no-warning.mjs +++ /dev/null @@ -1,22 +0,0 @@ -// Test top-level await of a never-settling load without warning in ESM files -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndExit } from '../common/child_process.js'; - -spawnSyncAndExit( - execPath, - [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/never-settling-resolve-step/loader.mjs'), - fixtures.path('es-module-loaders/never-settling-resolve-step/never-load.mjs'), - ], - { - status: 13, - signal: null, - stdout: /^should be output$/, - stderr: '', - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-never-settling-load-esm-with-warning.mjs b/test/module-hooks/test-async-loader-hooks-never-settling-load-esm-with-warning.mjs deleted file mode 100644 index d4b30ae23f86..000000000000 --- a/test/module-hooks/test-async-loader-hooks-never-settling-load-esm-with-warning.mjs +++ /dev/null @@ -1,21 +0,0 @@ -// Test top-level await of a never-settling load with warning in ESM files -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndExit } from '../common/child_process.js'; - -spawnSyncAndExit( - execPath, - [ - '--experimental-loader', - fixtures.fileURL('es-module-loaders/never-settling-resolve-step/loader.mjs'), - fixtures.path('es-module-loaders/never-settling-resolve-step/never-load.mjs'), - ], - { - status: 13, - signal: null, - stdout: /^should be output$/, - stderr: /Warning: Detected unsettled top-level await at.+never-load\.mjs:5/, - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-never-settling-race-cjs.mjs b/test/module-hooks/test-async-loader-hooks-never-settling-race-cjs.mjs deleted file mode 100644 index 1de677f88838..000000000000 --- a/test/module-hooks/test-async-loader-hooks-never-settling-race-cjs.mjs +++ /dev/null @@ -1,20 +0,0 @@ -// Test race of never-settling hooks in CJS files -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndAssert } from '../common/child_process.js'; - -spawnSyncAndAssert( - execPath, - [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/never-settling-resolve-step/loader.mjs'), - fixtures.path('es-module-loaders/never-settling-resolve-step/race.cjs'), - ], - { - stdout: /^true$/, - stderr: '', - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-never-settling-race-esm.mjs b/test/module-hooks/test-async-loader-hooks-never-settling-race-esm.mjs deleted file mode 100644 index e662dc43210d..000000000000 --- a/test/module-hooks/test-async-loader-hooks-never-settling-race-esm.mjs +++ /dev/null @@ -1,20 +0,0 @@ -// Test top-level await of a race of never-settling hooks in ESM files -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndAssert } from '../common/child_process.js'; - -spawnSyncAndAssert( - execPath, - [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/never-settling-resolve-step/loader.mjs'), - fixtures.path('es-module-loaders/never-settling-resolve-step/race.mjs'), - ], - { - stdout: /^true$/, - stderr: '', - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-never-settling-resolve-cjs.mjs b/test/module-hooks/test-async-loader-hooks-never-settling-resolve-cjs.mjs deleted file mode 100644 index 1469a7b313cc..000000000000 --- a/test/module-hooks/test-async-loader-hooks-never-settling-resolve-cjs.mjs +++ /dev/null @@ -1,20 +0,0 @@ -// Test never-settling resolve in CJS files -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndAssert } from '../common/child_process.js'; - -spawnSyncAndAssert( - execPath, - [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/never-settling-resolve-step/loader.mjs'), - fixtures.path('es-module-loaders/never-settling-resolve-step/never-resolve.cjs'), - ], - { - stdout: /^should be output$/, - stderr: '', - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-never-settling-resolve-esm-no-warning.mjs b/test/module-hooks/test-async-loader-hooks-never-settling-resolve-esm-no-warning.mjs deleted file mode 100644 index 43a7a98127c8..000000000000 --- a/test/module-hooks/test-async-loader-hooks-never-settling-resolve-esm-no-warning.mjs +++ /dev/null @@ -1,22 +0,0 @@ -// Test top-level await of a never-settling resolve without warning in ESM files -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndExit } from '../common/child_process.js'; - -spawnSyncAndExit( - execPath, - [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/never-settling-resolve-step/loader.mjs'), - fixtures.path('es-module-loaders/never-settling-resolve-step/never-resolve.mjs'), - ], - { - status: 13, - signal: null, - stdout: /^should be output$/, - stderr: '', - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-never-settling-resolve-esm-with-warning.mjs b/test/module-hooks/test-async-loader-hooks-never-settling-resolve-esm-with-warning.mjs deleted file mode 100644 index 1a4c40c8bb96..000000000000 --- a/test/module-hooks/test-async-loader-hooks-never-settling-resolve-esm-with-warning.mjs +++ /dev/null @@ -1,21 +0,0 @@ -// Test top-level await of a never-settling resolve with warning in ESM files -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndExit } from '../common/child_process.js'; - -spawnSyncAndExit( - execPath, - [ - '--experimental-loader', - fixtures.fileURL('es-module-loaders/never-settling-resolve-step/loader.mjs'), - fixtures.path('es-module-loaders/never-settling-resolve-step/never-resolve.mjs'), - ], - { - status: 13, - signal: null, - stdout: /^should be output$/, - stderr: /Warning: Detected unsettled top-level await at.+never-resolve\.mjs:5/, - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-no-leak-internals.mjs b/test/module-hooks/test-async-loader-hooks-no-leak-internals.mjs deleted file mode 100644 index 781aed8601f5..000000000000 --- a/test/module-hooks/test-async-loader-hooks-no-leak-internals.mjs +++ /dev/null @@ -1,20 +0,0 @@ -// Test that loader hooks should not leak internals or expose import.meta.resolve -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndAssert } from '../common/child_process.js'; - -spawnSyncAndAssert( - execPath, - [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/loader-edge-cases.mjs'), - fixtures.path('empty.js'), - ], - { - stdout: '', - stderr: '', - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-process-exit-async.mjs b/test/module-hooks/test-async-loader-hooks-process-exit-async.mjs deleted file mode 100644 index af3b939bcf8c..000000000000 --- a/test/module-hooks/test-async-loader-hooks-process-exit-async.mjs +++ /dev/null @@ -1,24 +0,0 @@ -// Test that it should be fine to call `process.exit` from a custom async hook -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndExit } from '../common/child_process.js'; - -spawnSyncAndExit( - execPath, - [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/loader-exit-on-load.mjs'), - '--input-type=module', - '--eval', - 'import "data:exit"', - ], - { - status: 42, - signal: null, - stdout: '', - stderr: '', - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-process-exit-sync.mjs b/test/module-hooks/test-async-loader-hooks-process-exit-sync.mjs deleted file mode 100644 index 64c079b90b75..000000000000 --- a/test/module-hooks/test-async-loader-hooks-process-exit-sync.mjs +++ /dev/null @@ -1,24 +0,0 @@ -// Test that it should be fine to call `process.exit` from a custom sync hook -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndExit } from '../common/child_process.js'; - -spawnSyncAndExit( - execPath, - [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/loader-exit-on-resolve.mjs'), - '--input-type=module', - '--eval', - 'import "data:text/javascript,import.meta.resolve(%22exit:%22)"', - ], - { - status: 42, - signal: null, - stdout: '', - stderr: '', - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-process-exit-top-level.mjs b/test/module-hooks/test-async-loader-hooks-process-exit-top-level.mjs deleted file mode 100644 index a25c0013c507..000000000000 --- a/test/module-hooks/test-async-loader-hooks-process-exit-top-level.mjs +++ /dev/null @@ -1,22 +0,0 @@ -// Test that it should be fine to call process.exit from the loader thread top-level -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndExit } from '../common/child_process.js'; - -spawnSyncAndExit( - execPath, - [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/loader-exit-top-level.mjs'), - fixtures.path('empty.js'), - ], - { - status: 42, - signal: null, - stdout: '', - stderr: '', - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-register-with-cjs.mjs b/test/module-hooks/test-async-loader-hooks-register-with-cjs.mjs deleted file mode 100644 index 8d83ee6be878..000000000000 --- a/test/module-hooks/test-async-loader-hooks-register-with-cjs.mjs +++ /dev/null @@ -1,20 +0,0 @@ -// Test that `register` should work with cjs -import '../common/index.mjs'; -import assert from 'node:assert'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndAssert } from '../common/child_process.js'; - -spawnSyncAndAssert( - execPath, - [ - '--no-warnings', - fixtures.path('module-hooks/register-loader-with-cjs.cjs'), - ], - { - stdout(output) { - assert.deepStrictEqual(output.split('\n').sort(), ['hooks initialize 1', '{"default":"foo"}', ''].sort()); - }, - stderr: '', - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-register-with-import.mjs b/test/module-hooks/test-async-loader-hooks-register-with-import.mjs deleted file mode 100644 index 454eccb5c624..000000000000 --- a/test/module-hooks/test-async-loader-hooks-register-with-import.mjs +++ /dev/null @@ -1,22 +0,0 @@ -// Test that `register` should work with `import` -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndAssert } from '../common/child_process.js'; - -spawnSyncAndAssert( - execPath, - [ - '--no-warnings', - '--import', - fixtures.fileURL('es-module-loaders/register-loader.mjs'), - '--input-type=module', - '--eval', - 'import "node:os"', - ], - { - stdout: 'resolve passthru', - stderr: '', - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-register-with-ports.mjs b/test/module-hooks/test-async-loader-hooks-register-with-ports.mjs deleted file mode 100644 index 022760596fd9..000000000000 --- a/test/module-hooks/test-async-loader-hooks-register-with-ports.mjs +++ /dev/null @@ -1,18 +0,0 @@ -// Test that loader should allow communicating with loader via `register` ports -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndAssert } from '../common/child_process.js'; - -spawnSyncAndAssert( - execPath, - [ - '--no-warnings', - fixtures.path('module-hooks/register-loader-with-ports.mjs'), - ], - { - stdout: 'register undefined\nmessage initialize\nmessage resolve node:os', - stderr: '', - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-register-with-require.mjs b/test/module-hooks/test-async-loader-hooks-register-with-require.mjs deleted file mode 100644 index 87ea43f8a66c..000000000000 --- a/test/module-hooks/test-async-loader-hooks-register-with-require.mjs +++ /dev/null @@ -1,22 +0,0 @@ -// Test that `register` should work with `require` -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndAssert } from '../common/child_process.js'; - -spawnSyncAndAssert( - execPath, - [ - '--no-warnings', - '--require', - fixtures.path('es-module-loaders/register-loader.cjs'), - '--input-type=module', - '--eval', - 'import "node:os";', - ], - { - stdout: 'resolve passthru\nresolve passthru', - stderr: '', - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-register-with-url-parenturl.mjs b/test/module-hooks/test-async-loader-hooks-register-with-url-parenturl.mjs deleted file mode 100644 index a033ab3c4d2f..000000000000 --- a/test/module-hooks/test-async-loader-hooks-register-with-url-parenturl.mjs +++ /dev/null @@ -1,26 +0,0 @@ -// Test that `register` should accept URL objects as `parentURL` -import '../common/index.mjs'; -import assert from 'node:assert'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndAssert } from '../common/child_process.js'; - -spawnSyncAndAssert( - execPath, - [ - '--no-warnings', - '--import', - fixtures.fileURL('es-module-loaders/register-hooks-with-current-cwd-parent-url.mjs'), - fixtures.path('es-module-loaders/register-loader-with-url-parenturl.mjs'), - ], - { - cwd: fixtures.path('es-module-loaders/'), - }, - { - stdout(output) { - assert.deepStrictEqual(output.split('\n').sort(), ['hooks initialize 1', '{"default":"foo"}'].sort()); - }, - stderr: '', - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-remove-beforeexit-listener.mjs b/test/module-hooks/test-async-loader-hooks-remove-beforeexit-listener.mjs deleted file mode 100644 index 9a28bb59dd31..000000000000 --- a/test/module-hooks/test-async-loader-hooks-remove-beforeexit-listener.mjs +++ /dev/null @@ -1,22 +0,0 @@ -// Test that it should be fine to call `process.removeAllListeners("beforeExit")`("beforeExit") from the main thread -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndAssert } from '../common/child_process.js'; - -spawnSyncAndAssert( - execPath, - [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/loader-delayed-async-load.mjs'), - '--input-type=module', - '--eval', - 'setInterval(() => process.removeAllListeners("beforeExit"),1).unref();await import("data:text/javascript,")', - ], - { - stdout: '', - stderr: '', - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-require-esm-in-hooks-thread.mjs b/test/module-hooks/test-async-loader-hooks-require-esm-in-hooks-thread.mjs deleted file mode 100644 index 8a647568ffee..000000000000 --- a/test/module-hooks/test-async-loader-hooks-require-esm-in-hooks-thread.mjs +++ /dev/null @@ -1,23 +0,0 @@ -// Test that require(esm) of a module with imports works while loading a module.register() hooks module. -// Refs: https://github.com/nodejs/node/issues/66767 -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndAssert } from '../common/child_process.js'; - -spawnSyncAndAssert( - execPath, - [ - '--no-warnings', - '--input-type=module', - '--eval', - `import { register } from 'node:module'; - register(${JSON.stringify(fixtures.fileURL('module-hooks/require-esm-in-hooks-thread/hooks.mjs'))}); - console.log('ok');`, - ], - { - stdout: 'ok', - stderr: '', - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-require-resolve-default.mjs b/test/module-hooks/test-async-loader-hooks-require-resolve-default.mjs deleted file mode 100644 index 71e539fe118b..000000000000 --- a/test/module-hooks/test-async-loader-hooks-require-resolve-default.mjs +++ /dev/null @@ -1,20 +0,0 @@ -// Test that loader should use CJS loader to respond to `require.resolve` calls by default -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndAssert } from '../common/child_process.js'; - -spawnSyncAndAssert( - execPath, - [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/loader-resolve-passthru.mjs'), - fixtures.path('require-resolve.js'), - ], - { - stdout: 'resolve passthru', - stderr: '', - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-require-resolve-opt-in.mjs b/test/module-hooks/test-async-loader-hooks-require-resolve-opt-in.mjs deleted file mode 100644 index 0cbf2fd2d942..000000000000 --- a/test/module-hooks/test-async-loader-hooks-require-resolve-opt-in.mjs +++ /dev/null @@ -1,22 +0,0 @@ -// Test that loader should use ESM loader to respond to `require.resolve` calls when opting in -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndAssert } from '../common/child_process.js'; - -spawnSyncAndAssert( - execPath, - [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/loader-load-commonjs-with-source.mjs'), - '--experimental-loader', - fixtures.fileURL('es-module-loaders/loader-resolve-passthru.mjs'), - fixtures.path('require-resolve.js'), - ], - { - stdout: 'resolve passthru\n'.repeat(10).trim(), - stderr: '', - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-source-maps-cjs.mjs b/test/module-hooks/test-async-loader-hooks-source-maps-cjs.mjs deleted file mode 100644 index 83bd53405d1b..000000000000 --- a/test/module-hooks/test-async-loader-hooks-source-maps-cjs.mjs +++ /dev/null @@ -1,23 +0,0 @@ -// Test that loader should support source maps in commonjs translator -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndExit } from '../common/child_process.js'; - -spawnSyncAndExit( - execPath, - [ - '--no-warnings', - '--enable-source-maps', - '--import', - fixtures.fileURL('es-module-loaders/loader-load-source-maps.mjs'), - fixtures.path('source-map/throw-on-require.js'), - ], - { - status: 1, - signal: null, - stdout: '', - stderr: /throw-on-require\.ts:9:9/, - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-throw-bigint.mjs b/test/module-hooks/test-async-loader-hooks-throw-bigint.mjs deleted file mode 100644 index 34136f81bf21..000000000000 --- a/test/module-hooks/test-async-loader-hooks-throw-bigint.mjs +++ /dev/null @@ -1,22 +0,0 @@ -// Test that loader hooks should handle bigint thrown from top-level -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndExit } from '../common/child_process.js'; - -spawnSyncAndExit( - execPath, - [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/loader-throw-bigint.mjs'), - fixtures.path('empty.js'), - ], - { - status: 1, - signal: null, - stdout: '', - stderr: /^1$/m, - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-throw-boolean.mjs b/test/module-hooks/test-async-loader-hooks-throw-boolean.mjs deleted file mode 100644 index 476fad3c1415..000000000000 --- a/test/module-hooks/test-async-loader-hooks-throw-boolean.mjs +++ /dev/null @@ -1,22 +0,0 @@ -// Test that loader hooks should handle boolean thrown from top-level -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndExit } from '../common/child_process.js'; - -spawnSyncAndExit( - execPath, - [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/loader-throw-boolean.mjs'), - fixtures.path('empty.js'), - ], - { - status: 1, - signal: null, - stdout: '', - stderr: /^true$/m, - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-throw-empty-object.mjs b/test/module-hooks/test-async-loader-hooks-throw-empty-object.mjs deleted file mode 100644 index ac09704c28ca..000000000000 --- a/test/module-hooks/test-async-loader-hooks-throw-empty-object.mjs +++ /dev/null @@ -1,22 +0,0 @@ -// Test that loader hooks should handle empty plain object thrown from top-level -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndExit } from '../common/child_process.js'; - -spawnSyncAndExit( - execPath, - [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/loader-throw-empty-object.mjs'), - fixtures.path('empty.js'), - ], - { - status: 1, - signal: null, - stdout: '', - stderr: /^\{\}$/m, - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-throw-error.mjs b/test/module-hooks/test-async-loader-hooks-throw-error.mjs deleted file mode 100644 index b37ce50db1e3..000000000000 --- a/test/module-hooks/test-async-loader-hooks-throw-error.mjs +++ /dev/null @@ -1,22 +0,0 @@ -// Test that loader hooks should handle regular Error object thrown from top-level -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndExit } from '../common/child_process.js'; - -spawnSyncAndExit( - execPath, - [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/loader-throw-error.mjs'), - fixtures.path('empty.js'), - ], - { - status: 1, - signal: null, - stdout: '', - stderr: /^Error: error message$/m, - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-throw-function.mjs b/test/module-hooks/test-async-loader-hooks-throw-function.mjs deleted file mode 100644 index bb371f727c84..000000000000 --- a/test/module-hooks/test-async-loader-hooks-throw-function.mjs +++ /dev/null @@ -1,22 +0,0 @@ -// Test that loader hooks should handle function thrown from top-level -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndExit } from '../common/child_process.js'; - -spawnSyncAndExit( - execPath, - [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/loader-throw-function.mjs'), - fixtures.path('empty.js'), - ], - { - status: 1, - signal: null, - stdout: '', - stderr: /^\[Function: fnName\]$/m, - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-throw-null.mjs b/test/module-hooks/test-async-loader-hooks-throw-null.mjs deleted file mode 100644 index 33528ff2b975..000000000000 --- a/test/module-hooks/test-async-loader-hooks-throw-null.mjs +++ /dev/null @@ -1,22 +0,0 @@ -// Test that loader hooks should handle null thrown from top-level -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndExit } from '../common/child_process.js'; - -spawnSyncAndExit( - execPath, - [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/loader-throw-null.mjs'), - fixtures.path('empty.js'), - ], - { - status: 1, - signal: null, - stdout: '', - stderr: /^null$/m, - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-throw-number.mjs b/test/module-hooks/test-async-loader-hooks-throw-number.mjs deleted file mode 100644 index da1f301089ca..000000000000 --- a/test/module-hooks/test-async-loader-hooks-throw-number.mjs +++ /dev/null @@ -1,22 +0,0 @@ -// Test that loader hooks should handle number thrown from top-level -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndExit } from '../common/child_process.js'; - -spawnSyncAndExit( - execPath, - [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/loader-throw-number.mjs'), - fixtures.path('empty.js'), - ], - { - status: 1, - signal: null, - stdout: '', - stderr: /^1$/m, - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-throw-object.mjs b/test/module-hooks/test-async-loader-hooks-throw-object.mjs deleted file mode 100644 index 8280bd0d105b..000000000000 --- a/test/module-hooks/test-async-loader-hooks-throw-object.mjs +++ /dev/null @@ -1,22 +0,0 @@ -// Test that loader hooks should handle plain object thrown from top-level -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndExit } from '../common/child_process.js'; - -spawnSyncAndExit( - execPath, - [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/loader-throw-object.mjs'), - fixtures.path('empty.js'), - ], - { - status: 1, - signal: null, - stdout: '', - stderr: /^\{ fn: \[Function: fn\], symbol: Symbol\(symbol\), u: undefined \}$/m, - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-throw-string.mjs b/test/module-hooks/test-async-loader-hooks-throw-string.mjs deleted file mode 100644 index 4ea6372e865c..000000000000 --- a/test/module-hooks/test-async-loader-hooks-throw-string.mjs +++ /dev/null @@ -1,22 +0,0 @@ -// Test that loader hooks should handle string thrown from top-level -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndExit } from '../common/child_process.js'; - -spawnSyncAndExit( - execPath, - [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/loader-throw-string.mjs'), - fixtures.path('empty.js'), - ], - { - status: 1, - signal: null, - stdout: '', - stderr: /^literal string$/m, - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-throw-symbol.mjs b/test/module-hooks/test-async-loader-hooks-throw-symbol.mjs deleted file mode 100644 index 44339537ba9c..000000000000 --- a/test/module-hooks/test-async-loader-hooks-throw-symbol.mjs +++ /dev/null @@ -1,20 +0,0 @@ -// Test that loader hooks should handle symbol thrown from top-level -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndExit } from '../common/child_process.js'; - -spawnSyncAndExit( - execPath, - [ - '--experimental-loader', - fixtures.fileURL('es-module-loaders/loader-throw-symbol.mjs'), - fixtures.path('empty.js'), - ], - { - status: 1, - signal: null, - stdout: '', // Throwing a symbol doesn't produce any output - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-throw-undefined.mjs b/test/module-hooks/test-async-loader-hooks-throw-undefined.mjs deleted file mode 100644 index aff5292a857f..000000000000 --- a/test/module-hooks/test-async-loader-hooks-throw-undefined.mjs +++ /dev/null @@ -1,22 +0,0 @@ -// Test that loader hooks should handle undefined thrown from top-level -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndExit } from '../common/child_process.js'; - -spawnSyncAndExit( - execPath, - [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/loader-throw-undefined.mjs'), - fixtures.path('empty.js'), - ], - { - status: 1, - signal: null, - stdout: '', - stderr: /^undefined$/m, - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-use-hooks-require-esm.mjs b/test/module-hooks/test-async-loader-hooks-use-hooks-require-esm.mjs deleted file mode 100644 index 1eee6a6014aa..000000000000 --- a/test/module-hooks/test-async-loader-hooks-use-hooks-require-esm.mjs +++ /dev/null @@ -1,21 +0,0 @@ -// Test that loader should use hooks for require of ESM -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndAssert } from '../common/child_process.js'; - -spawnSyncAndAssert( - execPath, - [ - '--no-deprecation', - '--no-experimental-require-module', - '--import', - fixtures.fileURL('es-module-loaders/builtin-named-exports.mjs'), - fixtures.path('es-modules/require-esm-throws-with-loaders.js'), - ], - { - stdout: '', - stderr: '', - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-with-worker-permission-allowed.mjs b/test/module-hooks/test-async-loader-hooks-with-worker-permission-allowed.mjs deleted file mode 100644 index 9be7e8c39600..000000000000 --- a/test/module-hooks/test-async-loader-hooks-with-worker-permission-allowed.mjs +++ /dev/null @@ -1,24 +0,0 @@ -// Test that loader hooks should allow spawning workers when allowed by CLI flags -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndAssert } from '../common/child_process.js'; - -spawnSyncAndAssert( - execPath, - [ - '--no-warnings', - '--permission', - '--allow-worker', - '--allow-fs-read', - '*', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/loader-worker-spawn.mjs'), - fixtures.path('es-modules/esm-top-level-await.mjs'), - ], - { - stdout: /^1\n2$/, - stderr: '', - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-with-worker-permission-restricted.mjs b/test/module-hooks/test-async-loader-hooks-with-worker-permission-restricted.mjs deleted file mode 100644 index fe76f2f5842c..000000000000 --- a/test/module-hooks/test-async-loader-hooks-with-worker-permission-restricted.mjs +++ /dev/null @@ -1,25 +0,0 @@ -// Test that loader hooks should not allow spawning workers if restricted by CLI flags -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndExit } from '../common/child_process.js'; - -spawnSyncAndExit( - execPath, - [ - '--no-warnings', - '--permission', - '--allow-fs-read', - '*', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/loader-worker-spawn.mjs'), - fixtures.path('es-modules/esm-top-level-await.mjs'), - ], - { - status: 1, - signal: null, - stdout: '', - stderr: /code: 'ERR_ACCESS_DENIED'/, - trim: true, - }, -); diff --git a/test/module-hooks/test-async-loader-hooks-without-worker-permission.mjs b/test/module-hooks/test-async-loader-hooks-without-worker-permission.mjs deleted file mode 100644 index 21589234144d..000000000000 --- a/test/module-hooks/test-async-loader-hooks-without-worker-permission.mjs +++ /dev/null @@ -1,25 +0,0 @@ -// Test that loader hooks should not work without worker permission -import '../common/index.mjs'; -import { execPath } from 'node:process'; -import fixtures from '../common/fixtures.js'; -import { spawnSyncAndExit } from '../common/child_process.js'; - -spawnSyncAndExit( - execPath, - [ - '--no-warnings', - '--permission', - '--allow-fs-read', - '*', - '--experimental-loader', - fixtures.fileURL('empty.js'), - fixtures.path('es-modules/esm-top-level-await.mjs'), - ], - { - status: 1, - signal: null, - stdout: '', - stderr: /Error: Access to this API has been restricted/, - trim: true, - }, -); diff --git a/test/module-hooks/test-module-hooks-load-async-and-sync.js b/test/module-hooks/test-module-hooks-load-async-and-sync.js deleted file mode 100644 index 75f4987942f9..000000000000 --- a/test/module-hooks/test-module-hooks-load-async-and-sync.js +++ /dev/null @@ -1,32 +0,0 @@ -'use strict'; -// This tests that sync and async hooks can be mixed. - -require('../common'); -const { spawnSyncAndAssert } = require('../common/child_process'); -const fixtures = require('../common/fixtures'); - -const app = fixtures.path('module-hooks', 'sync-and-async', 'app.js'); - -const testCases = [ - // When mixing sync and async hooks, the sync ones always run first. - { preload: ['sync-customize', 'async-customize'], stdout: 'customized by sync hook' }, - { preload: ['async-customize', 'sync-customize'], stdout: 'customized by sync hook' }, - // It should still work when neither hook does any customization. - { preload: ['sync-forward', 'async-forward'], stdout: 'Hello world' }, - { preload: ['async-forward', 'sync-forward'], stdout: 'Hello world' }, - // It should work when only one hook is customizing. - { preload: ['sync-customize', 'async-forward'], stdout: 'customized by sync hook' }, - { preload: ['async-customize', 'sync-forward'], stdout: 'customized by async hook' }, -]; - - -for (const { preload, stdout } of testCases) { - const importArgs = []; - for (const p of preload) { - importArgs.push('--import', fixtures.fileURL(`module-hooks/sync-and-async/${p}.js`)); - } - spawnSyncAndAssert(process.execPath, [...importArgs, app], { - stdout, - trim: true, - }); -} diff --git a/test/module-hooks/test-module-hooks-load-builtin-import.mjs b/test/module-hooks/test-module-hooks-load-builtin-import.mjs index f78c69692fe0..159189f53c5d 100644 --- a/test/module-hooks/test-module-hooks-load-builtin-import.mjs +++ b/test/module-hooks/test-module-hooks-load-builtin-import.mjs @@ -5,8 +5,7 @@ import process from 'node:process'; // This tests that imported builtins get null as source from default // step, and the source returned are ignored. -// TODO(joyeecheung): this is to align with the module.register() behavior -// but perhaps the load hooks should not be invoked for builtins at all. +// TODO(joyeecheung): perhaps the load hooks should not be invoked for builtins at all. // Pick a builtin that's unlikely to be loaded already - like zlib. assert(!process.moduleLoadList.includes('NativeModule zlib')); diff --git a/test/module-hooks/test-module-hooks-load-builtin-require.js b/test/module-hooks/test-module-hooks-load-builtin-require.js index d86eb4b9df76..ac4de0f14da3 100644 --- a/test/module-hooks/test-module-hooks-load-builtin-require.js +++ b/test/module-hooks/test-module-hooks-load-builtin-require.js @@ -7,8 +7,7 @@ const { registerHooks } = require('module'); // This tests that required builtins get null as source from default // step, and the source returned are ignored. -// TODO(joyeecheung): this is to align with the module.register() behavior -// but perhaps the load hooks should not be invoked for builtins at all. +// TODO(joyeecheung): perhaps the load hooks should not be invoked for builtins at all. // Pick a builtin that's unlikely to be loaded already - like zlib. assert(!process.moduleLoadList.includes('NativeModule zlib')); diff --git a/test/module-hooks/test-module-hooks-resolve-require-resolve-loaded-with-source.js b/test/module-hooks/test-module-hooks-resolve-require-resolve-loaded-with-source.js deleted file mode 100644 index de66beda5b2e..000000000000 --- a/test/module-hooks/test-module-hooks-resolve-require-resolve-loaded-with-source.js +++ /dev/null @@ -1,35 +0,0 @@ -'use strict'; - -// This tests that require.resolve() in a CJS file loaded via the re-invented -// require (triggered when module.register() installs an async loader that -// provides source for a CJS file) still goes through resolve hooks registered -// via module.registerHooks(). - -const common = require('../common'); -const assert = require('assert'); -const { register, registerHooks } = require('module'); -const fixtures = require('../common/fixtures'); -const { pathToFileURL } = require('url'); - -const redirectedPath = fixtures.path('module-hooks', 'redirected-assert.js'); - -// Register an async loader that provides source for CJS files, which triggers -// the re-invented require path. -register(fixtures.fileURL('module-hooks', 'logger-async-hooks.mjs')); - -const hook = registerHooks({ - resolve: common.mustCall((specifier, context, nextResolve) => { - if (specifier === 'test-require-resolve-hook-target') { - return { - url: pathToFileURL(redirectedPath).href, - shortCircuit: true, - }; - } - return nextResolve(specifier, context); - }, 2), -}); - -import('../fixtures/module-hooks/require-resolve-caller.js').then(common.mustCall((ns) => { - assert.strictEqual(ns.default.resolved, redirectedPath); - hook.deregister(); -})); diff --git a/test/parallel/test-esm-loader-hooks-inspect-brk.js b/test/parallel/test-esm-loader-hooks-inspect-brk.js deleted file mode 100644 index 251ebb230dcd..000000000000 --- a/test/parallel/test-esm-loader-hooks-inspect-brk.js +++ /dev/null @@ -1,31 +0,0 @@ -// This tests esm loader's internal worker will not be blocked by --inspect-brk. -// Regression: https://github.com/nodejs/node/issues/53681 - -'use strict'; -const common = require('../common'); - -common.skipIfInspectorDisabled(); - -const assert = require('assert'); -const fixtures = require('../common/fixtures'); -const { NodeInstance } = require('../common/inspector-helper.js'); - -async function runTest() { - const main = fixtures.path('es-module-loaders', 'register-loader.mjs'); - const child = new NodeInstance(['--inspect-brk=0'], '', main); - - const session = await child.connectInspectorSession(); - await session.send({ method: 'NodeRuntime.enable' }); - await session.waitForNotification('NodeRuntime.waitingForDebugger'); - await session.send([ - { 'method': 'Runtime.enable' }, - { 'method': 'Debugger.enable' }, - { 'method': 'Runtime.runIfWaitingForDebugger' }, - ]); - await session.send({ method: 'NodeRuntime.disable' }); - await session.waitForNotification('Debugger.paused'); - await session.runToCompletion(); - assert.strictEqual((await child.expectShutdown()).exitCode, 0); -} - -runTest(); diff --git a/test/parallel/test-esm-loader-hooks-inspect-wait.js b/test/parallel/test-esm-loader-hooks-inspect-wait.js deleted file mode 100644 index 427b05641cce..000000000000 --- a/test/parallel/test-esm-loader-hooks-inspect-wait.js +++ /dev/null @@ -1,35 +0,0 @@ -// This tests esm loader's internal worker will not be blocked by --inspect-wait. -// Regression: https://github.com/nodejs/node/issues/53681 - -'use strict'; -const common = require('../common'); - -common.skipIfInspectorDisabled(); - -const assert = require('assert'); -const fixtures = require('../common/fixtures'); -const { NodeInstance } = require('../common/inspector-helper.js'); - -async function runTest() { - const main = fixtures.path('es-module-loaders', 'register-loader.mjs'); - process.env.NODE_DEBUG = 'esm,async_loader_worker'; - const child = new NodeInstance(['--inspect-wait=0'], '', main); - - const session = await child.connectInspectorSession(); - await session.send({ method: 'NodeRuntime.enable' }); - await session.waitForNotification('NodeRuntime.waitingForDebugger'); - await session.send([ - { 'method': 'Runtime.enable' }, - { 'method': 'Debugger.enable' }, - { 'method': 'Runtime.runIfWaitingForDebugger' }, - ]); - await session.send({ method: 'NodeRuntime.disable' }); - await session.waitForDisconnect(); - const result = await child.expectShutdown(); - assert.deepStrictEqual(result, { - exitCode: 0, - signal: null, - }); -} - -runTest(); diff --git a/test/parallel/test-is-internal-thread.mjs b/test/parallel/test-is-internal-thread.mjs index dd8af897c219..9aab6be87fd9 100644 --- a/test/parallel/test-is-internal-thread.mjs +++ b/test/parallel/test-is-internal-thread.mjs @@ -1,34 +1,16 @@ -import { spawnPromisified } from '../common/index.mjs'; +import * as common 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'; -import * as common from '../common/index.mjs'; 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', common.mustCall((message) => { assert.strictEqual(message, 'isInternalThread: false'); })); diff --git a/test/parallel/test-permission-allow-inspector.js b/test/parallel/test-permission-allow-inspector.js deleted file mode 100644 index 05022ff24044..000000000000 --- a/test/parallel/test-permission-allow-inspector.js +++ /dev/null @@ -1,54 +0,0 @@ -// Flags: --permission --allow-fs-read=* --allow-inspector -'use strict'; - -const common = require('../common'); - -common.skipIfInspectorDisabled(); - -const { Session } = require('node:inspector/promises'); -const assert = require('node:assert'); - -const session = new Session(); -session.connect(); - -// Guarantee WorkerImpl doesn't gets bypassed -(async () => { - await session.post('Debugger.enable'); - await session.post('Runtime.enable'); - - globalThis.Worker = require('node:worker_threads').Worker; - - const { result: { objectId } } = await session.post('Runtime.evaluate', { expression: 'Worker' }); - const { internalProperties } = await session.post('Runtime.getProperties', { objectId: objectId }); - const { - value: { - value: { scriptId } - } - } = internalProperties.filter((prop) => prop.name === '[[FunctionLocation]]')[0]; - const { scriptSource } = await session.post('Debugger.getScriptSource', { scriptId }); - - const lineNumber = scriptSource.substring(0, scriptSource.indexOf('new WorkerImpl')).split('\n').length; - - // Attempt to bypass it based on https://hackerone.com/reports/1962701 - await session.post('Debugger.setBreakpointByUrl', { - lineNumber: lineNumber, - url: 'node:internal/worker', - columnNumber: 0, - condition: '((isInternal = true),false)' - }); - - assert.throws(() => { - new Worker(` - const child_process = require("node:child_process"); - console.log(child_process.execSync("ls -l").toString()); - - console.log(require("fs").readFileSync("/etc/passwd").toString()) - `, { - eval: true, - }); - }, { - message: 'Access to this API has been restricted. Use --allow-worker to manage permissions.', - code: 'ERR_ACCESS_DENIED', - permission: 'WorkerThreads' - }); -})().then(common.mustCall()); diff --git a/test/parallel/test-process-env-allowed-flags-are-documented.js b/test/parallel/test-process-env-allowed-flags-are-documented.js index 910fec097b69..ba33ae989863 100644 --- a/test/parallel/test-process-env-allowed-flags-are-documented.js +++ b/test/parallel/test-process-env-allowed-flags-are-documented.js @@ -137,7 +137,6 @@ assert(undocumented.delete('--experimental-worker')); assert(undocumented.delete('--napi-modules')); assert(undocumented.delete('--node-snapshot')); assert(undocumented.delete('--no-node-snapshot')); -assert(undocumented.delete('--loader')); assert(undocumented.delete('--verify-base-objects')); assert(undocumented.delete('--no-verify-base-objects')); assert(undocumented.delete('--trace-promises')); diff --git a/test/parallel/test-webworker-conditions.js b/test/parallel/test-webworker-conditions.js index 313e3f3d643d..6cf564597eb0 100644 --- a/test/parallel/test-webworker-conditions.js +++ b/test/parallel/test-webworker-conditions.js @@ -70,8 +70,4 @@ function runWebWorker(url, options) { const [threadResult] = await once(thread, 'message'); assert.deepStrictEqual(threadResult, { thread: 'default', webWorker: 'worker' }); - // Async module customization hooks registered from a Web Worker. - assert.strictEqual( - await runWebWorker(new URL('hooks-worker.mjs', base), { type: 'module' }), - 'worker'); })().then(common.mustCall()); diff --git a/typings/internalBinding/worker.d.ts b/typings/internalBinding/worker.d.ts index e168ff8abb17..7801b2c569dd 100644 --- a/typings/internalBinding/worker.d.ts +++ b/typings/internalBinding/worker.d.ts @@ -41,7 +41,6 @@ export interface WorkerBinding { threadId: number; threadName: string; isMainThread: boolean; - isInternalThread: boolean; ownsProcessState: boolean; resourceLimits?: Float64Array; kMaxYoungGenerationSizeMb: number;