diff --git a/test/es-module/test-esm-loader-hooks.mjs b/test/es-module/test-esm-loader-hooks.mjs deleted file mode 100644 index 3ff5954c06ef54..00000000000000 --- a/test/es-module/test-esm-loader-hooks.mjs +++ /dev/null @@ -1,860 +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', { concurrency: !process.env.TEST_PARALLEL }, () => { - it('are called with all expected arguments', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--experimental-loader', - fixtures.fileURL('es-module-loaders/hooks-input.mjs'), - fixtures.path('es-modules/json-modules.mjs'), - ]); - - assert.strictEqual(stderr, ''); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - - const lines = stdout.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[4], ''); - assert.strictEqual(lines.length, 5); - }); - - it('are called with all expected arguments using register function', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(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'))});`, - ]); - - assert.strictEqual(stderr, ''); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - - const lines = stdout.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[4], ''); - assert.strictEqual(lines.length, 5); - }); - - describe('should handle never-settling hooks in ESM files', { concurrency: !process.env.TEST_PARALLEL }, () => { - it('top-level await of a never-settling resolve without warning', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(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'), - ]); - - assert.strictEqual(stderr, ''); - assert.match(stdout, /^should be output\r?\n$/); - assert.strictEqual(code, 13); - assert.strictEqual(signal, null); - }); - - it('top-level await of a never-settling resolve with warning', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(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'), - ]); - - assert.match(stderr, /Warning: Detected unsettled top-level await at.+never-resolve\.mjs:5/); - assert.match(stdout, /^should be output\r?\n$/); - assert.strictEqual(code, 13); - assert.strictEqual(signal, null); - }); - - it('top-level await of a never-settling load without warning', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(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'), - ]); - - assert.strictEqual(stderr, ''); - assert.match(stdout, /^should be output\r?\n$/); - assert.strictEqual(code, 13); - assert.strictEqual(signal, null); - }); - - it('top-level await of a never-settling load with warning', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(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'), - ]); - - assert.match(stderr, /Warning: Detected unsettled top-level await at.+never-load\.mjs:5/); - assert.match(stdout, /^should be output\r?\n$/); - assert.strictEqual(code, 13); - assert.strictEqual(signal, null); - }); - - it('top-level await of a race of never-settling hooks', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(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'), - ]); - - assert.strictEqual(stderr, ''); - assert.match(stdout, /^true\r?\n$/); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - - it('import.meta.resolve of a never-settling resolve should throw', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(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'), - ]); - - assert.strictEqual(stderr, ''); - assert.match(stdout, /^should be output\r?\n$/); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - }); - - describe('should handle never-settling hooks in CJS files', { concurrency: !process.env.TEST_PARALLEL }, () => { - it('never-settling resolve', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(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'), - ]); - - assert.strictEqual(stderr, ''); - assert.match(stdout, /^should be output\r?\n$/); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - - - it('never-settling load', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(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'), - ]); - - assert.strictEqual(stderr, ''); - assert.match(stdout, /^should be output\r?\n$/); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - - it('race of never-settling hooks', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(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'), - ]); - - assert.strictEqual(stderr, ''); - assert.match(stdout, /^true\r?\n$/); - assert.strictEqual(code, 0); - assert.strictEqual(signal, null); - }); - }); - - it('should not work without worker permission', async () => { - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ - '--no-warnings', - '--permission', - '--allow-fs-read', - '*', - '--experimental-loader', - fixtures.fileURL('empty.js'), - fixtures.path('es-modules/esm-top-level-await.mjs'), - ]); - - assert.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:///c:/virtual-entry-point', - format: 'commonjs', - }; - } - return n(s, c); - }) - }export ${ - encodeURIComponent(async function load(u, c, n) { - if (u === 'file:///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:///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-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, + }, +);