URL: http://github.com/nodejs/node/pull/61374.diff
rt.match(stderr, /Error: Access to this API has been restricted/); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 1); - assert.strictEqual(signal, null); - }); - - it('should allow loader hooks to spawn workers when allowed by the CLI flags', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--permission', - '--allow-worker', - '--allow-fs-read', - '*', - '--experimental-loader', - `data:text/javascript,import{Worker}from"worker_threads";new Worker(${encodeURIComponent(JSON.stringify(fixtures.path('empty.js')))}).unref()`, - fixtures.path('es-modules/esm-top-level-await.mjs'), - ]); - - assert.strictEqual(stderr, ''); - assert.match(stdout, /^1\r?\n2\r?\n$/); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - - it('should not allow loader hooks to spawn workers if restricted by the CLI flags', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--permission', - '--allow-fs-read', - '*', - '--experimental-loader', - `data:text/javascript,import{Worker}from"worker_threads";new Worker(${encodeURIComponent(JSON.stringify(fixtures.path('empty.js')))}).unref()`, - fixtures.path('es-modules/esm-top-level-await.mjs'), - ]); - - assert.match(stderr, /code: 'ERR_ACCESS_DENIED'/); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 1); - assert.strictEqual(signal, null); - }); - - it('should not leak internals or expose import.meta.resolve', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/loader-edge-cases.mjs'), - fixtures.path('empty.js'), - ]); - - assert.strictEqual(stderr, ''); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - - it('should be fine to call `process.exit` from a custom async hook', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - 'data:text/javascript,export function load(a,b,next){if(a==="data:exit")process.exit(42);return next(a,b)}', - '--input-type=module', - '--eval', - 'import "data:exit"', - ]); - - assert.strictEqual(stderr, ''); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 42); - assert.strictEqual(signal, null); - }); - - it('should be fine to call `process.exit` from a custom sync hook', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - 'data:text/javascript,export function resolve(a,b,next){if(a==="exit:")process.exit(42);return next(a,b)}', - '--input-type=module', - '--eval', - 'import "data:text/javascript,import.meta.resolve(%22exit:%22)"', - ]); - - assert.strictEqual(stderr, ''); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 42); - assert.strictEqual(signal, null); - }); - - it('should be fine to call `process.exit` from the loader thread top-level', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - 'data:text/javascript,process.exit(42)', - fixtures.path('empty.js'), - ]); - - assert.strictEqual(stderr, ''); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 42); - assert.strictEqual(signal, null); - }); - - describe('should handle a throwing top-level body', () => { - it('should handle regular Error object', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - 'data:text/javascript,throw new Error("error message")', - fixtures.path('empty.js'), - ]); - - assert.match(stderr, /Error: error message\r?\n/); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 1); - assert.strictEqual(signal, null); - }); - - it('should handle null', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - 'data:text/javascript,throw null', - fixtures.path('empty.js'), - ]); - - assert.match(stderr, /\nnull\r?\n/); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 1); - assert.strictEqual(signal, null); - }); - - it('should handle undefined', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - 'data:text/javascript,throw undefined', - fixtures.path('empty.js'), - ]); - - assert.match(stderr, /\nundefined\r?\n/); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 1); - assert.strictEqual(signal, null); - }); - - it('should handle boolean', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - 'data:text/javascript,throw true', - fixtures.path('empty.js'), - ]); - - assert.match(stderr, /\ntrue\r?\n/); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 1); - assert.strictEqual(signal, null); - }); - - it('should handle empty plain object', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - 'data:text/javascript,throw {}', - fixtures.path('empty.js'), - ]); - - assert.match(stderr, /\n\{\}\r?\n/); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 1); - assert.strictEqual(signal, null); - }); - - it('should handle plain object', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - 'data:text/javascript,throw {fn(){},symbol:Symbol("symbol"),u:undefined}', - fixtures.path('empty.js'), - ]); - - assert.match(stderr, /\n\{ fn: \[Function: fn\], symbol: Symbol\(symbol\), u: undefined \}\r?\n/); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 1); - assert.strictEqual(signal, null); - }); - - it('should handle number', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - 'data:text/javascript,throw 1', - fixtures.path('empty.js'), - ]); - - assert.match(stderr, /\n1\r?\n/); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 1); - assert.strictEqual(signal, null); - }); - - it('should handle bigint', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - 'data:text/javascript,throw 1n', - fixtures.path('empty.js'), - ]); - - assert.match(stderr, /\n1\r?\n/); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 1); - assert.strictEqual(signal, null); - }); - - it('should handle string', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - 'data:text/javascript,throw "literal string"', - fixtures.path('empty.js'), - ]); - - assert.match(stderr, /\nliteral string\r?\n/); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 1); - assert.strictEqual(signal, null); - }); - - it('should handle symbol', async () => { - const { code, signal, stdout } = await spawnPromisified(execPath, [ - '--experimental-loader', - 'data:text/javascript,throw Symbol("symbol descriptor")', - fixtures.path('empty.js'), - ]); - - // Throwing a symbol doesn't produce any output - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 1); - assert.strictEqual(signal, null); - }); - - it('should handle function', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - 'data:text/javascript,throw function fnName(){}', - fixtures.path('empty.js'), - ]); - - assert.match(stderr, /\n\[Function: fnName\]\r?\n/); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 1); - assert.strictEqual(signal, null); - }); - }); - - describe('globalPreload', () => { - it('should emit warning', async () => { - const { stderr } = await spawnPromisified(execPath, [ - '--experimental-loader', - 'data:text/javascript,export function globalPreload(){}', - '--experimental-loader', - 'data:text/javascript,export function globalPreload(){return""}', - fixtures.path('empty.js'), - ]); - - assert.strictEqual(stderr.match(/`globalPreload` has been removed; use `initialize` instead/g).length, 1); - }); - - it('should not emit warning when initialize is supplied', async () => { - const { stderr } = await spawnPromisified(execPath, [ - '--experimental-loader', - 'data:text/javascript,export function globalPreload(){}export function initialize(){}', - fixtures.path('empty.js'), - ]); - - assert.doesNotMatch(stderr, /`globalPreload` has been removed; use `initialize` instead/); - }); - }); - - it('should be fine to call `process.removeAllListeners("beforeExit")` from the main thread', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - 'data:text/javascript,export function load(a,b,c){return new Promise(d=>setTimeout(()=>d(c(a,b)),99))}', - '--input-type=module', - '--eval', - 'setInterval(() => process.removeAllListeners("beforeExit"),1).unref();await import("data:text/javascript,")', - ]); - - assert.strictEqual(stderr, ''); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - - describe('`initialize`/`register`', () => { - it('should invoke `initialize` correctly', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/hooks-initialize.mjs'), - '--input-type=module', - '--eval', - 'import os from "node:os";', - ]); - - assert.strictEqual(stderr, ''); - assert.deepStrictEqual(stdout.split('\n'), ['hooks initialize 1', '']); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - - it('should allow communicating with loader via `register` ports', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--input-type=module', - '--eval', - ` - import {MessageChannel} from 'node:worker_threads'; - import {register} from 'node:module'; - import {once} from 'node:events'; - const {port1, port2} = new MessageChannel(); - port1.on('message', (msg) => { - console.log('message', msg); - }); - const result = register( - ${JSON.stringify(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(); - `, - ]); - - assert.strictEqual(stderr, ''); - assert.deepStrictEqual(stdout.split('\n'), [ 'register undefined', - 'message initialize', - 'message resolve node:os', - '' ]); - - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - - it('should have `register` accept URL objects as `parentURL`', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--import', - `data:text/javascript,${encodeURIComponent( - 'import{ register } from "node:module";' + - 'import { pathToFileURL } from "node:url";' + - 'register("./hooks-initialize.mjs", pathToFileURL("./"));' - )}`, - '--input-type=module', - '--eval', - ` - import {register} from 'node:module'; - register( - ${JSON.stringify(fixtures.fileURL('es-module-loaders/loader-load-foo-or-42.mjs'))}, - new URL('data:'), - ); - - import('node:os').then((result) => { - console.log(JSON.stringify(result)); - }); - `, - ], { cwd: fixtures.fileURL('es-module-loaders/') }); - - assert.strictEqual(stderr, ''); - assert.deepStrictEqual(stdout.split('\n').sort(), ['hooks initialize 1', '{"default":"foo"}', ''].sort()); - - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - - it('should have `register` work with cjs', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--input-type=commonjs', - '--eval', - ` - 'use strict'; - const {register} = require('node:module'); - register( - ${JSON.stringify(fixtures.fileURL('es-module-loaders/hooks-initialize.mjs'))}, - ); - register( - ${JSON.stringify(fixtures.fileURL('es-module-loaders/loader-load-foo-or-42.mjs'))}, - ); - - import('node:os').then((result) => { - console.log(JSON.stringify(result)); - }); - `, - ]); - - assert.strictEqual(stderr, ''); - assert.deepStrictEqual(stdout.split('\n').sort(), ['hooks initialize 1', '{"default":"foo"}', ''].sort()); - - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - - it('`register` should work with `require`', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--require', - fixtures.path('es-module-loaders/register-loader.cjs'), - '--input-type=module', - '--eval', - 'import "node:os";', - ]); - - assert.strictEqual(stderr, ''); - assert.deepStrictEqual(stdout.split('\n'), ['resolve passthru', 'resolve passthru', '']); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - - it('`register` should work with `import`', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--import', - fixtures.fileURL('es-module-loaders/register-loader.mjs'), - '--input-type=module', - '--eval', - 'import "node:os"', - ]); - - assert.strictEqual(stderr, ''); - assert.deepStrictEqual(stdout.split('\n'), ['resolve passthru', '']); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - - it('should execute `initialize` in sequence', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--input-type=module', - '--eval', - ` - import {register} from 'node:module'; - console.log('result 1', register( - ${JSON.stringify(fixtures.fileURL('es-module-loaders/hooks-initialize.mjs'))} - )); - console.log('result 2', register( - ${JSON.stringify(fixtures.fileURL('es-module-loaders/hooks-initialize.mjs'))} - )); - - await import('node:os'); - `, - ]); - - assert.strictEqual(stderr, ''); - assert.deepStrictEqual(stdout.split('\n'), [ 'hooks initialize 1', - 'result 1 undefined', - 'hooks initialize 2', - 'result 2 undefined', - '' ]); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - - it('should handle `initialize` returning never-settling promise', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--input-type=module', - '--eval', - ` - import {register} from 'node:module'; - try { - register('data:text/javascript,export function initialize(){return new Promise(()=>{})}'); - } catch (e) { - console.log('caught', e.code); - } - `, - ]); - - assert.strictEqual(stderr, ''); - assert.strictEqual(stdout.trim(), 'caught ERR_ASYNC_LOADER_REQUEST_NEVER_SETTLED'); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - - it('should handle `initialize` returning rejecting promise', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--input-type=module', - '--eval', - ` - import {register} from 'node:module'; - register('data:text/javascript,export function initialize(){return Promise.reject()}'); - `, - ]); - - assert.match(stderr, /undefined\r?\n/); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 1); - assert.strictEqual(signal, null); - }); - - it('should handle `initialize` throwing null', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--input-type=module', - '--eval', - ` - import {register} from 'node:module'; - register('data:text/javascript,export function initialize(){throw null}'); - `, - ]); - - assert.match(stderr, /null\r?\n/); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 1); - assert.strictEqual(signal, null); - }); - - it('should be fine to call `process.exit` from a initialize hook', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--input-type=module', - '--eval', - ` - import {register} from 'node:module'; - register('data:text/javascript,export function initialize(){process.exit(42);}'); - `, - ]); - - assert.strictEqual(stderr, ''); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 42); - assert.strictEqual(signal, null); - }); - }); - - it('should use CJS loader to respond to require.resolve calls by default', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/loader-resolve-passthru.mjs'), - fixtures.path('require-resolve.js'), - ]); - - assert.strictEqual(stderr, ''); - assert.strictEqual(stdout, 'resolve passthru\n'); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - - it('should use ESM loader to respond to require.resolve calls when opting in', async () => { - const readFile = async () => {}; - const fileURLToPath = () => {}; - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - `data:text/javascript,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; - }}`, - '--experimental-loader', - fixtures.fileURL('es-module-loaders/loader-resolve-passthru.mjs'), - fixtures.path('require-resolve.js'), - ]); - - assert.strictEqual(stderr, ''); - assert.strictEqual(stdout, 'resolve passthru\n'.repeat(10)); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - - it('should use hooks', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ - '--no-experimental-require-module', - '--import', - fixtures.fileURL('es-module-loaders/builtin-named-exports.mjs'), - fixtures.path('es-modules/require-esm-throws-with-loaders.js'), - ]); - - assert.strictEqual(stderr, ''); - assert.strictEqual(stdout, ''); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - - it('should support source maps in commonjs translator', async () => { - const readFile = async () => {}; - const hook = ` - 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; - } - }`; - - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--enable-source-maps', - '--import', - `data:text/javascript,${encodeURIComponent(` - import{ register } from "node:module"; - register(${ - JSON.stringify('data:text/javascript,' + encodeURIComponent(hook)) - }); - `)}`, - fixtures.path('source-map/throw-on-require.js'), - ]); - - assert.strictEqual(stdout, ''); - assert.match(stderr, /throw-on-require\.ts:9:9/); - assert.strictEqual(code, 1); - assert.strictEqual(signal, null); - }); - - it('should handle mixed of opt-in modules and non-opt-in ones', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - `data:text/javascript,const fixtures=${encodeURI(JSON.stringify(fixtures.path('empty.js')))};export ${ - encodeURIComponent(function resolve(s, c, n) { - if (s.endsWith('entry-point')) { - return { - shortCircuit: true, - url: 'file://github.com/c:/virtual-entry-point', - format: 'commonjs', - }; - } - return n(s, c); - }) - }export ${ - encodeURIComponent(async function load(u, c, n) { - if (u === 'file://github.com/c:/virtual-entry-point') { - return { - shortCircuit: true, - source: `"use strict";require(${JSON.stringify(fixtures)});console.log("Hello");`, - format: 'commonjs', - }; - } - return n(u, c); - })}`, - 'entry-point', - ]); - - assert.strictEqual(stderr, ''); - assert.strictEqual(stdout, 'Hello\n'); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); -}); diff --git a/test/fixtures/es-module-loaders/loader-delayed-async-load.mjs b/test/fixtures/es-module-loaders/loader-delayed-async-load.mjs new file mode 100644 index 00000000000000..33bae9bdafd9c9 --- /dev/null +++ b/test/fixtures/es-module-loaders/loader-delayed-async-load.mjs @@ -0,0 +1,3 @@ +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-exit-on-load.mjs b/test/fixtures/es-module-loaders/loader-exit-on-load.mjs new file mode 100644 index 00000000000000..4ee413fa46ce41 --- /dev/null +++ b/test/fixtures/es-module-loaders/loader-exit-on-load.mjs @@ -0,0 +1,4 @@ +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 new file mode 100644 index 00000000000000..cffa4bcc486a95 --- /dev/null +++ b/test/fixtures/es-module-loaders/loader-exit-on-resolve.mjs @@ -0,0 +1,4 @@ +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 new file mode 100644 index 00000000000000..6427ca06037fcd --- /dev/null +++ b/test/fixtures/es-module-loaders/loader-exit-top-level.mjs @@ -0,0 +1 @@ +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 new file mode 100644 index 00000000000000..7055b90f4e7568 --- /dev/null +++ b/test/fixtures/es-module-loaders/loader-globalpreload-and-initialize.mjs @@ -0,0 +1,2 @@ +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 new file mode 100644 index 00000000000000..6b7f59d6319804 --- /dev/null +++ b/test/fixtures/es-module-loaders/loader-globalpreload-only.mjs @@ -0,0 +1 @@ +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 new file mode 100644 index 00000000000000..f54cf72ce5b9f9 --- /dev/null +++ b/test/fixtures/es-module-loaders/loader-globalpreload-with-return.mjs @@ -0,0 +1,3 @@ +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 new file mode 100644 index 00000000000000..53cb4fa26f0164 --- /dev/null +++ b/test/fixtures/es-module-loaders/loader-initialize-exit.mjs @@ -0,0 +1,3 @@ +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 new file mode 100644 index 00000000000000..80033b663a5a0d --- /dev/null +++ b/test/fixtures/es-module-loaders/loader-initialize-never-settling.mjs @@ -0,0 +1,3 @@ +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 new file mode 100644 index 00000000000000..899accc15114e9 --- /dev/null +++ b/test/fixtures/es-module-loaders/loader-initialize-rejecting.mjs @@ -0,0 +1,3 @@ +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 new file mode 100644 index 00000000000000..3d834b5c23cfb3 --- /dev/null +++ b/test/fixtures/es-module-loaders/loader-initialize-throw-null.mjs @@ -0,0 +1,3 @@ +export function initialize() { + throw null; +} 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 new file mode 100644 index 00000000000000..3f431e630cddce --- /dev/null +++ b/test/fixtures/es-module-loaders/loader-load-commonjs-with-source.mjs @@ -0,0 +1,13 @@ +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-source-maps.mjs b/test/fixtures/es-module-loaders/loader-load-source-maps.mjs new file mode 100644 index 00000000000000..bf5ef6b91028a4 --- /dev/null +++ b/test/fixtures/es-module-loaders/loader-load-source-maps.mjs @@ -0,0 +1,9 @@ +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-mixed-opt-in.mjs b/test/fixtures/es-module-loaders/loader-mixed-opt-in.mjs new file mode 100644 index 00000000000000..69b17a88249bef --- /dev/null +++ b/test/fixtures/es-module-loaders/loader-mixed-opt-in.mjs @@ -0,0 +1,28 @@ +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://github.com/c:/virtual-entry-point', + format: 'commonjs', + }; + } + return n(s, c); +} + +export async function load(u, c, n) { + if (u === 'file://github.com/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-throw-bigint.mjs b/test/fixtures/es-module-loaders/loader-throw-bigint.mjs new file mode 100644 index 00000000000000..700b0387fa7ffc --- /dev/null +++ b/test/fixtures/es-module-loaders/loader-throw-bigint.mjs @@ -0,0 +1 @@ +throw 1n; diff --git a/test/fixtures/es-module-loaders/loader-throw-boolean.mjs b/test/fixtures/es-module-loaders/loader-throw-boolean.mjs new file mode 100644 index 00000000000000..874fbd92e5fa22 --- /dev/null +++ b/test/fixtures/es-module-loaders/loader-throw-boolean.mjs @@ -0,0 +1 @@ +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 new file mode 100644 index 00000000000000..778886fcb90a4d --- /dev/null +++ b/test/fixtures/es-module-loaders/loader-throw-empty-object.mjs @@ -0,0 +1 @@ +throw {}; diff --git a/test/fixtures/es-module-loaders/loader-throw-error.mjs b/test/fixtures/es-module-loaders/loader-throw-error.mjs new file mode 100644 index 00000000000000..816051b6c7831f --- /dev/null +++ b/test/fixtures/es-module-loaders/loader-throw-error.mjs @@ -0,0 +1 @@ +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 new file mode 100644 index 00000000000000..35503d670976cd --- /dev/null +++ b/test/fixtures/es-module-loaders/loader-throw-function.mjs @@ -0,0 +1 @@ +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 new file mode 100644 index 00000000000000..37d3d14b8bfe1b --- /dev/null +++ b/test/fixtures/es-module-loaders/loader-throw-null.mjs @@ -0,0 +1 @@ +throw null; diff --git a/test/fixtures/es-module-loaders/loader-throw-number.mjs b/test/fixtures/es-module-loaders/loader-throw-number.mjs new file mode 100644 index 00000000000000..6dbc3994c7ac5d --- /dev/null +++ b/test/fixtures/es-module-loaders/loader-throw-number.mjs @@ -0,0 +1 @@ +throw 1; diff --git a/test/fixtures/es-module-loaders/loader-throw-object.mjs b/test/fixtures/es-module-loaders/loader-throw-object.mjs new file mode 100644 index 00000000000000..fa7c9b7d43af4d --- /dev/null +++ b/test/fixtures/es-module-loaders/loader-throw-object.mjs @@ -0,0 +1 @@ +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 new file mode 100644 index 00000000000000..613a4321f50de6 --- /dev/null +++ b/test/fixtures/es-module-loaders/loader-throw-string.mjs @@ -0,0 +1 @@ +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 new file mode 100644 index 00000000000000..05876e0b4ada86 --- /dev/null +++ b/test/fixtures/es-module-loaders/loader-throw-symbol.mjs @@ -0,0 +1 @@ +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 new file mode 100644 index 00000000000000..38ecbdff9801f6 --- /dev/null +++ b/test/fixtures/es-module-loaders/loader-throw-undefined.mjs @@ -0,0 +1 @@ +throw undefined; diff --git a/test/fixtures/es-module-loaders/loader-worker-spawn.mjs b/test/fixtures/es-module-loaders/loader-worker-spawn.mjs new file mode 100644 index 00000000000000..2860d116e6478c --- /dev/null +++ b/test/fixtures/es-module-loaders/loader-worker-spawn.mjs @@ -0,0 +1,7 @@ +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/register-hooks-with-current-cwd-parent-url.mjs b/test/fixtures/es-module-loaders/register-hooks-with-current-cwd-parent-url.mjs new file mode 100644 index 00000000000000..580d092ab5655a --- /dev/null +++ b/test/fixtures/es-module-loaders/register-hooks-with-current-cwd-parent-url.mjs @@ -0,0 +1,3 @@ +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 new file mode 100644 index 00000000000000..1d82d2abc5576d --- /dev/null +++ b/test/fixtures/es-module-loaders/register-loader-with-url-parenturl.mjs @@ -0,0 +1,11 @@ +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/module-hooks/register-loader-in-sequence.mjs b/test/fixtures/module-hooks/register-loader-in-sequence.mjs new file mode 100644 index 00000000000000..c129fcfc18092d --- /dev/null +++ b/test/fixtures/module-hooks/register-loader-in-sequence.mjs @@ -0,0 +1,11 @@ +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 new file mode 100644 index 00000000000000..13091effa970bf --- /dev/null +++ b/test/fixtures/module-hooks/register-loader-initialize-never-settling.mjs @@ -0,0 +1,8 @@ +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 new file mode 100644 index 00000000000000..2cd6c666bd147d --- /dev/null +++ b/test/fixtures/module-hooks/register-loader-with-cjs.cjs @@ -0,0 +1,14 @@ +'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 new file mode 100644 index 00000000000000..45e125f96a208f --- /dev/null +++ b/test/fixtures/module-hooks/register-loader-with-ports.mjs @@ -0,0 +1,22 @@ +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/module-hooks/test-async-loader-hooks-called-with-expected-args.mjs b/test/module-hooks/test-async-loader-hooks-called-with-expected-args.mjs new file mode 100644 index 00000000000000..195bf02bb6fdb3 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-called-with-expected-args.mjs @@ -0,0 +1,28 @@ +// 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 new file mode 100644 index 00000000000000..d1f9181240d758 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-called-with-register.mjs @@ -0,0 +1,31 @@ +// 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 new file mode 100644 index 00000000000000..54501748bceac1 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-globalpreload-no-warning-with-initialize.mjs @@ -0,0 +1,21 @@ +// 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 new file mode 100644 index 00000000000000..36a2c0089a91d0 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-globalpreload-warning.mjs @@ -0,0 +1,23 @@ +// 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 new file mode 100644 index 00000000000000..bc973f58784527 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-initialize-in-sequence.mjs @@ -0,0 +1,21 @@ +// 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 new file mode 100644 index 00000000000000..72dc87c12e359a --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-initialize-invoke.mjs @@ -0,0 +1,22 @@ +// 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 new file mode 100644 index 00000000000000..d1c12238ef0ca6 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-initialize-never-settling.mjs @@ -0,0 +1,18 @@ +// 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 new file mode 100644 index 00000000000000..d9476af27e8f7d --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-initialize-process-exit.mjs @@ -0,0 +1,23 @@ +// 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 new file mode 100644 index 00000000000000..e5d2a62fbc86da --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-initialize-rejecting.mjs @@ -0,0 +1,24 @@ +// 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 new file mode 100644 index 00000000000000..69844e31ae91fc --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-initialize-throw-null.mjs @@ -0,0 +1,24 @@ +// 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 new file mode 100644 index 00000000000000..398df25d7bbe66 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-mixed-opt-in.mjs @@ -0,0 +1,20 @@ +// 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 new file mode 100644 index 00000000000000..35a4f7a566e519 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-never-settling-import-meta-resolve.mjs @@ -0,0 +1,20 @@ +// 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 new file mode 100644 index 00000000000000..05401b9080b339 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-never-settling-load-cjs.mjs @@ -0,0 +1,20 @@ +// 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 new file mode 100644 index 00000000000000..dbadecc3287672 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-never-settling-load-esm-no-warning.mjs @@ -0,0 +1,22 @@ +// 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 new file mode 100644 index 00000000000000..d4b30ae23f86f5 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-never-settling-load-esm-with-warning.mjs @@ -0,0 +1,21 @@ +// 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 new file mode 100644 index 00000000000000..1de677f888383b --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-never-settling-race-cjs.mjs @@ -0,0 +1,20 @@ +// 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 new file mode 100644 index 00000000000000..e662dc43210d72 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-never-settling-race-esm.mjs @@ -0,0 +1,20 @@ +// 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 new file mode 100644 index 00000000000000..1469a7b313cce2 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-never-settling-resolve-cjs.mjs @@ -0,0 +1,20 @@ +// 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 new file mode 100644 index 00000000000000..43a7a98127c851 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-never-settling-resolve-esm-no-warning.mjs @@ -0,0 +1,22 @@ +// 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 new file mode 100644 index 00000000000000..1a4c40c8bb96c0 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-never-settling-resolve-esm-with-warning.mjs @@ -0,0 +1,21 @@ +// 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 new file mode 100644 index 00000000000000..781aed8601f54a --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-no-leak-internals.mjs @@ -0,0 +1,20 @@ +// 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 new file mode 100644 index 00000000000000..af3b939bcf8c0e --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-process-exit-async.mjs @@ -0,0 +1,24 @@ +// 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 new file mode 100644 index 00000000000000..64c079b90b7507 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-process-exit-sync.mjs @@ -0,0 +1,24 @@ +// 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 new file mode 100644 index 00000000000000..a25c0013c5076d --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-process-exit-top-level.mjs @@ -0,0 +1,22 @@ +// 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 new file mode 100644 index 00000000000000..8d83ee6be87816 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-register-with-cjs.mjs @@ -0,0 +1,20 @@ +// 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 new file mode 100644 index 00000000000000..454eccb5c62478 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-register-with-import.mjs @@ -0,0 +1,22 @@ +// 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 new file mode 100644 index 00000000000000..022760596fd929 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-register-with-ports.mjs @@ -0,0 +1,18 @@ +// 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 new file mode 100644 index 00000000000000..87ea43f8a66c9d --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-register-with-require.mjs @@ -0,0 +1,22 @@ +// 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 new file mode 100644 index 00000000000000..a033ab3c4d2fdc --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-register-with-url-parenturl.mjs @@ -0,0 +1,26 @@ +// 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 new file mode 100644 index 00000000000000..9a28bb59dd3164 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-remove-beforeexit-listener.mjs @@ -0,0 +1,22 @@ +// 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-resolve-default.mjs b/test/module-hooks/test-async-loader-hooks-require-resolve-default.mjs new file mode 100644 index 00000000000000..71e539fe118b8b --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-require-resolve-default.mjs @@ -0,0 +1,20 @@ +// 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 new file mode 100644 index 00000000000000..0cbf2fd2d942a4 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-require-resolve-opt-in.mjs @@ -0,0 +1,22 @@ +// 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 new file mode 100644 index 00000000000000..83bd53405d1bd9 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-source-maps-cjs.mjs @@ -0,0 +1,23 @@ +// 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 new file mode 100644 index 00000000000000..34136f81bf2193 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-throw-bigint.mjs @@ -0,0 +1,22 @@ +// 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 new file mode 100644 index 00000000000000..476fad3c141535 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-throw-boolean.mjs @@ -0,0 +1,22 @@ +// 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 new file mode 100644 index 00000000000000..ac09704c28ca6d --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-throw-empty-object.mjs @@ -0,0 +1,22 @@ +// 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 new file mode 100644 index 00000000000000..b37ce50db1e3f4 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-throw-error.mjs @@ -0,0 +1,22 @@ +// 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 new file mode 100644 index 00000000000000..bb371f727c84c7 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-throw-function.mjs @@ -0,0 +1,22 @@ +// 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 new file mode 100644 index 00000000000000..33528ff2b97560 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-throw-null.mjs @@ -0,0 +1,22 @@ +// 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 new file mode 100644 index 00000000000000..da1f301089ca6c --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-throw-number.mjs @@ -0,0 +1,22 @@ +// 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 new file mode 100644 index 00000000000000..8280bd0d105bb9 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-throw-object.mjs @@ -0,0 +1,22 @@ +// 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 new file mode 100644 index 00000000000000..4ea6372e865c61 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-throw-string.mjs @@ -0,0 +1,22 @@ +// 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 new file mode 100644 index 00000000000000..44339537ba9ce8 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-throw-symbol.mjs @@ -0,0 +1,20 @@ +// 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 new file mode 100644 index 00000000000000..aff5292a857f59 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-throw-undefined.mjs @@ -0,0 +1,22 @@ +// 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 new file mode 100644 index 00000000000000..a86b0607246fa9 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-use-hooks-require-esm.mjs @@ -0,0 +1,20 @@ +// 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-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 new file mode 100644 index 00000000000000..9be7e8c39600a5 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-with-worker-permission-allowed.mjs @@ -0,0 +1,24 @@ +// 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 new file mode 100644 index 00000000000000..fe76f2f5842c00 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-with-worker-permission-restricted.mjs @@ -0,0 +1,25 @@ +// 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 new file mode 100644 index 00000000000000..21589234144db1 --- /dev/null +++ b/test/module-hooks/test-async-loader-hooks-without-worker-permission.mjs @@ -0,0 +1,25 @@ +// 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, + }, +);Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: