test: refactor ESM tests to improve performance

PR-URL: https://github.com/nodejs/node/pull/43784
Reviewed-By: Geoffrey Booth <webadmin@geoffreybooth.com>
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
This commit is contained in:
Jacob Smith 2022-07-29 10:42:55 +02:00 committed by GitHub
parent e5add6659d
commit 447635b440
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
56 changed files with 1386 additions and 1438 deletions

View file

@ -336,7 +336,7 @@ class ESMLoader {
* A list of exports from user-defined loaders (as returned by
* ESMLoader.import()).
*/
async addCustomLoaders(
addCustomLoaders(
customLoaders = [],
) {
for (let i = 0; i < customLoaders.length; i++) {

View file

@ -24,7 +24,7 @@
const process = global.process; // Some tests tamper with the process global.
const assert = require('assert');
const { exec, execSync, spawnSync } = require('child_process');
const { exec, execSync, spawn, spawnSync } = require('child_process');
const fs = require('fs');
// Do not require 'os' until needed so that test-os-checked-function can
// monkey patch it. If 'os' is required here, that test will fail.
@ -842,6 +842,36 @@ function requireNoPackageJSONAbove(dir = __dirname) {
}
}
function spawnPromisified(...args) {
let stderr = '';
let stdout = '';
const child = spawn(...args);
child.stderr.setEncoding('utf8');
child.stderr.on('data', (data) => { stderr += data; });
child.stdout.setEncoding('utf8');
child.stdout.on('data', (data) => { stdout += data; });
return new Promise((resolve, reject) => {
child.on('close', (code, signal) => {
resolve({
code,
signal,
stderr,
stdout,
});
});
child.on('error', (code, signal) => {
reject({
code,
signal,
stderr,
stdout,
});
});
});
}
const common = {
allowGlobals,
buildType,
@ -891,6 +921,7 @@ const common = {
skipIfEslintMissing,
skipIfInspectorDisabled,
skipIfWorker,
spawnPromisified,
get enoughTestMem() {
return require('os').totalmem() > 0x70000000; /* 1.75 Gb */

View file

@ -23,53 +23,7 @@ const {
hasCrypto,
hasIPv6,
childShouldThrowAndAbort,
createZeroFilledFile,
platformTimeout,
allowGlobals,
mustCall,
mustCallAtLeast,
mustSucceed,
hasMultiLocalhost,
skipIfDumbTerminal,
skipIfEslintMissing,
canCreateSymLink,
getCallSite,
mustNotCall,
mustNotMutateObjectDeep,
printSkipMessage,
skip,
nodeProcessAborted,
isAlive,
expectWarning,
expectsError,
skipIfInspectorDisabled,
skipIf32Bits,
getArrayBufferViews,
getBufferSources,
getTTYfd,
runWithInvalidFD
} = common;
export {
isMainThread,
isWindows,
isAIX,
isIBMi,
isLinuxPPCBE,
isSunOS,
isDumbTerminal,
isFreeBSD,
isOpenBSD,
isLinux,
isOSX,
enoughTestMem,
buildType,
localIPv6Hosts,
opensslCli,
PIPE,
hasCrypto,
hasIPv6,
childShouldThrowAndAbort,
checkoutEOL,
createZeroFilledFile,
platformTimeout,
allowGlobals,
@ -95,5 +49,55 @@ export {
getBufferSources,
getTTYfd,
runWithInvalidFD,
createRequire
spawnPromisified,
} = common;
export {
isMainThread,
isWindows,
isAIX,
isIBMi,
isLinuxPPCBE,
isSunOS,
isDumbTerminal,
isFreeBSD,
isOpenBSD,
isLinux,
isOSX,
enoughTestMem,
buildType,
localIPv6Hosts,
opensslCli,
PIPE,
hasCrypto,
hasIPv6,
childShouldThrowAndAbort,
checkoutEOL,
createZeroFilledFile,
platformTimeout,
allowGlobals,
mustCall,
mustCallAtLeast,
mustSucceed,
hasMultiLocalhost,
skipIfDumbTerminal,
skipIfEslintMissing,
canCreateSymLink,
getCallSite,
mustNotCall,
mustNotMutateObjectDeep,
printSkipMessage,
skip,
nodeProcessAborted,
isAlive,
expectWarning,
expectsError,
skipIfInspectorDisabled,
skipIf32Bits,
getArrayBufferViews,
getBufferSources,
getTTYfd,
runWithInvalidFD,
createRequire,
spawnPromisified,
};

View file

@ -1,10 +1,12 @@
'use strict';
const common = require('../common');
const fixtures = require('../common/fixtures');
const { spawn } = require('child_process');
const assert = require('assert');
const path = require('path');
const { spawnPromisified } = require('../common');
const fixtures = require('../common/fixtures.js');
const assert = require('node:assert');
const path = require('node:path');
const { execPath } = require('node:process');
const { describe, it } = require('node:test');
const requiringCjsAsEsm = path.resolve(fixtures.path('/es-modules/cjs-esm.js'));
const requiringEsm = path.resolve(fixtures.path('/es-modules/cjs-esm-esm.js'));
@ -12,53 +14,55 @@ const pjson = path.resolve(
fixtures.path('/es-modules/package-type-module/package.json')
);
{
const required = path.resolve(
fixtures.path('/es-modules/package-type-module/cjs.js')
);
const basename = 'cjs.js';
const child = spawn(process.execPath, [requiringCjsAsEsm]);
let stderr = '';
child.stderr.setEncoding('utf8');
child.stderr.on('data', (data) => {
stderr += data;
});
child.on('close', common.mustCall((code, signal) => {
describe('CJS ↔︎ ESM interop warnings', { concurrency: true }, () => {
it(async () => {
const required = path.resolve(
fixtures.path('/es-modules/package-type-module/cjs.js')
);
const basename = 'cjs.js';
const { code, signal, stderr } = await spawnPromisified(execPath, [requiringCjsAsEsm]);
assert.ok(
stderr.replaceAll('\r', '').includes(
`Error [ERR_REQUIRE_ESM]: require() of ES Module ${required} from ${requiringCjsAsEsm} not supported.\n`
)
);
assert.ok(
stderr.replaceAll('\r', '').includes(
`Instead rename ${basename} to end in .cjs, change the requiring ` +
'code to use dynamic import() which is available in all CommonJS ' +
`modules, or change "type": "module" to "type": "commonjs" in ${pjson} to ` +
'treat all .js files as CommonJS (using .mjs for all ES modules ' +
'instead).\n'
)
);
assert.strictEqual(code, 1);
assert.strictEqual(signal, null);
assert.ok(stderr.replaceAll('\r', '').includes(
`Error [ERR_REQUIRE_ESM]: require() of ES Module ${required} from ${
requiringCjsAsEsm} not supported.\n`));
assert.ok(stderr.replaceAll('\r', '').includes(
`Instead rename ${basename} to end in .cjs, change the requiring ` +
'code to use dynamic import() which is available in all CommonJS ' +
`modules, or change "type": "module" to "type": "commonjs" in ${pjson} to ` +
'treat all .js files as CommonJS (using .mjs for all ES modules ' +
'instead).\n'));
}));
}
{
const required = path.resolve(
fixtures.path('/es-modules/package-type-module/esm.js')
);
const basename = 'esm.js';
const child = spawn(process.execPath, [requiringEsm]);
let stderr = '';
child.stderr.setEncoding('utf8');
child.stderr.on('data', (data) => {
stderr += data;
});
child.on('close', common.mustCall((code, signal) => {
it(async () => {
const required = path.resolve(
fixtures.path('/es-modules/package-type-module/esm.js')
);
const basename = 'esm.js';
const { code, signal, stderr } = await spawnPromisified(execPath, [requiringEsm]);
assert.ok(
stderr.replace(/\r/g, '').includes(
`Error [ERR_REQUIRE_ESM]: require() of ES Module ${required} from ${requiringEsm} not supported.\n`
)
);
assert.ok(
stderr.replace(/\r/g, '').includes(
`Instead change the require of ${basename} in ${requiringEsm} to` +
' a dynamic import() which is available in all CommonJS modules.\n'
)
);
assert.strictEqual(code, 1);
assert.strictEqual(signal, null);
assert.ok(stderr.replace(/\r/g, '').includes(
`Error [ERR_REQUIRE_ESM]: require() of ES Module ${required} from ${
requiringEsm} not supported.\n`));
assert.ok(stderr.replace(/\r/g, '').includes(
`Instead change the require of ${basename} in ${requiringEsm} to` +
' a dynamic import() which is available in all CommonJS modules.\n'));
}));
}
});
});

View file

@ -1,21 +1,20 @@
'use strict';
const common = require('../common');
const fixtures = require('../common/fixtures');
const { spawn } = require('child_process');
const assert = require('assert');
const { spawnPromisified } = require('../common');
const fixtures = require('../common/fixtures.js');
const assert = require('node:assert');
const { execPath } = require('node:process');
const { describe, it } = require('node:test');
const entry = fixtures.path('/es-modules/builtin-imports-case.mjs');
const child = spawn(process.execPath, [entry]);
child.stderr.setEncoding('utf8');
let stdout = '';
child.stdout.setEncoding('utf8');
child.stdout.on('data', (data) => {
stdout += data;
describe('ESM: importing builtins & CJS', () => {
it('should work', async () => {
const { code, signal, stdout } = await spawnPromisified(execPath, [entry]);
assert.strictEqual(code, 0);
assert.strictEqual(signal, null);
assert.strictEqual(stdout, 'ok\n');
});
});
child.on('close', common.mustCall((code, signal) => {
assert.strictEqual(code, 0);
assert.strictEqual(signal, null);
assert.strictEqual(stdout, 'ok\n');
}));

View file

@ -1,35 +1,29 @@
'use strict';
const common = require('../common');
const fixtures = require('../common/fixtures');
const { spawn } = require('child_process');
const assert = require('assert');
const { spawnPromisified } = require('../common');
const fixtures = require('../common/fixtures.js');
const assert = require('node:assert');
const { execPath } = require('node:process');
const { describe, it } = require('node:test');
const entry = fixtures.path('/es-modules/cjs-exports.mjs');
let child = spawn(process.execPath, [entry]);
child.stderr.setEncoding('utf8');
let stdout = '';
child.stdout.setEncoding('utf8');
child.stdout.on('data', (data) => {
stdout += data;
describe('ESM: importing CJS', { concurrency: true }, () => {
it('should support valid CJS exports', async () => {
const validEntry = fixtures.path('/es-modules/cjs-exports.mjs');
const { code, signal, stdout } = await spawnPromisified(execPath, [validEntry]);
assert.strictEqual(code, 0);
assert.strictEqual(signal, null);
assert.strictEqual(stdout, 'ok\n');
});
it('should eror on invalid CJS exports', async () => {
const invalidEntry = fixtures.path('/es-modules/cjs-exports-invalid.mjs');
const { code, signal, stderr } = await spawnPromisified(execPath, [invalidEntry]);
assert.strictEqual(code, 1);
assert.strictEqual(signal, null);
assert.ok(stderr.includes('Warning: To load an ES module'));
assert.ok(stderr.includes('Unexpected token \'export\''));
});
});
child.on('close', common.mustCall((code, signal) => {
assert.strictEqual(code, 0);
assert.strictEqual(signal, null);
assert.strictEqual(stdout, 'ok\n');
}));
const entryInvalid = fixtures.path('/es-modules/cjs-exports-invalid.mjs');
child = spawn(process.execPath, [entryInvalid]);
let stderr = '';
child.stderr.setEncoding('utf8');
child.stderr.on('data', (data) => {
stderr += data;
});
child.on('close', common.mustCall((code, signal) => {
assert.strictEqual(code, 1);
assert.strictEqual(signal, null);
assert.ok(stderr.includes('Warning: To load an ES module'));
assert.ok(stderr.includes('Unexpected token \'export\''));
}));

View file

@ -1,163 +1,96 @@
import { mustCall } from '../common/index.mjs';
import assert from 'assert';
import fixtures from '../common/fixtures.js';
import { spawn } from 'child_process';
import { spawnPromisified } from '../common/index.mjs';
import * as fixtures from '../common/fixtures.mjs';
import assert from 'node:assert';
import { execPath } from 'node:process';
import { describe, it } from 'node:test';
const Export1 = fixtures.path('/es-modules/es-note-unexpected-export-1.cjs');
const Export2 = fixtures.path('/es-modules/es-note-unexpected-export-2.cjs');
const Import1 = fixtures.path('/es-modules/es-note-unexpected-import-1.cjs');
const Import2 = fixtures.path('/es-modules/es-note-promiserej-import-2.cjs');
const Import3 = fixtures.path('/es-modules/es-note-unexpected-import-3.cjs');
const Import4 = fixtures.path('/es-modules/es-note-unexpected-import-4.cjs');
const Import5 = fixtures.path('/es-modules/es-note-unexpected-import-5.cjs');
const Error1 = fixtures.path('/es-modules/es-note-error-1.mjs');
const Error2 = fixtures.path('/es-modules/es-note-error-2.mjs');
const Error3 = fixtures.path('/es-modules/es-note-error-3.mjs');
const Error4 = fixtures.path('/es-modules/es-note-error-4.mjs');
// Expect note to be included in the error output
const expectedNote = 'To load an ES module, ' +
'set "type": "module" in the package.json ' +
'or use the .mjs extension.';
const expectedCode = 1;
const mustIncludeMessage = {
getMessage: () => (stderr) => `${expectedNote} not found in ${stderr}`,
includeNote: true,
};
const mustNotIncludeMessage = {
getMessage: () => (stderr) => `${expectedNote} must not be included in ${stderr}`,
includeNote: false,
};
const pExport1 = spawn(process.execPath, [Export1]);
let pExport1Stderr = '';
pExport1.stderr.setEncoding('utf8');
pExport1.stderr.on('data', (data) => {
pExport1Stderr += data;
describe('ESM: Errors for unexpected exports', { concurrency: true }, () => {
for (
const { errorNeedle, filePath, getMessage, includeNote }
of [
{
// name: '',
filePath: fixtures.path('/es-modules/es-note-unexpected-export-1.cjs'),
...mustIncludeMessage,
},
{
// name: '',
filePath: fixtures.path('/es-modules/es-note-unexpected-import-1.cjs'),
...mustIncludeMessage,
},
{
// name: '',
filePath: fixtures.path('/es-modules/es-note-promiserej-import-2.cjs'),
...mustNotIncludeMessage,
},
{
// name: '',
filePath: fixtures.path('/es-modules/es-note-unexpected-import-3.cjs'),
...mustIncludeMessage,
},
{
// name: '',
filePath: fixtures.path('/es-modules/es-note-unexpected-import-4.cjs'),
...mustIncludeMessage,
},
{
// name: '',
filePath: fixtures.path('/es-modules/es-note-unexpected-import-5.cjs'),
...mustNotIncludeMessage,
},
{
// name: '',
filePath: fixtures.path('/es-modules/es-note-error-1.mjs'),
...mustNotIncludeMessage,
errorNeedle: /Error: some error/,
},
{
// name: '',
filePath: fixtures.path('/es-modules/es-note-error-2.mjs'),
...mustNotIncludeMessage,
errorNeedle: /string/,
},
{
// name: '',
filePath: fixtures.path('/es-modules/es-note-error-3.mjs'),
...mustNotIncludeMessage,
errorNeedle: /null/,
},
{
// name: '',
filePath: fixtures.path('/es-modules/es-note-error-4.mjs'),
...mustNotIncludeMessage,
errorNeedle: /undefined/,
},
]
) {
it(`should ${includeNote ? '' : 'NOT'} include note`, async () => {
const { code, stderr } = await spawnPromisified(execPath, [filePath]);
assert.strictEqual(code, 1);
if (errorNeedle != null) assert.match(stderr, errorNeedle);
const shouldIncludeNote = stderr.includes(expectedNote);
assert.ok(
includeNote ? shouldIncludeNote : !shouldIncludeNote,
`${filePath} ${getMessage(stderr)}`,
);
});
}
});
pExport1.on('close', mustCall((code) => {
assert.strictEqual(code, expectedCode);
assert.ok(pExport1Stderr.includes(expectedNote),
`${expectedNote} not found in ${pExport1Stderr}`);
}));
const pExport2 = spawn(process.execPath, [Export2]);
let pExport2Stderr = '';
pExport2.stderr.setEncoding('utf8');
pExport2.stderr.on('data', (data) => {
pExport2Stderr += data;
});
pExport2.on('close', mustCall((code) => {
assert.strictEqual(code, expectedCode);
assert.ok(pExport2Stderr.includes(expectedNote),
`${expectedNote} not found in ${pExport2Stderr}`);
}));
const pImport1 = spawn(process.execPath, [Import1]);
let pImport1Stderr = '';
pImport1.stderr.setEncoding('utf8');
pImport1.stderr.on('data', (data) => {
pImport1Stderr += data;
});
pImport1.on('close', mustCall((code) => {
assert.strictEqual(code, expectedCode);
assert.ok(pImport1Stderr.includes(expectedNote),
`${expectedNote} not found in ${pExport1Stderr}`);
}));
// Note this test shouldn't include the note
const pImport2 = spawn(process.execPath, [Import2]);
let pImport2Stderr = '';
pImport2.stderr.setEncoding('utf8');
pImport2.stderr.on('data', (data) => {
pImport2Stderr += data;
});
pImport2.on('close', mustCall((code) => {
assert.strictEqual(code, expectedCode);
assert.ok(!pImport2Stderr.includes(expectedNote),
`${expectedNote} must not be included in ${pImport2Stderr}`);
}));
const pImport3 = spawn(process.execPath, [Import3]);
let pImport3Stderr = '';
pImport3.stderr.setEncoding('utf8');
pImport3.stderr.on('data', (data) => {
pImport3Stderr += data;
});
pImport3.on('close', mustCall((code) => {
assert.strictEqual(code, expectedCode);
assert.ok(pImport3Stderr.includes(expectedNote),
`${expectedNote} not found in ${pImport3Stderr}`);
}));
const pImport4 = spawn(process.execPath, [Import4]);
let pImport4Stderr = '';
pImport4.stderr.setEncoding('utf8');
pImport4.stderr.on('data', (data) => {
pImport4Stderr += data;
});
pImport4.on('close', mustCall((code) => {
assert.strictEqual(code, expectedCode);
assert.ok(pImport4Stderr.includes(expectedNote),
`${expectedNote} not found in ${pImport4Stderr}`);
}));
// Must exit non-zero and show note
const pImport5 = spawn(process.execPath, [Import5]);
let pImport5Stderr = '';
pImport5.stderr.setEncoding('utf8');
pImport5.stderr.on('data', (data) => {
pImport5Stderr += data;
});
pImport5.on('close', mustCall((code) => {
assert.strictEqual(code, expectedCode);
assert.ok(!pImport5Stderr.includes(expectedNote),
`${expectedNote} must not be included in ${pImport5Stderr}`);
}));
const pError1 = spawn(process.execPath, [Error1]);
let pError1Stderr = '';
pError1.stderr.setEncoding('utf8');
pError1.stderr.on('data', (data) => {
pError1Stderr += data;
});
pError1.on('close', mustCall((code) => {
assert.strictEqual(code, expectedCode);
assert.ok(pError1Stderr.includes('Error: some error'));
assert.ok(!pError1Stderr.includes(expectedNote),
`${expectedNote} must not be included in ${pError1Stderr}`);
}));
const pError2 = spawn(process.execPath, [Error2]);
let pError2Stderr = '';
pError2.stderr.setEncoding('utf8');
pError2.stderr.on('data', (data) => {
pError2Stderr += data;
});
pError2.on('close', mustCall((code) => {
assert.strictEqual(code, expectedCode);
assert.ok(pError2Stderr.includes('string'));
assert.ok(!pError2Stderr.includes(expectedNote),
`${expectedNote} must not be included in ${pError2Stderr}`);
}));
const pError3 = spawn(process.execPath, [Error3]);
let pError3Stderr = '';
pError3.stderr.setEncoding('utf8');
pError3.stderr.on('data', (data) => {
pError3Stderr += data;
});
pError3.on('close', mustCall((code) => {
assert.strictEqual(code, expectedCode);
assert.ok(pError3Stderr.includes('null'));
assert.ok(!pError3Stderr.includes(expectedNote),
`${expectedNote} must not be included in ${pError3Stderr}`);
}));
const pError4 = spawn(process.execPath, [Error4]);
let pError4Stderr = '';
pError4.stderr.setEncoding('utf8');
pError4.stderr.on('data', (data) => {
pError4Stderr += data;
});
pError4.on('close', mustCall((code) => {
assert.strictEqual(code, expectedCode);
assert.ok(pError4Stderr.includes('undefined'));
assert.ok(!pError4Stderr.includes(expectedNote),
`${expectedNote} must not be included in ${pError4Stderr}`);
}));

View file

@ -1,21 +1,20 @@
'use strict';
const common = require('../common');
const fixtures = require('../common/fixtures');
const { spawn } = require('child_process');
const assert = require('assert');
const { spawnPromisified } = require('../common');
const fixtures = require('../common/fixtures.js');
const assert = require('node:assert');
const { execPath } = require('node:process');
const { describe, it } = require('node:test');
const entry = fixtures.path('/es-modules/cjs.js');
const child = spawn(process.execPath, [entry]);
child.stderr.setEncoding('utf8');
let stdout = '';
child.stdout.setEncoding('utf8');
child.stdout.on('data', (data) => {
stdout += data;
describe('ESM: importing CJS', () => {
it('should work', async () => {
const { code, signal, stdout } = await spawnPromisified(execPath, [
fixtures.path('/es-modules/cjs.js'),
]);
assert.strictEqual(code, 0);
assert.strictEqual(signal, null);
assert.strictEqual(stdout, 'executed\n');
});
});
child.on('close', common.mustCall((code, signal) => {
assert.strictEqual(code, 0);
assert.strictEqual(signal, null);
assert.strictEqual(stdout, 'executed\n');
}));

View file

@ -1,11 +1,18 @@
'use strict';
require('../common');
const fixtures = require('../common/fixtures');
const assert = require('assert');
const { spawn } = require('child_process');
const native = fixtures.path('es-module-url/native.mjs');
const child = spawn(process.execPath, [native]);
child.on('exit', (code) => {
assert.strictEqual(code, 1);
const { spawnPromisified } = require('../common');
const fixtures = require('../common/fixtures.js');
const assert = require('node:assert');
const { execPath } = require('node:process');
const { describe, it } = require('node:test');
describe('ESM: importing an encoded path', () => {
it('should throw', async () => {
const { code } = await spawnPromisified(execPath, [
fixtures.path('es-module-url/native.mjs'),
]);
assert.strictEqual(code, 1);
});
});

View file

@ -1,55 +1,48 @@
import { mustCall } from '../common/index.mjs';
import { spawnPromisified } from '../common/index.mjs';
import { fileURL } from '../common/fixtures.mjs';
import { doesNotMatch, match, strictEqual } from 'assert';
import { spawn } from 'child_process';
import { execPath } from 'process';
import { doesNotMatch, match, strictEqual } from 'node:assert';
import { execPath } from 'node:process';
import { describe, it } from 'node:test';
// Verify no warnings are printed when no experimental features are enabled or used
{
const input = `import ${JSON.stringify(fileURL('es-module-loaders', 'module-named-exports.mjs'))}`;
const child = spawn(execPath, [
'--input-type=module',
'--eval',
input,
]);
let stderr = '';
child.stderr.setEncoding('utf8');
child.stderr.on('data', (data) => { stderr += data; });
child.on('close', mustCall((code, signal) => {
strictEqual(code, 0);
strictEqual(signal, null);
describe('ESM: warn for obsolete hooks provided', { concurrency: true }, () => {
it('should not print warnings when no experimental features are enabled or used', async () => {
const { code, signal, stderr } = await spawnPromisified(execPath, [
'--input-type=module',
'--eval',
`import ${JSON.stringify(fileURL('es-module-loaders', 'module-named-exports.mjs'))}`,
]);
doesNotMatch(
stderr,
/ExperimentalWarning/,
new Error('No experimental warning(s) should be emitted when no experimental feature is enabled')
);
}));
}
// Verify experimental warning is printed when experimental feature is enabled
for (
const [experiment, arg] of [
[/Custom ESM Loaders/, `--experimental-loader=${fileURL('es-module-loaders', 'hooks-custom.mjs')}`],
[/Network Imports/, '--experimental-network-imports'],
[/specifier resolution/, '--experimental-specifier-resolution=node'],
]
) {
const input = `import ${JSON.stringify(fileURL('es-module-loaders', 'module-named-exports.mjs'))}`;
const child = spawn(execPath, [
arg,
'--input-type=module',
'--eval',
input,
]);
let stderr = '';
child.stderr.setEncoding('utf8');
child.stderr.on('data', (data) => { stderr += data; });
child.on('close', mustCall((code, signal) => {
strictEqual(code, 0);
strictEqual(signal, null);
match(stderr, /ExperimentalWarning/);
match(stderr, experiment);
}));
}
});
describe('experimental warnings for enabled experimental feature', () => {
for (
const [experiment, arg] of [
[/Custom ESM Loaders/, `--experimental-loader=${fileURL('es-module-loaders', 'hooks-custom.mjs')}`],
[/Network Imports/, '--experimental-network-imports'],
[/specifier resolution/, '--experimental-specifier-resolution=node'],
]
) {
it(`should print for ${experiment.toString().replaceAll('/', '')}`, async () => {
const { code, signal, stderr } = await spawnPromisified(execPath, [
arg,
'--input-type=module',
'--eval',
`import ${JSON.stringify(fileURL('es-module-loaders', 'module-named-exports.mjs'))}`,
]);
match(stderr, /ExperimentalWarning/);
match(stderr, experiment);
strictEqual(code, 0);
strictEqual(signal, null);
});
}
});
});

View file

@ -1,39 +1,48 @@
import { mustCall } from '../common/index.mjs';
import { path } from '../common/fixtures.mjs';
import { match, notStrictEqual } from 'assert';
import { spawn } from 'child_process';
import { execPath } from 'process';
import { spawnPromisified } from '../common/index.mjs';
import * as fixtures from '../common/fixtures.mjs';
import assert from 'node:assert';
import { execPath } from 'node:process';
import { describe, it } from 'node:test';
const importStatement =
'import { foo, notfound } from \'./module-named-exports.mjs\';';
const importStatement = 'import { foo, notfound } from \'./module-named-exports.mjs\';';
const importStatementMultiline = `import {
foo,
notfound
} from './module-named-exports.mjs';
`;
[importStatement, importStatementMultiline].forEach((input) => {
const child = spawn(execPath, [
'--input-type=module',
'--eval',
input,
], {
cwd: path('es-module-loaders'),
});
describe('ESM: nonexistent exports', { concurrency: true }, () => {
for (
const { name, input }
of [
{
input: importStatement,
name: 'single-line import',
},
{
input: importStatementMultiline,
name: 'multi-line import',
},
]
) {
it(`should throw for nonexistent exports via ${name}`, async () => {
const { code, stderr } = await spawnPromisified(execPath, [
'--input-type=module',
'--eval',
input,
], {
cwd: fixtures.path('es-module-loaders'),
});
let stderr = '';
child.stderr.setEncoding('utf8');
child.stderr.on('data', (data) => {
stderr += data;
});
child.on('close', mustCall((code, _signal) => {
notStrictEqual(code, 0);
assert.notStrictEqual(code, 0);
// SyntaxError: The requested module './module-named-exports.mjs'
// does not provide an export named 'notfound'
match(stderr, /SyntaxError:/);
// The quotes ensure that the path starts with ./ and not ../
match(stderr, /'\.\/module-named-exports\.mjs'/);
match(stderr, /notfound/);
}));
// SyntaxError: The requested module './module-named-exports.mjs'
// does not provide an export named 'notfound'
assert.match(stderr, /SyntaxError:/);
// The quotes ensure that the path starts with ./ and not ../
assert.match(stderr, /'\.\/module-named-exports\.mjs'/);
assert.match(stderr, /notfound/);
});
}
});

View file

@ -1,24 +1,22 @@
import { mustCall } from '../common/index.mjs';
import { path } from '../common/fixtures.mjs';
import { match, notStrictEqual } from 'assert';
import { spawn } from 'child_process';
import { execPath } from 'process';
import { spawnPromisified } from '../common/index.mjs';
import * as fixtures from '../common/fixtures.mjs';
import assert from 'node:assert';
import { execPath } from 'node:process';
import { describe, it } from 'node:test';
const child = spawn(execPath, [
path('es-modules', 'import-json-named-export.mjs'),
]);
let stderr = '';
child.stderr.setEncoding('utf8');
child.stderr.on('data', (data) => {
stderr += data;
describe('ESM: named JSON exports', { concurrency: true }, () => {
it('should throw, citing named import', async () => {
const { code, stderr } = await spawnPromisified(execPath, [
fixtures.path('es-modules', 'import-json-named-export.mjs'),
]);
// SyntaxError: The requested module '../experimental.json'
// does not provide an export named 'ofLife'
assert.match(stderr, /SyntaxError:/);
assert.match(stderr, /'\.\.\/experimental\.json'/);
assert.match(stderr, /'ofLife'/);
assert.notStrictEqual(code, 0);
});
});
child.on('close', mustCall((code, _signal) => {
notStrictEqual(code, 0);
// SyntaxError: The requested module '../experimental.json'
// does not provide an export named 'ofLife'
match(stderr, /SyntaxError:/);
match(stderr, /'\.\.\/experimental\.json'/);
match(stderr, /'ofLife'/);
}));

View file

@ -1,30 +1,29 @@
import '../common/index.mjs';
import { spawnPromisified } from '../common/index.mjs';
import * as fixtures from '../common/fixtures.mjs';
import assert from 'node:assert';
import { spawnSync } from 'node:child_process';
import { execPath } from 'node:process';
import { describe, it } from 'node:test';
{ // Verify unadulterated source is loaded when there are no loaders
const { status, stderr, stdout } = spawnSync(
process.execPath,
[
describe('ESM: ensure initialisation happens only once', { concurrency: true }, () => {
it(async () => {
const { code, stderr, stdout } = await spawnPromisified(execPath, [
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-resolve-passthru.mjs'),
'--no-warnings',
fixtures.path('es-modules', 'runmain.mjs'),
],
{ encoding: 'utf8' },
);
]);
// Length minus 1 because the first match is the needle.
const resolveHookRunCount = (stdout.match(/resolve passthru/g)?.length ?? 0) - 1;
// Length minus 1 because the first match is the needle.
const resolveHookRunCount = (stdout.match(/resolve passthru/g)?.length ?? 0) - 1;
assert.strictEqual(stderr, '');
/**
* resolveHookRunCount = 2:
* 1. fixtures//runmain.mjs
* 2. node:module (imported by fixtures//runmain.mjs)
*/
assert.strictEqual(resolveHookRunCount, 2);
assert.strictEqual(status, 0);
}
assert.strictEqual(stderr, '');
/**
* resolveHookRunCount = 2:
* 1. fixtures//runmain.mjs
* 2. node:module (imported by fixtures//runmain.mjs)
*/
assert.strictEqual(resolveHookRunCount, 2);
assert.strictEqual(code, 0);
});
});

View file

@ -1,13 +0,0 @@
'use strict';
require('../common');
const fixtures = require('../common/fixtures');
const assert = require('assert');
const { spawnSync } = require('child_process');
const fixture = fixtures.path('/es-modules/import-invalid-ext.mjs');
const child = spawnSync(process.execPath, [fixture]);
const errMsg = 'TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension';
assert.strictEqual(child.status, 1);
assert.strictEqual(child.signal, null);
assert.strictEqual(child.stdout.toString().trim(), '');
assert.ok(child.stderr.toString().includes(errMsg));

View file

@ -1,27 +1,28 @@
'use strict';
const { mustCall, checkoutEOL } = require('../common');
const fixtures = require('../common/fixtures');
const { spawn } = require('child_process');
const { strictEqual, ok } = require('assert');
const { checkoutEOL, spawnPromisified } = require('../common');
const fixtures = require('../common/fixtures.js');
const assert = require('node:assert');
const { execPath } = require('node:process');
const { describe, it } = require('node:test');
const entry = fixtures.path('/es-modules/import-invalid-pjson.mjs');
const invalidJson = fixtures.path('/node_modules/invalid-pjson/package.json');
const child = spawn(process.execPath, [entry]);
child.stderr.setEncoding('utf8');
let stderr = '';
child.stderr.on('data', (data) => {
stderr += data;
describe('ESM: Package.json', { concurrency: true }, () => {
it('should throw on invalid pson', async () => {
const entry = fixtures.path('/es-modules/import-invalid-pjson.mjs');
const invalidJson = fixtures.path('/node_modules/invalid-pjson/package.json');
const { code, signal, stderr } = await spawnPromisified(execPath, [entry]);
assert.ok(
stderr.includes(
`[ERR_INVALID_PACKAGE_CONFIG]: Invalid package config ${invalidJson} ` +
`while importing "invalid-pjson" from ${entry}. ` +
`Unexpected token } in JSON at position ${12 + checkoutEOL.length * 2}`
),
stderr
);
assert.strictEqual(code, 1);
assert.strictEqual(signal, null);
});
});
child.on('close', mustCall((code, signal) => {
strictEqual(code, 1);
strictEqual(signal, null);
ok(
stderr.includes(
`[ERR_INVALID_PACKAGE_CONFIG]: Invalid package config ${invalidJson} ` +
`while importing "invalid-pjson" from ${entry}. ` +
`Unexpected token } in JSON at position ${12 + checkoutEOL.length * 2}`
),
stderr);
}));

View file

@ -1,27 +1,25 @@
import '../common/index.mjs';
import { path } from '../common/fixtures.mjs';
import { strictEqual, ok } from 'assert';
import { spawn } from 'child_process';
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';
import secret from '../fixtures/experimental.json' assert { type: 'json' };
strictEqual(secret.ofLife, 42);
// Test warning message
const child = spawn(process.execPath, [
path('/es-modules/json-modules.mjs'),
]);
describe('ESM: importing JSON', () => {
it('should load JSON', () => {
assert.strictEqual(secret.ofLife, 42);
});
let stderr = '';
child.stderr.setEncoding('utf8');
child.stderr.on('data', (data) => {
stderr += data;
});
child.on('close', (code, signal) => {
strictEqual(code, 0);
strictEqual(signal, null);
ok(stderr.toString().includes(
'ExperimentalWarning: Importing JSON modules is an experimental feature. ' +
'This feature could change at any time'
));
it('should print an experimental warning', async () => {
const { code, signal, stderr } = await spawnPromisified(execPath, [
fixtures.path('/es-modules/json-modules.mjs'),
]);
assert.match(stderr, /ExperimentalWarning/);
assert.match(stderr, /JSON modules/);
assert.strictEqual(code, 0);
assert.strictEqual(signal, null);
});
});

View file

@ -1,7 +1,9 @@
import '../common/index.mjs';
import fixtures from '../common/fixtures.js';
import { spawnPromisified } from '../common/index.mjs';
import * as fixtures from '../common/fixtures.mjs';
import assert from 'node:assert';
import { spawnSync } from 'node:child_process';
import { execPath } from 'node:process';
import { describe, it } from 'node:test';
const setupArgs = [
'--no-warnings',
@ -14,420 +16,420 @@ const commonArgs = [
commonInput,
];
{ // Verify unadulterated source is loaded when there are no loaders
const { status, stderr, stdout } = spawnSync(
process.execPath,
[
...setupArgs,
'import fs from "node:fs"; console.log(typeof fs?.constants?.F_OK )',
],
{ encoding: 'utf8' },
);
describe('ESM: loader chaining', { concurrency: true }, () => {
it('should load unadulterated source when there are no loaders', async () => {
const { code, stderr, stdout } = await spawnPromisified(
execPath,
[
...setupArgs,
'import fs from "node:fs"; console.log(typeof fs?.constants?.F_OK )',
],
{ encoding: 'utf8' },
);
assert.strictEqual(stderr, '');
assert.match(stdout, /number/); // node:fs is an object
assert.strictEqual(status, 0);
}
assert.strictEqual(stderr, '');
assert.match(stdout, /number/); // node:fs is an object
assert.strictEqual(code, 0);
});
{ // Verify loaded source is properly different when only load changes something
const { status, stderr, stdout } = spawnSync(
process.execPath,
[
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-resolve-passthru.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-load-foo-or-42.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-load-passthru.mjs'),
...commonArgs,
],
{ encoding: 'utf8' },
);
it('should load properly different source when only load changes something', async () => {
const { code, stderr, stdout } = await spawnPromisified(
execPath,
[
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-resolve-passthru.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-load-foo-or-42.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-load-passthru.mjs'),
...commonArgs,
],
{ encoding: 'utf8' },
);
assert.strictEqual(stderr, '');
assert.match(stdout, /load passthru/);
assert.match(stdout, /resolve passthru/);
assert.match(stdout, /foo/);
assert.strictEqual(status, 0);
}
assert.strictEqual(stderr, '');
assert.match(stdout, /load passthru/);
assert.match(stdout, /resolve passthru/);
assert.match(stdout, /foo/);
assert.strictEqual(code, 0);
});
{ // Verify multiple changes from hooks result in proper output
const { status, stderr, stdout } = spawnSync(
process.execPath,
[
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-resolve-shortcircuit.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-resolve-foo.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-resolve-42.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-load-foo-or-42.mjs'),
...commonArgs,
],
{ encoding: 'utf8' },
);
it('should result in proper output from multiple changes in resolve hooks', async () => {
const { code, stderr, stdout } = await spawnPromisified(
execPath,
[
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-resolve-shortcircuit.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-resolve-foo.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-resolve-42.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-load-foo-or-42.mjs'),
...commonArgs,
],
{ encoding: 'utf8' },
);
assert.strictEqual(stderr, '');
assert.match(stdout, /resolve 42/); // It did go thru resolve-42
assert.match(stdout, /foo/); // LIFO, so resolve-foo won
assert.strictEqual(status, 0);
}
assert.strictEqual(stderr, '');
assert.match(stdout, /resolve 42/); // It did go thru resolve-42
assert.match(stdout, /foo/); // LIFO, so resolve-foo won
assert.strictEqual(code, 0);
});
{ // Verify modifying context within resolve chain is respected
const { status, stderr, stdout } = spawnSync(
process.execPath,
[
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-resolve-shortcircuit.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-resolve-42.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-load-foo-or-42.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-load-receiving-modified-context.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-load-passing-modified-context.mjs'),
...commonArgs,
],
{ encoding: 'utf8' },
);
it('should respect modified context within resolve chain', async () => {
const { code, stderr, stdout } = await spawnPromisified(
execPath,
[
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-resolve-shortcircuit.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-resolve-42.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-load-foo-or-42.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-load-receiving-modified-context.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-load-passing-modified-context.mjs'),
...commonArgs,
],
{ encoding: 'utf8' },
);
assert.strictEqual(stderr, '');
assert.match(stdout, /bar/);
assert.strictEqual(status, 0);
}
assert.strictEqual(stderr, '');
assert.match(stdout, /bar/);
assert.strictEqual(code, 0);
});
{ // Verify multiple changes from hooks result in proper output
const { status, stderr, stdout } = spawnSync(
process.execPath,
[
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-resolve-shortcircuit.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-resolve-42.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-resolve-foo.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-load-foo-or-42.mjs'),
...commonArgs,
],
{ encoding: 'utf8' },
);
it('should result in proper output from multiple changes in resolve hooks', async () => {
const { code, stderr, stdout } = await spawnPromisified(
execPath,
[
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-resolve-shortcircuit.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-resolve-42.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-resolve-foo.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-load-foo-or-42.mjs'),
...commonArgs,
],
{ encoding: 'utf8' },
);
assert.strictEqual(stderr, '');
assert.match(stdout, /resolve foo/); // It did go thru resolve-foo
assert.match(stdout, /42/); // LIFO, so resolve-42 won
assert.strictEqual(status, 0);
}
assert.strictEqual(stderr, '');
assert.match(stdout, /resolve foo/); // It did go thru resolve-foo
assert.match(stdout, /42/); // LIFO, so resolve-42 won
assert.strictEqual(code, 0);
});
{ // Verify multiple calls to next within same loader receive correct "next" fn
const { status, stderr, stdout } = spawnSync(
process.execPath,
[
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-resolve-shortcircuit.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-resolve-foo.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-resolve-multiple-next-calls.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-load-foo-or-42.mjs'),
...commonArgs,
],
{ encoding: 'utf8' },
);
it('should provide the correct "next" fn when multiple calls to next within same loader', async () => {
const { code, stderr, stdout } = await spawnPromisified(
execPath,
[
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-resolve-shortcircuit.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-resolve-foo.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-resolve-multiple-next-calls.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-load-foo-or-42.mjs'),
...commonArgs,
],
{ encoding: 'utf8' },
);
const countFoos = stdout.match(/resolve foo/g)?.length;
const countFoos = stdout.match(/resolve foo/g)?.length;
assert.strictEqual(stderr, '');
assert.strictEqual(countFoos, 2);
assert.strictEqual(status, 0);
}
assert.strictEqual(stderr, '');
assert.strictEqual(countFoos, 2);
assert.strictEqual(code, 0);
});
{ // Verify next<HookName> function's `name` is correct
const { status, stderr, stdout } = spawnSync(
process.execPath,
[
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-resolve-shortcircuit.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-resolve-42.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-load-foo-or-42.mjs'),
...commonArgs,
],
{ encoding: 'utf8' },
);
it('should use the correct `name` for next<HookName>\'s function', async () => {
const { code, stderr, stdout } = await spawnPromisified(
execPath,
[
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-resolve-shortcircuit.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-resolve-42.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-load-foo-or-42.mjs'),
...commonArgs,
],
{ encoding: 'utf8' },
);
assert.strictEqual(stderr, '');
assert.match(stdout, /next<HookName>: nextResolve/);
assert.strictEqual(status, 0);
}
assert.strictEqual(stderr, '');
assert.match(stdout, /next<HookName>: nextResolve/);
assert.strictEqual(code, 0);
});
{ // Verify error thrown for incomplete resolve chain, citing errant loader & hook
const { status, stderr, stdout } = spawnSync(
process.execPath,
[
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-resolve-incomplete.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-resolve-passthru.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-load-foo-or-42.mjs'),
...commonArgs,
],
{ encoding: 'utf8' },
);
it('should throw for incomplete resolve chain, citing errant loader & hook', async () => {
const { code, stderr, stdout } = await spawnPromisified(
execPath,
[
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-resolve-incomplete.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-resolve-passthru.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-load-foo-or-42.mjs'),
...commonArgs,
],
{ encoding: 'utf8' },
);
assert.match(stdout, /resolve passthru/);
assert.match(stderr, /ERR_LOADER_CHAIN_INCOMPLETE/);
assert.match(stderr, /loader-resolve-incomplete\.mjs/);
assert.match(stderr, /'resolve'/);
assert.strictEqual(code, 1);
});
assert.match(stdout, /resolve passthru/);
assert.match(stderr, /ERR_LOADER_CHAIN_INCOMPLETE/);
assert.match(stderr, /loader-resolve-incomplete\.mjs/);
assert.match(stderr, /'resolve'/);
assert.strictEqual(status, 1);
}
it('should NOT throw when nested resolve hook signaled a short circuit', async () => {
const { code, stderr, stdout } = await spawnPromisified(
execPath,
[
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-resolve-shortcircuit.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-resolve-next-modified.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-load-foo-or-42.mjs'),
...commonArgs,
],
{ encoding: 'utf8' },
);
{ // Verify error NOT thrown when nested resolve hook signaled a short circuit
const { status, stderr, stdout } = spawnSync(
process.execPath,
[
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-resolve-shortcircuit.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-resolve-next-modified.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-load-foo-or-42.mjs'),
...commonArgs,
],
{ encoding: 'utf8' },
);
assert.strictEqual(stderr, '');
assert.strictEqual(stdout.trim(), 'foo');
assert.strictEqual(code, 0);
});
assert.strictEqual(stderr, '');
assert.strictEqual(stdout.trim(), 'foo');
assert.strictEqual(status, 0);
}
it('should NOT throw when nested load hook signaled a short circuit', async () => {
const { code, stderr, stdout } = await spawnPromisified(
execPath,
[
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-resolve-shortcircuit.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-resolve-42.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-load-foo-or-42.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-load-next-modified.mjs'),
...commonArgs,
],
{ encoding: 'utf8' },
);
{ // Verify error NOT thrown when nested load hook signaled a short circuit
const { status, stderr, stdout } = spawnSync(
process.execPath,
[
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-resolve-shortcircuit.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-resolve-42.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-load-foo-or-42.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-load-next-modified.mjs'),
...commonArgs,
],
{ encoding: 'utf8' },
);
assert.strictEqual(stderr, '');
assert.match(stdout, /421/);
assert.strictEqual(code, 0);
});
assert.strictEqual(stderr, '');
assert.match(stdout, /421/);
assert.strictEqual(status, 0);
}
it('should throw when the resolve chain is broken', async () => {
const { code, stderr, stdout } = await spawnPromisified(
execPath,
[
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-resolve-passthru.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-resolve-incomplete.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-load-foo-or-42.mjs'),
...commonArgs,
],
{ encoding: 'utf8' },
);
{ // Verify resolve chain does break and throws appropriately
const { status, stderr, stdout } = spawnSync(
process.execPath,
[
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-resolve-passthru.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-resolve-incomplete.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-load-foo-or-42.mjs'),
...commonArgs,
],
{ encoding: 'utf8' },
);
assert.doesNotMatch(stdout, /resolve passthru/);
assert.match(stderr, /ERR_LOADER_CHAIN_INCOMPLETE/);
assert.match(stderr, /loader-resolve-incomplete\.mjs/);
assert.match(stderr, /'resolve'/);
assert.strictEqual(code, 1);
});
assert.doesNotMatch(stdout, /resolve passthru/);
assert.match(stderr, /ERR_LOADER_CHAIN_INCOMPLETE/);
assert.match(stderr, /loader-resolve-incomplete\.mjs/);
assert.match(stderr, /'resolve'/);
assert.strictEqual(status, 1);
}
it('should throw for incomplete load chain, citing errant loader & hook', async () => {
const { code, stderr, stdout } = await spawnPromisified(
execPath,
[
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-resolve-passthru.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-load-incomplete.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-load-passthru.mjs'),
...commonArgs,
],
{ encoding: 'utf8' },
);
{ // Verify error thrown for incomplete load chain, citing errant loader & hook
const { status, stderr, stdout } = spawnSync(
process.execPath,
[
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-resolve-passthru.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-load-incomplete.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-load-passthru.mjs'),
...commonArgs,
],
{ encoding: 'utf8' },
);
assert.match(stdout, /load passthru/);
assert.match(stderr, /ERR_LOADER_CHAIN_INCOMPLETE/);
assert.match(stderr, /loader-load-incomplete\.mjs/);
assert.match(stderr, /'load'/);
assert.strictEqual(code, 1);
});
assert.match(stdout, /load passthru/);
assert.match(stderr, /ERR_LOADER_CHAIN_INCOMPLETE/);
assert.match(stderr, /loader-load-incomplete\.mjs/);
assert.match(stderr, /'load'/);
assert.strictEqual(status, 1);
}
it('should throw when the load chain is broken', async () => {
const { code, stderr, stdout } = await spawnPromisified(
execPath,
[
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-resolve-passthru.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-load-passthru.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-load-incomplete.mjs'),
...commonArgs,
],
{ encoding: 'utf8' },
);
{ // Verify load chain does break and throws appropriately
const { status, stderr, stdout } = spawnSync(
process.execPath,
[
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-resolve-passthru.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-load-passthru.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-load-incomplete.mjs'),
...commonArgs,
],
{ encoding: 'utf8' },
);
assert.doesNotMatch(stdout, /load passthru/);
assert.match(stderr, /ERR_LOADER_CHAIN_INCOMPLETE/);
assert.match(stderr, /loader-load-incomplete\.mjs/);
assert.match(stderr, /'load'/);
assert.strictEqual(code, 1);
});
assert.doesNotMatch(stdout, /load passthru/);
assert.match(stderr, /ERR_LOADER_CHAIN_INCOMPLETE/);
assert.match(stderr, /loader-load-incomplete\.mjs/);
assert.match(stderr, /'load'/);
assert.strictEqual(status, 1);
}
it('should throw when invalid `specifier` argument passed to `nextResolve`', async () => {
const { code, stderr } = await spawnPromisified(
execPath,
[
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-resolve-passthru.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-resolve-bad-next-specifier.mjs'),
...commonArgs,
],
{ encoding: 'utf8' },
);
{ // Verify error thrown when invalid `specifier` argument passed to `nextResolve`
const { status, stderr } = spawnSync(
process.execPath,
[
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-resolve-passthru.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-resolve-bad-next-specifier.mjs'),
...commonArgs,
],
{ encoding: 'utf8' },
);
assert.strictEqual(code, 1);
assert.match(stderr, /ERR_INVALID_ARG_TYPE/);
assert.match(stderr, /loader-resolve-bad-next-specifier\.mjs/);
assert.match(stderr, /'resolve' hook's nextResolve\(\) specifier/);
});
assert.strictEqual(status, 1);
assert.match(stderr, /ERR_INVALID_ARG_TYPE/);
assert.match(stderr, /loader-resolve-bad-next-specifier\.mjs/);
assert.match(stderr, /'resolve' hook's nextResolve\(\) specifier/);
}
it('should throw when resolve hook is invalid', async () => {
const { code, stderr } = await spawnPromisified(
execPath,
[
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-resolve-passthru.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-resolve-null-return.mjs'),
...commonArgs,
],
{ encoding: 'utf8' },
);
{ // Verify error thrown when resolve hook is invalid
const { status, stderr } = spawnSync(
process.execPath,
[
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-resolve-passthru.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-resolve-null-return.mjs'),
...commonArgs,
],
{ encoding: 'utf8' },
);
assert.strictEqual(code, 1);
assert.match(stderr, /ERR_INVALID_RETURN_VALUE/);
assert.match(stderr, /loader-resolve-null-return\.mjs/);
assert.match(stderr, /'resolve' hook's nextResolve\(\)/);
assert.match(stderr, /an object/);
assert.match(stderr, /got null/);
});
assert.strictEqual(status, 1);
assert.match(stderr, /ERR_INVALID_RETURN_VALUE/);
assert.match(stderr, /loader-resolve-null-return\.mjs/);
assert.match(stderr, /'resolve' hook's nextResolve\(\)/);
assert.match(stderr, /an object/);
assert.match(stderr, /got null/);
}
it('should throw when invalid `context` argument passed to `nextResolve`', async () => {
const { code, stderr } = await spawnPromisified(
execPath,
[
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-resolve-passthru.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-resolve-bad-next-context.mjs'),
...commonArgs,
],
{ encoding: 'utf8' },
);
{ // Verify error thrown when invalid `context` argument passed to `nextResolve`
const { status, stderr } = spawnSync(
process.execPath,
[
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-resolve-passthru.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-resolve-bad-next-context.mjs'),
...commonArgs,
],
{ encoding: 'utf8' },
);
assert.match(stderr, /ERR_INVALID_ARG_TYPE/);
assert.match(stderr, /loader-resolve-bad-next-context\.mjs/);
assert.match(stderr, /'resolve' hook's nextResolve\(\) context/);
assert.strictEqual(code, 1);
});
assert.match(stderr, /ERR_INVALID_ARG_TYPE/);
assert.match(stderr, /loader-resolve-bad-next-context\.mjs/);
assert.match(stderr, /'resolve' hook's nextResolve\(\) context/);
assert.strictEqual(status, 1);
}
it('should throw when load hook is invalid', async () => {
const { code, stderr } = await spawnPromisified(
execPath,
[
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-load-passthru.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-load-null-return.mjs'),
...commonArgs,
],
{ encoding: 'utf8' },
);
{ // Verify error thrown when load hook is invalid
const { status, stderr } = spawnSync(
process.execPath,
[
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-load-passthru.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-load-null-return.mjs'),
...commonArgs,
],
{ encoding: 'utf8' },
);
assert.strictEqual(code, 1);
assert.match(stderr, /ERR_INVALID_RETURN_VALUE/);
assert.match(stderr, /loader-load-null-return\.mjs/);
assert.match(stderr, /'load' hook's nextLoad\(\)/);
assert.match(stderr, /an object/);
assert.match(stderr, /got null/);
});
assert.strictEqual(status, 1);
assert.match(stderr, /ERR_INVALID_RETURN_VALUE/);
assert.match(stderr, /loader-load-null-return\.mjs/);
assert.match(stderr, /'load' hook's nextLoad\(\)/);
assert.match(stderr, /an object/);
assert.match(stderr, /got null/);
}
it('should throw when invalid `url` argument passed to `nextLoad`', async () => {
const { code, stderr } = await spawnPromisified(
execPath,
[
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-load-passthru.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-load-bad-next-url.mjs'),
...commonArgs,
],
{ encoding: 'utf8' },
);
{ // Verify error thrown when invalid `url` argument passed to `nextLoad`
const { status, stderr } = spawnSync(
process.execPath,
[
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-load-passthru.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-load-bad-next-url.mjs'),
...commonArgs,
],
{ encoding: 'utf8' },
);
assert.match(stderr, /ERR_INVALID_ARG_TYPE/);
assert.match(stderr, /loader-load-bad-next-url\.mjs/);
assert.match(stderr, /'load' hook's nextLoad\(\) url/);
assert.strictEqual(code, 1);
});
assert.match(stderr, /ERR_INVALID_ARG_TYPE/);
assert.match(stderr, /loader-load-bad-next-url\.mjs/);
assert.match(stderr, /'load' hook's nextLoad\(\) url/);
assert.strictEqual(status, 1);
}
it('should throw when invalid `url` argument passed to `nextLoad`', async () => {
const { code, stderr } = await spawnPromisified(
execPath,
[
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-load-passthru.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-load-impersonating-next-url.mjs'),
...commonArgs,
],
{ encoding: 'utf8' },
);
{ // Verify error thrown when invalid `url` argument passed to `nextLoad`
const { status, stderr } = spawnSync(
process.execPath,
[
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-load-passthru.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-load-impersonating-next-url.mjs'),
...commonArgs,
],
{ encoding: 'utf8' },
);
assert.match(stderr, /ERR_INVALID_ARG_VALUE/);
assert.match(stderr, /loader-load-impersonating-next-url\.mjs/);
assert.match(stderr, /'load' hook's nextLoad\(\) url/);
assert.strictEqual(code, 1);
});
assert.match(stderr, /ERR_INVALID_ARG_VALUE/);
assert.match(stderr, /loader-load-impersonating-next-url\.mjs/);
assert.match(stderr, /'load' hook's nextLoad\(\) url/);
assert.strictEqual(status, 1);
}
{ // Verify error thrown when invalid `context` argument passed to `nextLoad`
const { status, stderr } = spawnSync(
process.execPath,
[
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-load-passthru.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-load-bad-next-context.mjs'),
...commonArgs,
],
{ encoding: 'utf8' },
);
assert.match(stderr, /ERR_INVALID_ARG_TYPE/);
assert.match(stderr, /loader-load-bad-next-context\.mjs/);
assert.match(stderr, /'load' hook's nextLoad\(\) context/);
assert.strictEqual(status, 1);
}
it('should throw when invalid `context` argument passed to `nextLoad`', async () => {
const { code, stderr } = await spawnPromisified(
execPath,
[
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-load-passthru.mjs'),
'--loader',
fixtures.fileURL('es-module-loaders', 'loader-load-bad-next-context.mjs'),
...commonArgs,
],
{ encoding: 'utf8' },
);
assert.match(stderr, /ERR_INVALID_ARG_TYPE/);
assert.match(stderr, /loader-load-bad-next-context\.mjs/);
assert.match(stderr, /'load' hook's nextLoad\(\) context/);
assert.strictEqual(code, 1);
});
});

View file

@ -1,10 +1,11 @@
import { mustCall } from '../common/index.mjs';
import { spawnPromisified } from '../common/index.mjs';
import fixtures from '../common/fixtures.js';
import { strictEqual } from 'node:assert';
import { spawn } from 'node:child_process';
import assert from 'node:assert';
import http from 'node:http';
import path from 'node:path';
import { execPath } from 'node:process';
import { promisify } from 'node:util';
import { describe, it } from 'node:test';
const files = {
@ -40,33 +41,31 @@ const {
port,
} = server.address();
{ // Verify nested HTTP imports work
const child = spawn( // ! `spawn` MUST be used (vs `spawnSync`) to avoid blocking the event loop
process.execPath,
[
'--no-warnings',
'--loader',
fixtures.fileURL('es-module-loaders', 'http-loader.mjs'),
'--input-type=module',
'--eval',
`import * as main from 'http://${host}:${port}/main.mjs'; console.log(main)`,
]
);
/**
* ! If more cases are added to this test, they cannot (yet) be concurrent because there is no
* ! `afterAll` teardown in which to close the server.
*/
let stderr = '';
let stdout = '';
describe('ESM: http import via loader', { concurrency: false }, () => {
it('should work', async () => {
// ! MUST NOT use spawnSync to avoid blocking the event loop
const { code, signal, stderr, stdout } = await spawnPromisified(
execPath,
[
'--no-warnings',
'--loader',
fixtures.fileURL('es-module-loaders', 'http-loader.mjs'),
'--input-type=module',
'--eval',
`import * as main from 'http://${host}:${port}/main.mjs'; console.log(main)`,
]
);
child.stderr.setEncoding('utf8');
child.stderr.on('data', (data) => stderr += data);
child.stdout.setEncoding('utf8');
child.stdout.on('data', (data) => stdout += data);
assert.strictEqual(stderr, '');
assert.strictEqual(stdout, '[Module: null prototype] { sum: [Function: sum] }\n');
assert.strictEqual(code, 0);
assert.strictEqual(signal, null);
child.on('close', mustCall((code, signal) => {
strictEqual(stderr, '');
strictEqual(stdout, '[Module: null prototype] { sum: [Function: sum] }\n');
strictEqual(code, 0);
strictEqual(signal, null);
server.close();
}));
}
server.close(); // ! This MUST come after the final test, but inside the async `it` function
});
});

View file

@ -1,27 +1,24 @@
import { mustCall } from '../common/index.mjs';
import { path } from '../common/fixtures.mjs';
import { match, ok, notStrictEqual } from 'assert';
import { spawn } from 'child_process';
import { execPath } from 'process';
import { spawnPromisified } from '../common/index.mjs';
import * as fixtures from '../common/fixtures.mjs';
import assert from 'node:assert';
import { execPath } from 'node:process';
import { describe, it } from 'node:test';
const child = spawn(execPath, [
'--experimental-loader',
'i-dont-exist',
path('print-error-message.js'),
]);
let stderr = '';
child.stderr.setEncoding('utf8');
child.stderr.on('data', (data) => {
stderr += data;
describe('ESM: nonexistent loader', () => {
it('should throw', async () => {
const { code, stderr } = await spawnPromisified(execPath, [
'--experimental-loader',
'i-dont-exist',
fixtures.path('print-error-message.js'),
]);
assert.notStrictEqual(code, 0);
// Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'i-dont-exist' imported from
assert.match(stderr, /ERR_MODULE_NOT_FOUND/);
assert.match(stderr, /'i-dont-exist'/);
assert.ok(!stderr.includes('Bad command or file name'));
});
});
child.on('close', mustCall((code, _signal) => {
notStrictEqual(code, 0);
// Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'i-dont-exist'
// imported from
match(stderr, /ERR_MODULE_NOT_FOUND/);
match(stderr, /'i-dont-exist'/);
ok(!stderr.includes('Bad command or file name'));
}));

View file

@ -1,30 +1,28 @@
import { mustCall } from '../common/index.mjs';
import { spawnPromisified } from '../common/index.mjs';
import { fileURL, path } from '../common/fixtures.mjs';
import { match, notStrictEqual } from 'assert';
import { spawn } from 'child_process';
import { execPath } from 'process';
import { match, notStrictEqual } from 'node:assert';
import { execPath } from 'node:process';
import { describe, it } from 'node:test';
const child = spawn(execPath, [
'--no-warnings',
'--throw-deprecation',
'--experimental-loader',
fileURL('es-module-loaders', 'hooks-obsolete.mjs').href,
path('print-error-message.js'),
]);
let stderr = '';
child.stderr.setEncoding('utf8');
child.stderr.on('data', (data) => {
stderr += data;
describe('ESM: deprecation warnings for obsolete hooks', { concurrency: true }, () => {
it(async () => {
const { code, stderr } = await spawnPromisified(execPath, [
'--no-warnings',
'--throw-deprecation',
'--experimental-loader',
fileURL('es-module-loaders', 'hooks-obsolete.mjs').href,
path('print-error-message.js'),
]);
// DeprecationWarning: Obsolete loader hook(s) supplied and will be ignored:
// dynamicInstantiate, getFormat, getSource, transformSource
match(stderr, /DeprecationWarning:/);
match(stderr, /dynamicInstantiate/);
match(stderr, /getFormat/);
match(stderr, /getSource/);
match(stderr, /transformSource/);
notStrictEqual(code, 0);
});
});
child.on('close', mustCall((code, _signal) => {
notStrictEqual(code, 0);
// DeprecationWarning: Obsolete loader hook(s) supplied and will be ignored:
// dynamicInstantiate, getFormat, getSource, transformSource
match(stderr, /DeprecationWarning:/);
match(stderr, /dynamicInstantiate/);
match(stderr, /getFormat/);
match(stderr, /getSource/);
match(stderr, /transformSource/);
}));

View file

@ -1,65 +1,43 @@
import { mustCall } from '../common/index.mjs';
import { spawnPromisified } from '../common/index.mjs';
import { fileURL, path } from '../common/fixtures.mjs';
import { match, ok, notStrictEqual, strictEqual } from 'assert';
import { spawn } from 'child_process';
import { execPath } from 'process';
import { execPath } from 'node:process';
import { describe, it } from 'node:test';
{
const child = spawn(execPath, [
'--experimental-loader',
fileURL('es-module-loaders', 'thenable-load-hook.mjs').href,
path('es-modules', 'test-esm-ok.mjs'),
]);
let stderr = '';
child.stderr.setEncoding('utf8');
child.stderr.on('data', (data) => {
stderr += data;
});
child.on('close', mustCall((code, _signal) => {
describe('ESM: thenable loader hooks', { concurrency: true }, () => {
it('should behave as a normal promise resolution', async () => {
const { code, stderr } = await spawnPromisified(execPath, [
'--experimental-loader',
fileURL('es-module-loaders', 'thenable-load-hook.mjs').href,
path('es-modules', 'test-esm-ok.mjs'),
]);
strictEqual(code, 0);
ok(!stderr.includes('must not call'));
}));
}
{
const child = spawn(execPath, [
'--experimental-loader',
fileURL('es-module-loaders', 'thenable-load-hook-rejected.mjs').href,
path('es-modules', 'test-esm-ok.mjs'),
]);
let stderr = '';
child.stderr.setEncoding('utf8');
child.stderr.on('data', (data) => {
stderr += data;
});
child.on('close', mustCall((code, _signal) => {
notStrictEqual(code, 0);
it('should crash the node process rejection with an error', async () => {
const { code, stderr } = await spawnPromisified(execPath, [
'--experimental-loader',
fileURL('es-module-loaders', 'thenable-load-hook-rejected.mjs').href,
path('es-modules', 'test-esm-ok.mjs'),
]);
notStrictEqual(code, 0);
match(stderr, /\sError: must crash the process\r?\n/);
ok(!stderr.includes('must not call'));
}));
}
{
const child = spawn(execPath, [
'--experimental-loader',
fileURL('es-module-loaders', 'thenable-load-hook-rejected-no-arguments.mjs').href,
path('es-modules', 'test-esm-ok.mjs'),
]);
let stderr = '';
child.stderr.setEncoding('utf8');
child.stderr.on('data', (data) => {
stderr += data;
});
child.on('close', mustCall((code, _signal) => {
it('should just reject without an error (but NOT crash the node process)', async () => {
const { code, stderr } = await spawnPromisified(execPath, [
'--experimental-loader',
fileURL('es-module-loaders', 'thenable-load-hook-rejected-no-arguments.mjs').href,
path('es-modules', 'test-esm-ok.mjs'),
]);
notStrictEqual(code, 0);
match(stderr, /\sundefined\r?\n/);
ok(!stderr.includes('must not call'));
}));
}
});
});

View file

@ -1,24 +1,20 @@
import { mustCall } from '../common/index.mjs';
import { spawnPromisified } from '../common/index.mjs';
import { fileURL, path } from '../common/fixtures.mjs';
import { match, ok, notStrictEqual } from 'assert';
import { spawn } from 'child_process';
import { execPath } from 'process';
import { match, ok, notStrictEqual } from 'node:assert';
import { execPath } from 'node:process';
import { describe, it } from 'node:test';
const child = spawn(execPath, [
'--experimental-loader',
fileURL('es-module-loaders', 'syntax-error.mjs').href,
path('print-error-message.js'),
]);
let stderr = '';
child.stderr.setEncoding('utf8');
child.stderr.on('data', (data) => {
stderr += data;
describe('ESM: loader with syntax error', { concurrency: true }, () => {
it('should crash the node process', async () => {
const { code, stderr } = await spawnPromisified(execPath, [
'--experimental-loader',
fileURL('es-module-loaders', 'syntax-error.mjs').href,
path('print-error-message.js'),
]);
match(stderr, /SyntaxError:/);
ok(!stderr.includes('Bad command or file name'));
notStrictEqual(code, 0);
});
});
child.on('close', mustCall((code, _signal) => {
notStrictEqual(code, 0);
match(stderr, /SyntaxError:/);
ok(!stderr.includes('Bad command or file name'));
}));

View file

@ -1,35 +1,34 @@
import { mustCall } from '../common/index.mjs';
import { spawnPromisified } from '../common/index.mjs';
import { fixturesDir } from '../common/fixtures.mjs';
import { match, notStrictEqual } from 'assert';
import { spawn } from 'child_process';
import { execPath } from 'process';
import { match, notStrictEqual } from 'node:assert';
import { execPath } from 'node:process';
import { describe, it } from 'node:test';
[
{
input: 'import "./print-error-message"',
// Did you mean to import ../print-error-message.js?
expected: / \.\.\/print-error-message\.js\?/,
},
{
input: 'import obj from "some_module/obj"',
expected: / some_module\/obj\.js\?/,
},
].forEach(({ input, expected }) => {
const child = spawn(execPath, [
'--input-type=module',
'--eval',
input,
], {
cwd: fixturesDir,
});
let stderr = '';
child.stderr.setEncoding('utf8');
child.stderr.on('data', (data) => {
stderr += data;
});
child.on('close', mustCall((code, _signal) => {
notStrictEqual(code, 0);
describe('ESM: module not found hint', { concurrency: true }, () => {
for (
const { input, expected }
of [
{
input: 'import "./print-error-message"',
// Did you mean to import ../print-error-message.js?
expected: / \.\.\/print-error-message\.js\?/,
},
{
input: 'import obj from "some_module/obj"',
expected: / some_module\/obj\.js\?/,
},
]
) it('should cite a variant form', async () => {
const { code, stderr } = await spawnPromisified(execPath, [
'--input-type=module',
'--eval',
input,
], {
cwd: fixturesDir,
});
match(stderr, expected);
}));
notStrictEqual(code, 0);
});
});

View file

@ -1,23 +1,20 @@
import { mustCall } from '../common/index.mjs';
import { spawnPromisified } from '../common/index.mjs';
import { fileURL } from '../common/fixtures.mjs';
import { match, strictEqual } from 'assert';
import { spawn } from 'child_process';
import { execPath } from 'process';
import { match, strictEqual } from 'node:assert';
import { execPath } from 'node:process';
import { describe, it } from 'node:test';
// Verify non-js extensions fail for ESM
const child = spawn(execPath, [
'--input-type=module',
'--eval',
`import ${JSON.stringify(fileURL('es-modules', 'file.unknown'))}`,
]);
let stderr = '';
child.stderr.setEncoding('utf8');
child.stderr.on('data', (data) => {
stderr += data;
describe('ESM: non-js extensions fail', { concurrency: true }, () => {
it(async () => {
const { code, stderr, signal } = await spawnPromisified(execPath, [
'--input-type=module',
'--eval',
`import ${JSON.stringify(fileURL('es-modules', 'file.unknown'))}`,
]);
match(stderr, /ERR_UNKNOWN_FILE_EXTENSION/);
strictEqual(code, 1);
strictEqual(signal, null);
});
});
child.on('close', mustCall((code, signal) => {
strictEqual(code, 1);
strictEqual(signal, null);
match(stderr, /ERR_UNKNOWN_FILE_EXTENSION/);
}));

View file

@ -1,25 +1,19 @@
import '../common/index.mjs';
import { spawnPromisified } from '../common/index.mjs';
import { path } from '../common/fixtures.mjs';
import { strictEqual, ok } from 'assert';
import { spawn } from 'child_process';
import { strictEqual } from 'node:assert';
import { execPath } from 'node:process';
import { describe, it } from 'node:test';
const child = spawn(process.execPath, [
'--experimental-import-meta-resolve',
path('/es-modules/import-resolve-exports.mjs'),
]);
let stderr = '';
child.stderr.setEncoding('utf8');
child.stderr.on('data', (data) => {
stderr += data;
});
child.on('close', (code, signal) => {
strictEqual(code, 0);
strictEqual(signal, null);
ok(!stderr.toString().includes(
'ExperimentalWarning: The ESM module loader is experimental'
));
ok(!stderr.toString().includes(
'ExperimentalWarning: Conditional exports'
));
describe('ESM: experiemental warning for import.meta.resolve', { concurrency: true }, () => {
it('should not warn when caught', async () => {
const { code, signal, stderr } = await spawnPromisified(execPath, [
'--experimental-import-meta-resolve',
path('es-modules/import-resolve-exports.mjs'),
]);
strictEqual(stderr, '');
strictEqual(code, 0);
strictEqual(signal, null);
});
});

View file

@ -39,15 +39,15 @@ function doTest(flags, done) {
// dictates that it'll resolve relative imports in the main file relative to
// the symlink, and not relative to the symlink target; the file structure set
// up above requires this to not crash when loading ./submodule_link.js
spawn(process.execPath,
flags.concat([
'--preserve-symlinks',
'--preserve-symlinks-main', entry_link_absolute_path,
]),
{ stdio: 'inherit' }).on('exit', (code) => {
assert.strictEqual(code, 0);
done();
});
spawn(process.execPath, [
'--preserve-symlinks',
'--preserve-symlinks-main',
entry_link_absolute_path,
], { stdio: 'inherit' })
.on('exit', (code) => {
assert.strictEqual(code, 0);
done();
});
}
// First test the commonjs module loader

View file

@ -1,19 +1,28 @@
'use strict';
const { mustCall } = require('../common');
const assert = require('assert');
const fixtures = require('../common/fixtures');
const { spawn } = require('child_process');
const assert = require('node:assert');
const { spawn } = require('node:child_process');
const { execPath } = require('node:process');
const { describe, it } = require('node:test');
const child = spawn(process.execPath, [
'--interactive',
], {
cwd: fixtures.path('es-modules', 'pkgimports'),
describe('ESM: REPL runs', { concurrency: true }, () => {
it((context, done) => {
const child = spawn(execPath, [
'--interactive',
], {
cwd: fixtures.path('es-modules', 'pkgimports'),
});
child.stdin.end(
'try{require("#test");await import("#test")}catch{process.exit(-1)}'
);
child.on('exit', mustCall((code) => {
assert.strictEqual(code, 0);
done();
}));
});
});
child.stdin.end(
'try{require("#test");await import("#test")}catch{process.exit(-1)}'
);
child.on('exit', mustCall((code) => {
assert.strictEqual(code, 0);
}));

View file

@ -1,62 +1,79 @@
// Flags: --experimental-specifier-resolution=node
import { mustNotCall } from '../common/index.mjs';
import assert from 'assert';
import path from 'path';
import { spawn } from 'child_process';
import { fileURLToPath } from 'url';
import { spawnPromisified } from '../common/index.mjs';
import * as fixtures from '../common/fixtures.mjs';
import { match, strictEqual } from 'node:assert';
import { execPath } from 'node:process';
import { describe, it } from 'node:test';
// commonJS index.js
import commonjs from '../fixtures/es-module-specifiers/package-type-commonjs';
// esm index.js
import module from '../fixtures/es-module-specifiers/package-type-module';
// Notice the trailing slash
import success, { explicit, implicit, implicitModule, getImplicitCommonjs }
from '../fixtures/es-module-specifiers/';
assert.strictEqual(commonjs, 'commonjs');
assert.strictEqual(module, 'module');
assert.strictEqual(success, 'success');
assert.strictEqual(explicit, 'esm');
assert.strictEqual(implicit, 'cjs');
assert.strictEqual(implicitModule, 'cjs');
describe('ESM: specifier-resolution=node', { concurrency: true }, () => {
it(async () => {
const { code, stderr, stdout } = await spawnPromisified(execPath, [
'--no-warnings',
'--experimental-specifier-resolution=node',
'--input-type=module',
'--eval',
[
'import { strictEqual } from "node:assert";',
// commonJS index.js
`import commonjs from ${JSON.stringify(fixtures.fileURL('es-module-specifiers/package-type-commonjs'))};`,
// esm index.js
`import module from ${JSON.stringify(fixtures.fileURL('es-module-specifiers/package-type-module'))};`,
// Notice the trailing slash
`import success, { explicit, implicit, implicitModule } from ${JSON.stringify(fixtures.fileURL('es-module-specifiers/'))};`,
'strictEqual(commonjs, "commonjs");',
'strictEqual(module, "module");',
'strictEqual(success, "success");',
'strictEqual(explicit, "esm");',
'strictEqual(implicit, "cjs");',
'strictEqual(implicitModule, "cjs");',
].join('\n'),
]);
async function main() {
try {
await import('../fixtures/es-module-specifiers/do-not-exist.js');
} catch (e) {
// Files that do not exist should throw
assert.strictEqual(e.name, 'Error');
}
try {
await getImplicitCommonjs();
} catch (e) {
// Legacy loader cannot resolve .mjs automatically from main
assert.strictEqual(e.name, 'Error');
}
}
strictEqual(stderr, '');
strictEqual(stdout, '');
strictEqual(code, 0);
});
main().catch(mustNotCall);
it('should throw when the file doesn\'t exist', async () => {
const { code, stderr, stdout } = await spawnPromisified(execPath, [
'--no-warnings',
fixtures.path('es-module-specifiers/do-not-exist.js'),
]);
// Test path from command line arguments
[
'package-type-commonjs',
'package-type-module',
'/',
'/index',
].forEach((item) => {
const modulePath = path.join(
fileURLToPath(import.meta.url),
'../../fixtures/es-module-specifiers',
item,
);
[
'--experimental-specifier-resolution',
'--es-module-specifier-resolution',
].forEach((option) => {
spawn(process.execPath,
[`${option}=node`, modulePath],
{ stdio: 'inherit' }).on('exit', (code) => {
assert.strictEqual(code, 0);
});
match(stderr, /Cannot find module/);
strictEqual(stdout, '');
strictEqual(code, 1);
});
it('should throw when the omitted file extension is .mjs (legacy loader doesn\'t support it)', async () => {
const { code, stderr, stdout } = await spawnPromisified(execPath, [
'--no-warnings',
'--experimental-specifier-resolution=node',
'--input-type=module',
'--eval',
`import whatever from ${JSON.stringify(fixtures.fileURL('es-module-specifiers/implicit-main-type-commonjs'))};`,
]);
match(stderr, /ERR_MODULE_NOT_FOUND/);
strictEqual(stdout, '');
strictEqual(code, 1);
});
for (
const item of [
'package-type-commonjs',
'package-type-module',
'/',
'/index',
]
) it('should ', async () => {
const { code } = await spawnPromisified(execPath, [
'--no-warnings',
'--experimental-specifier-resolution=node',
'--es-module-specifier-resolution=node',
fixtures.path('es-module-specifiers', item),
]);
strictEqual(code, 0);
});
});

View file

@ -1,19 +1,16 @@
import { mustCall } from '../common/index.mjs';
import { spawnPromisified } from '../common/index.mjs';
import { path } from '../common/fixtures.mjs';
import { match, notStrictEqual } from 'assert';
import { spawn } from 'child_process';
import { execPath } from 'process';
import { match, notStrictEqual } from 'node:assert';
import { execPath } from 'node:process';
import { describe, it } from 'node:test';
const child = spawn(execPath, [
path('es-module-loaders', 'syntax-error.mjs'),
]);
let stderr = '';
child.stderr.setEncoding('utf8');
child.stderr.on('data', (data) => {
stderr += data;
describe('ESM: importing a module with syntax error(s)', { concurrency: true }, () => {
it('should throw', async () => {
const { code, stderr } = await spawnPromisified(execPath, [
path('es-module-loaders', 'syntax-error.mjs'),
]);
match(stderr, /SyntaxError:/);
notStrictEqual(code, 0);
});
});
child.on('close', mustCall((code, _signal) => {
notStrictEqual(code, 0);
match(stderr, /SyntaxError:/);
}));

View file

@ -1,7 +1,9 @@
import '../common/index.mjs';
import assert from 'assert';
import child_process from 'child_process';
import { spawnPromisified } from '../common/index.mjs';
import fixtures from '../common/fixtures.js';
import assert from 'node:assert';
import { execPath } from 'node:process';
import { describe, it } from 'node:test';
const commonArgs = [
'--no-warnings',
@ -9,102 +11,117 @@ const commonArgs = [
'--eval',
];
{
// Unresolved TLA promise, --eval
const { status, stdout, stderr } = child_process.spawnSync(
process.execPath,
[...commonArgs, 'await new Promise(() => {})'],
{ encoding: 'utf8' });
assert.deepStrictEqual([status, stdout, stderr], [13, '', '']);
}
describe('ESM: unsettled and rejected promises', { concurrency: true }, () => {
it('should exit for an unsettled TLA promise via --eval', async () => {
const { code, stderr, stdout } = await spawnPromisified(execPath, [
...commonArgs,
'await new Promise(() => {})',
]);
{
// Rejected TLA promise, --eval
const { status, stdout, stderr } = child_process.spawnSync(
process.execPath,
[...commonArgs, 'await Promise.reject(new Error("Xyz"))'],
{ encoding: 'utf8' });
assert.deepStrictEqual([status, stdout], [1, '']);
assert.match(stderr, /Error: Xyz/);
}
assert.strictEqual(stderr, '');
assert.strictEqual(stdout, '');
assert.strictEqual(code, 13);
});
{
// Unresolved TLA promise with explicit exit code, --eval
const { status, stdout, stderr } = child_process.spawnSync(
process.execPath,
[
it('should throw for a rejected TLA promise via --eval', async () => {
// Rejected TLA promise, --eval
const { code, stderr, stdout } = await spawnPromisified(execPath, [
...commonArgs,
'await Promise.reject(new Error("Xyz"))',
]);
assert.match(stderr, /Error: Xyz/);
assert.strictEqual(stdout, '');
assert.strictEqual(code, 1);
});
it('should exit for an unsettled TLA promise and respect explicit exit code via --eval', async () => {
// Rejected TLA promise, --eval
const { code, stderr, stdout } = await spawnPromisified(execPath, [
...commonArgs,
'process.exitCode = 42;await new Promise(() => {})',
],
{ encoding: 'utf8' });
assert.deepStrictEqual([status, stdout, stderr], [42, '', '']);
}
]);
{
// Rejected TLA promise with explicit exit code, --eval
const { status, stdout, stderr } = child_process.spawnSync(
process.execPath,
[
assert.strictEqual(stderr, '');
assert.strictEqual(stdout, '');
assert.strictEqual(code, 42);
});
it('should throw for a rejected TLA promise and ignore explicit exit code via --eval', async () => {
// Rejected TLA promise, --eval
const { code, stderr, stdout } = await spawnPromisified(execPath, [
...commonArgs,
'process.exitCode = 42;await Promise.reject(new Error("Xyz"))',
],
{ encoding: 'utf8' });
assert.deepStrictEqual([status, stdout], [1, '']);
assert.match(stderr, /Error: Xyz/);
}
]);
{
// Unresolved TLA promise, module file
const { status, stdout, stderr } = child_process.spawnSync(
process.execPath,
['--no-warnings', fixtures.path('es-modules/tla/unresolved.mjs')],
{ encoding: 'utf8' });
assert.deepStrictEqual([status, stdout, stderr], [13, '', '']);
}
assert.match(stderr, /Error: Xyz/);
assert.strictEqual(stdout, '');
assert.strictEqual(code, 1);
});
{
// Rejected TLA promise, module file
const { status, stdout, stderr } = child_process.spawnSync(
process.execPath,
['--no-warnings', fixtures.path('es-modules/tla/rejected.mjs')],
{ encoding: 'utf8' });
assert.deepStrictEqual([status, stdout], [1, '']);
assert.match(stderr, /Error: Xyz/);
}
it('should exit for an unsettled TLA promise via stdin', async () => {
const { code, stderr, stdout } = await spawnPromisified(execPath, [
'--no-warnings',
fixtures.path('es-modules/tla/unresolved.mjs'),
]);
{
// Unresolved TLA promise, module file
const { status, stdout, stderr } = child_process.spawnSync(
process.execPath,
['--no-warnings', fixtures.path('es-modules/tla/unresolved-withexitcode.mjs')],
{ encoding: 'utf8' });
assert.deepStrictEqual([status, stdout, stderr], [42, '', '']);
}
assert.strictEqual(stderr, '');
assert.strictEqual(stdout, '');
assert.strictEqual(code, 13);
});
{
// Rejected TLA promise, module file
const { status, stdout, stderr } = child_process.spawnSync(
process.execPath,
['--no-warnings', fixtures.path('es-modules/tla/rejected-withexitcode.mjs')],
{ encoding: 'utf8' });
assert.deepStrictEqual([status, stdout], [1, '']);
assert.match(stderr, /Error: Xyz/);
}
it('should throw for a rejected TLA promise via stdin', async () => {
const { code, stderr, stdout } = await spawnPromisified(execPath, [
'--no-warnings',
fixtures.path('es-modules/tla/rejected.mjs'),
]);
{
// Calling process.exit() in .mjs should return status 0
const { status, stdout, stderr } = child_process.spawnSync(
process.execPath,
['--no-warnings', fixtures.path('es-modules/tla/process-exit.mjs')],
{ encoding: 'utf8' });
assert.deepStrictEqual([status, stdout, stderr], [0, '', '']);
}
assert.match(stderr, /Error: Xyz/);
assert.strictEqual(stdout, '');
assert.strictEqual(code, 1);
});
{
// Calling process.exit() in worker thread shouldn't influence main thread
const { status, stdout, stderr } = child_process.spawnSync(
process.execPath,
['--no-warnings', fixtures.path('es-modules/tla/unresolved-with-worker-process-exit.mjs')],
{ encoding: 'utf8' });
assert.deepStrictEqual([status, stdout, stderr], [13, '', '']);
}
it('should exit for an unsettled TLA promise and respect explicit exit code via stdin', async () => {
const { code, stderr, stdout } = await spawnPromisified(execPath, [
'--no-warnings',
fixtures.path('es-modules/tla/unresolved-withexitcode.mjs'),
]);
assert.strictEqual(stderr, '');
assert.strictEqual(stdout, '');
assert.strictEqual(code, 42);
});
it('should throw for a rejected TLA promise and ignore explicit exit code via stdin', async () => {
const { code, stderr, stdout } = await spawnPromisified(execPath, [
'--no-warnings',
fixtures.path('es-modules/tla/rejected-withexitcode.mjs'),
]);
assert.match(stderr, /Error: Xyz/);
assert.strictEqual(stdout, '');
assert.strictEqual(code, 1);
});
it('should exit successfully when calling `process.exit()` in `.mjs` file', async () => {
const { code, stderr, stdout } = await spawnPromisified(execPath, [
'--no-warnings',
fixtures.path('es-modules/tla/process-exit.mjs'),
]);
assert.strictEqual(stderr, '');
assert.strictEqual(stdout, '');
assert.strictEqual(code, 0);
});
it('should be unaffected by `process.exit()` in worker thread', async () => {
const { code, stderr, stdout } = await spawnPromisified(execPath, [
'--no-warnings',
fixtures.path('es-modules/tla/unresolved-with-worker-process-exit.mjs'),
]);
assert.strictEqual(stderr, '');
assert.strictEqual(stdout, '');
assert.strictEqual(code, 13);
});
});

View file

@ -1,40 +1,36 @@
'use strict';
const common = require('../common');
const fixtures = require('../common/fixtures');
const { spawn } = require('child_process');
const assert = require('assert');
const { spawnPromisified } = require('../common');
const fixtures = require('../common/fixtures.js');
const assert = require('node:assert');
const { execPath } = require('node:process');
const { describe, it } = require('node:test');
// In a "type": "module" package scope, files with unknown extensions or no
// extensions should throw; both when used as a main entry point and also when
// referenced via `import`.
describe('ESM: extensionless and unknown specifiers', { concurrency: true }, () => {
for (
const fixturePath of [
'/es-modules/package-type-module/noext-esm',
'/es-modules/package-type-module/imports-noext.mjs',
'/es-modules/package-type-module/extension.unknown',
'/es-modules/package-type-module/imports-unknownext.mjs',
]
) {
it('should throw', async () => {
const entry = fixtures.path(fixturePath);
const { code, signal, stderr, stdout } = await spawnPromisified(execPath, [entry]);
[
'/es-modules/package-type-module/noext-esm',
'/es-modules/package-type-module/imports-noext.mjs',
'/es-modules/package-type-module/extension.unknown',
'/es-modules/package-type-module/imports-unknownext.mjs',
].forEach((fixturePath) => {
const entry = fixtures.path(fixturePath);
const child = spawn(process.execPath, [entry]);
let stdout = '';
let stderr = '';
child.stderr.setEncoding('utf8');
child.stdout.setEncoding('utf8');
child.stdout.on('data', (data) => {
stdout += data;
});
child.stderr.on('data', (data) => {
stderr += data;
});
child.on('close', common.mustCall((code, signal) => {
assert.strictEqual(code, 1);
assert.strictEqual(signal, null);
assert.strictEqual(stdout, '');
assert.ok(stderr.includes('ERR_UNKNOWN_FILE_EXTENSION'));
if (fixturePath.includes('noext')) {
// Check for explanation to users
assert.ok(stderr.includes('extensionless'));
}
}));
assert.strictEqual(code, 1);
assert.strictEqual(signal, null);
assert.strictEqual(stdout, '');
assert.ok(stderr.includes('ERR_UNKNOWN_FILE_EXTENSION'));
if (fixturePath.includes('noext')) {
// Check for explanation to users
assert.ok(stderr.includes('extensionless'));
}
});
}
});

View file

@ -1,37 +1,43 @@
// Flags: --experimental-wasm-modules
import '../common/index.mjs';
import { path } from '../common/fixtures.mjs';
import { add, addImported } from '../fixtures/es-modules/simple.wasm';
import { state } from '../fixtures/es-modules/wasm-dep.mjs';
import { strictEqual, ok } from 'assert';
import { spawn } from 'child_process';
import { spawnPromisified } from '../common/index.mjs';
import * as fixtures from '../common/fixtures.mjs';
import { strictEqual, match } from 'node:assert';
import { execPath } from 'node:process';
import { describe, it } from 'node:test';
strictEqual(state, 'WASM Start Executed');
strictEqual(add(10, 20), 30);
describe('ESM: WASM modules', { concurrency: true }, () => {
it('should load exports', async () => {
const { code, stderr, stdout } = await spawnPromisified(execPath, [
'--no-warnings',
'--experimental-wasm-modules',
'--input-type=module',
'--eval',
[
'import { strictEqual, match } from "node:assert";',
`import { add, addImported } from ${JSON.stringify(fixtures.fileURL('es-modules/simple.wasm'))};`,
`import { state } from ${JSON.stringify(fixtures.fileURL('es-modules/wasm-dep.mjs'))};`,
'strictEqual(state, "WASM Start Executed");',
'strictEqual(add(10, 20), 30);',
'strictEqual(addImported(0), 42);',
'strictEqual(state, "WASM JS Function Executed");',
'strictEqual(addImported(1), 43);',
].join('\n'),
]);
strictEqual(addImported(0), 42);
strictEqual(stderr, '');
strictEqual(stdout, '');
strictEqual(code, 0);
});
strictEqual(state, 'WASM JS Function Executed');
it('should emit experimental warning', async () => {
const { code, signal, stderr } = await spawnPromisified(execPath, [
'--experimental-wasm-modules',
fixtures.path('es-modules/wasm-modules.mjs'),
]);
strictEqual(addImported(1), 43);
// Test warning message
const child = spawn(process.execPath, [
'--experimental-wasm-modules',
path('/es-modules/wasm-modules.mjs'),
]);
let stderr = '';
child.stderr.setEncoding('utf8');
child.stderr.on('data', (data) => {
stderr += data;
});
child.on('close', (code, signal) => {
strictEqual(code, 0);
strictEqual(signal, null);
ok(stderr.toString().includes(
'ExperimentalWarning: Importing WebAssembly modules is ' +
'an experimental feature. This feature could change at any time'
));
strictEqual(code, 0);
strictEqual(signal, null);
match(stderr, /ExperimentalWarning/);
match(stderr, /WebAssembly/);
});
});

View file

@ -1,48 +1,48 @@
import { mustCall } from '../common/index.mjs';
import { match, notStrictEqual } from 'assert';
import { spawn } from 'child_process';
import { execPath } from 'process';
import { mustCall, spawnPromisified } from '../common/index.mjs';
import { ok, match, notStrictEqual } from 'node:assert';
import { spawn as spawnAsync } from 'node:child_process';
import { execPath } from 'node:process';
import { describe, it } from 'node:test';
{
const child = spawn(execPath, [
'--experimental-network-imports',
'--input-type=module',
'-e',
'import "http://example.com"',
]);
let stderr = '';
child.stderr.setEncoding('utf8');
child.stderr.on('data', (data) => {
stderr += data;
});
child.on('close', mustCall((code, _signal) => {
describe('ESM: http import via CLI', { concurrency: true }, () => {
const disallowedSpecifier = 'http://example.com';
it('should throw disallowed error for insecure protocol', async () => {
const { code, stderr } = await spawnPromisified(execPath, [
'--experimental-network-imports',
'--input-type=module',
'--eval',
`import ${JSON.stringify(disallowedSpecifier)}`,
]);
notStrictEqual(code, 0);
// [ERR_NETWORK_IMPORT_DISALLOWED]: import of 'http://example.com/' by
// …/[eval1] is not supported: http can only be used to load local
// resources (use https instead).
match(stderr, /[ERR_NETWORK_IMPORT_DISALLOWED]/);
}));
}
{
const child = spawn(execPath, [
'--experimental-network-imports',
'--input-type=module',
]);
child.stdin.end('import "http://example.com"');
let stderr = '';
child.stderr.setEncoding('utf8');
child.stderr.on('data', (data) => {
stderr += data;
match(stderr, /ERR_NETWORK_IMPORT_DISALLOWED/);
ok(stderr.includes(disallowedSpecifier));
});
child.on('close', mustCall((code, _signal) => {
notStrictEqual(code, 0);
// [ERR_NETWORK_IMPORT_DISALLOWED]: import of 'http://example.com/' by
// …/[stdin] is not supported: http can only be used to load local
// resources (use https instead).
match(stderr, /[ERR_NETWORK_IMPORT_DISALLOWED]/);
}));
}
it('should throw disallowed error for insecure protocol in REPL', () => {
const child = spawnAsync(execPath, [
'--experimental-network-imports',
'--input-type=module',
]);
child.stdin.end(`import ${JSON.stringify(disallowedSpecifier)}`);
let stderr = '';
child.stderr.setEncoding('utf8');
child.stderr.on('data', (data) => stderr += data);
child.on('close', mustCall((code, _signal) => {
notStrictEqual(code, 0);
// [ERR_NETWORK_IMPORT_DISALLOWED]: import of 'http://example.com/' by
// …/[stdin] is not supported: http can only be used to load local
// resources (use https instead).
match(stderr, /\[ERR_NETWORK_IMPORT_DISALLOWED\]/);
ok(stderr.includes(disallowedSpecifier));
}));
});
});

View file

@ -4,14 +4,14 @@ const JSON_URL_PATTERN = /\.json(\?[^#]*)?(#.*)?$/;
export function resolve(url, context, next) {
// Mutation from resolve hook should be discarded.
context.importAssertions.type = 'whatever';
return next(url, context);
return next(url);
}
export function load(url, context, next) {
if (context.importAssertions.type == null &&
if (context.importAssertions.type == null &&
(DATA_URL_PATTERN.test(url) || JSON_URL_PATTERN.test(url))) {
const { importAssertions } = context;
importAssertions.type = 'json';
}
return next(url, context);
return next(url);
}

View file

@ -35,7 +35,7 @@ export function load(url, context, next) {
format: 'module',
};
}
return next(url, context);
return next(url);
}
function generateBuiltinModule(builtinName) {

View file

@ -8,7 +8,7 @@ const JS_EXTENSIONS = new Set(['.js', '.mjs']);
const baseURL = new URL('file://');
baseURL.pathname = process.cwd() + '/';
export function resolve(specifier, { parentURL = baseURL }, defaultResolve) {
export function resolve(specifier, { parentURL = baseURL }, next) {
if (builtinModules.includes(specifier)) {
return {
shortCircuit: true,
@ -17,7 +17,7 @@ export function resolve(specifier, { parentURL = baseURL }, defaultResolve) {
}
if (/^\.{1,2}[/]/.test(specifier) !== true && !specifier.startsWith('file:')) {
// For node_modules support:
// return defaultResolve(specifier, {parentURL}, defaultResolve);
// return next(specifier);
throw new Error(
`imports must be URLs or begin with './', or '../'; '${specifier}' does not`);
}

View file

@ -3,7 +3,7 @@ let importedCJS = 0;
global.getModuleTypeStats = () => { return {importedESM, importedCJS} };
export async function load(url, context, next) {
return next(url, context, next);
return next(url);
}
export async function resolve(specifier, context, next) {

View file

@ -56,10 +56,10 @@ export function load(url, context, next) {
source: `export const message = 'Woohoo!'.toUpperCase();`,
};
return next(url, context, next);
return next(url);
}
export function resolve(specifier, context, next) {
export function resolve(specifier, { importAssertions }, next) {
let format = '';
if (specifier === 'esmHook/format.false') format = false;
@ -70,8 +70,8 @@ export function resolve(specifier, context, next) {
format,
shortCircuit: true,
url: pathToFileURL(specifier).href,
importAssertions: context.importAssertions,
importAssertions,
};
return next(specifier, context, next);
return next(specifier);
}

View file

@ -4,19 +4,19 @@ export function getSource() {}
export function transformSource() {}
export function resolve(specifier, context, next) {
if (specifier === 'whatever') return {
url: specifier,
};
return next(specifier);
}
export function load(url, context, next) {
if (url === 'whatever') return {
format: 'module',
source: '',
};
return next(url, context, next);
}
export function resolve(specifier, context, next) {
if (specifier === 'whatever') return {
url: specifier,
};
return next(specifier, context, next);
return next(url);
}

View file

@ -15,7 +15,7 @@ export function resolve(specifier, context, nextResolve) {
};
}
return nextResolve(specifier, context);
return nextResolve(specifier);
}
export function load(url, context, nextLoad) {
@ -36,5 +36,5 @@ export function load(url, context, nextLoad) {
});
}
return nextLoad(url, context);
return nextLoad(url);
}

View file

@ -1,10 +0,0 @@
export async function getFormat(url, context, defaultGetFormat) {
try {
if (new URL(url).pathname.endsWith('.unknown')) {
return {
format: 'module'
};
}
} catch {}
return defaultGetFormat(url, context, defaultGetFormat);
}

View file

@ -1,11 +1,11 @@
export async function resolve(specifier, { parentURL, importAssertions }, defaultResolve) {
export async function resolve(specifier, { parentURL, importAssertions }, next) {
if (parentURL && specifier === '../fixtures/es-modules/test-esm-ok.mjs') {
return {
shortCircuit: true,
url: 'file:///asdf',
};
}
return defaultResolve(specifier, {parentURL, importAssertions}, defaultResolve);
return next(specifier);
}
export async function load(url, context, next) {
@ -16,5 +16,5 @@ export async function load(url, context, next) {
source: '',
}
}
return next(url, context, next);
return next(url);
}

View file

@ -1,4 +1,4 @@
export async function resolve(specifier, { parentURL, importAssertions }, defaultResolve) {
export async function resolve(specifier, { parentURL, importAssertions }, next) {
if (parentURL && specifier === '../fixtures/es-modules/test-esm-ok.mjs') {
return {
shortCircuit: true,
@ -6,5 +6,5 @@ export async function resolve(specifier, { parentURL, importAssertions }, defaul
importAssertions,
};
}
return defaultResolve(specifier, {parentURL, importAssertions}, defaultResolve);
return next(specifier);
}

View file

@ -1,3 +1,3 @@
export async function load(url, context, next) {
return next([], context);
return next([]);
}

View file

@ -1,3 +1,3 @@
export async function load(url, context, next) {
return next('not/a/url', context);
return next('not/a/url');
}

View file

@ -2,7 +2,7 @@ export async function load(url, context, next) {
const {
format,
source,
} = await next(url, context);
} = await next(url);
return {
format,

View file

@ -1,4 +1,4 @@
export async function load(url, context, next) {
console.log('load passthru'); // This log is deliberate
return next(url, context);
return next(url);
}

View file

@ -1,3 +1,3 @@
export async function resolve(specifier, context, next) {
return next([], context);
return next([]);
}

View file

@ -1,4 +1,4 @@
export async function resolve(specifier, context, next) {
console.log('resolve foo'); // This log is deliberate
return next('file:///foo.mjs', context);
return next('file:///foo.mjs');
}

View file

@ -1,6 +1,6 @@
export async function resolve(specifier, context, next) {
const { url: first } = await next(specifier, context);
const { url: second } = await next(specifier, context);
const { url: first } = await next(specifier);
const { url: second } = await next(specifier);
return {
format: 'module',

View file

@ -1,4 +1,4 @@
export async function resolve(specifier, context, next) {
console.log('resolve passthru'); // This log is deliberate
return next(specifier, context);
return next(specifier);
}

View file

@ -5,7 +5,7 @@ import { createRequire } from '../../common/index.mjs';
const require = createRequire(import.meta.url);
const dep = require('./loader-dep.js');
export function resolve(specifier, { parentURL, importAssertions }, defaultResolve) {
export function resolve(specifier, context, next) {
assert.strictEqual(dep.format, 'module');
return defaultResolve(specifier, { parentURL, importAssertions }, defaultResolve);
return next(specifier);
}

View file

@ -3,5 +3,5 @@ export function resolve(specifier, context, next) {
url: 'node:unknown-builtin-module'
};
return next(specifier, context, next);
return next(specifier);
}

View file

@ -1,10 +1,10 @@
export function resolve(specifier, { parentURL }, defaultResolve) {
export function resolve(specifier, context, next) {
if (specifier === 'test') {
return {
url: 'file://'
};
}
return defaultResolve(specifier, {parentURL}, defaultResolve);
return next(specifier);
}
export function getFormat(url, context, defaultGetFormat) {

View file

@ -1,15 +1,15 @@
import assert from 'assert';
import assert from 'node:assert';
// a loader that asserts that the defaultResolve will throw "not found"
// (skipping the top-level main of course)
let mainLoad = true;
export async function resolve(specifier, { parentURL, importAssertions }, defaultResolve) {
export async function resolve(specifier, { importAssertions }, next) {
if (mainLoad) {
mainLoad = false;
return defaultResolve(specifier, {parentURL, importAssertions}, defaultResolve);
return next(specifier);
}
try {
await defaultResolve(specifier, {parentURL, importAssertions}, defaultResolve);
await next(specifier);
}
catch (e) {
assert.strictEqual(e.code, 'ERR_MODULE_NOT_FOUND');

View file

@ -28,7 +28,7 @@ export function resolve(specifier, context, next) {
url: specifier,
};
}
return next(specifier, context);
return next(specifier);
}
export function load(href, context, next) {
@ -39,5 +39,5 @@ export function load(href, context, next) {
source: SOURCES[href],
};
}
return next(href, context);
return next(href);
}