mirror of
https://github.com/nodejs/node.git
synced 2024-11-29 23:16:30 +01:00
3f3ad38c87
This reintroduces the dot main in exports as discussed in the previous Node.js modules meeting. The implementation includes both CommonJS and ES module resolution with the associated documentation and resolver specification changes. In addition to the dot main, "exports" as a string or direct fallback array is supported as well. Co-Authored-By: Geoffrey Booth <GeoffreyBooth@users.noreply.github.com> PR-URL: https://github.com/nodejs/node/pull/29494 Reviewed-By: Jan Krems <jan.krems@gmail.com> Reviewed-By: Myles Borins <myles.borins@gmail.com>
111 lines
4.4 KiB
JavaScript
111 lines
4.4 KiB
JavaScript
// Flags: --experimental-modules
|
|
|
|
import { mustCall } from '../common/index.mjs';
|
|
import { ok, deepStrictEqual, strictEqual } from 'assert';
|
|
|
|
import { requireFixture, importFixture } from '../fixtures/pkgexports.mjs';
|
|
|
|
[requireFixture, importFixture].forEach((loadFixture) => {
|
|
const isRequire = loadFixture === requireFixture;
|
|
|
|
const validSpecifiers = new Map([
|
|
// A simple mapping of a path.
|
|
['pkgexports/valid-cjs', { default: 'asdf' }],
|
|
// A directory mapping, pointing to the package root.
|
|
['pkgexports/sub/asdf.js', { default: 'asdf' }],
|
|
// A mapping pointing to a file that needs special encoding (%20) in URLs.
|
|
['pkgexports/space', { default: 'encoded path' }],
|
|
// Verifying that normal packages still work with exports turned on.
|
|
isRequire ? ['baz/index', { default: 'eye catcher' }] : [null],
|
|
// Fallbacks
|
|
['pkgexports/fallbackdir/asdf.js', { default: 'asdf' }],
|
|
['pkgexports/fallbackfile', { default: 'asdf' }],
|
|
// Dot main
|
|
['pkgexports', { default: 'asdf' }],
|
|
]);
|
|
for (const [validSpecifier, expected] of validSpecifiers) {
|
|
if (validSpecifier === null) continue;
|
|
|
|
loadFixture(validSpecifier)
|
|
.then(mustCall((actual) => {
|
|
deepStrictEqual({ ...actual }, expected);
|
|
}));
|
|
}
|
|
|
|
const undefinedExports = new Map([
|
|
// There's no such export - so there's nothing to do.
|
|
['pkgexports/missing', './missing'],
|
|
// The file exists but isn't exported. The exports is a number which counts
|
|
// as a non-null value without any properties, just like `{}`.
|
|
['pkgexports-number/hidden.js', './hidden.js'],
|
|
]);
|
|
|
|
const invalidExports = new Map([
|
|
// Even though 'pkgexports/sub/asdf.js' works, alternate "path-like"
|
|
// variants do not to prevent confusion and accidental loopholes.
|
|
['pkgexports/sub/./../asdf.js', './sub/./../asdf.js'],
|
|
// This path steps back inside the package but goes through an exports
|
|
// target that escapes the package, so we still catch that as invalid
|
|
['pkgexports/belowdir/pkgexports/asdf.js', './belowdir/pkgexports/asdf.js'],
|
|
// This target file steps below the package
|
|
['pkgexports/belowfile', './belowfile'],
|
|
// Directory mappings require a trailing / to work
|
|
['pkgexports/missingtrailer/x', './missingtrailer/x'],
|
|
// Invalid target handling
|
|
['pkgexports/null', './null'],
|
|
['pkgexports/invalid1', './invalid1'],
|
|
['pkgexports/invalid2', './invalid2'],
|
|
['pkgexports/invalid3', './invalid3'],
|
|
['pkgexports/invalid4', './invalid4'],
|
|
// Missing / invalid fallbacks
|
|
['pkgexports/nofallback1', './nofallback1'],
|
|
['pkgexports/nofallback2', './nofallback2'],
|
|
// Reaching into nested node_modules
|
|
['pkgexports/nodemodules', './nodemodules'],
|
|
]);
|
|
|
|
for (const [specifier, subpath] of undefinedExports) {
|
|
loadFixture(specifier).catch(mustCall((err) => {
|
|
strictEqual(err.code, (isRequire ? '' : 'ERR_') + 'MODULE_NOT_FOUND');
|
|
assertStartsWith(err.message, 'Package exports');
|
|
assertIncludes(err.message, `do not define a '${subpath}' subpath`);
|
|
}));
|
|
}
|
|
|
|
for (const [specifier, subpath] of invalidExports) {
|
|
loadFixture(specifier).catch(mustCall((err) => {
|
|
strictEqual(err.code, (isRequire ? '' : 'ERR_') + 'MODULE_NOT_FOUND');
|
|
assertStartsWith(err.message, (isRequire ? 'Package exports' :
|
|
'Cannot resolve'));
|
|
assertIncludes(err.message, isRequire ?
|
|
`do not define a valid '${subpath}' subpath` :
|
|
`matched for '${subpath}'`);
|
|
}));
|
|
}
|
|
|
|
// Covering out bases - not a file is still not a file after dir mapping.
|
|
loadFixture('pkgexports/sub/not-a-file.js').catch(mustCall((err) => {
|
|
strictEqual(err.code, (isRequire ? '' : 'ERR_') + 'MODULE_NOT_FOUND');
|
|
// ESM returns a full file path
|
|
assertStartsWith(err.message, isRequire ?
|
|
'Cannot find module \'pkgexports/sub/not-a-file.js\'' :
|
|
'Cannot find module');
|
|
}));
|
|
|
|
// THe use of %2F escapes in paths fails loading
|
|
loadFixture('pkgexports/sub/..%2F..%2Fbar.js').catch(mustCall((err) => {
|
|
strictEqual(err.code, isRequire ? 'ERR_INVALID_FILE_URL_PATH' :
|
|
'ERR_MODULE_NOT_FOUND');
|
|
}));
|
|
});
|
|
|
|
function assertStartsWith(actual, expected) {
|
|
const start = actual.toString().substr(0, expected.length);
|
|
strictEqual(start, expected);
|
|
}
|
|
|
|
function assertIncludes(actual, expected) {
|
|
ok(actual.toString().indexOf(expected),
|
|
`${JSON.stringify(actual)} includes ${JSON.stringify(expected)}`);
|
|
}
|