resolve.imports
Version:
resolve "imports" in package.json
62 lines (61 loc) • 2.57 kB
JavaScript
import { patternKeyCompare } from 'pattern-key-compare';
import { assert, ERR_INVALID_MODULE_SPECIFIER, ERR_PACKAGE_IMPORT_NOT_DEFINED } from './errors.js';
/**
* Resolve an import specifier based on the `imports` field in `package.json`.
*
* @param manifest of package.json
* @param specifier import specifier
* @return resolved specifier or undefined if not found
* @see https://nodejs.org/api/packages.html#subpath-imports
*/
export function resolve(manifest, specifier, options) {
assert(specifier.startsWith('#'), 'import specifier must start with #');
if (specifier === '#' || specifier === '#/')
throw new ERR_INVALID_MODULE_SPECIFIER(specifier, `is not a valid internal imports specifier name`, manifest.base);
if (manifest.content.imports) {
const conditions = new Set(options?.conditions ?? []);
const matched = manifest.content.imports[specifier];
if (matched) {
return noRecursive(resolvePackagePattern(matched, conditions));
}
const expansionKeys = getExpensionKeys(Object.keys(manifest.content.imports));
for (const key of expansionKeys) {
const keyParts = key.split('*');
const [prefix, suffix] = keyParts;
if (specifier.startsWith(prefix)) {
const replacer = resolvePackagePattern(manifest.content.imports[key], conditions);
if (replacer)
return noRecursive(replacePattern(replacer));
}
function replacePattern(replacer) {
const toKeep = suffix ? specifier.slice(prefix.length, -suffix.length) : specifier.slice(prefix.length);
return replacer.replace(/\*/g, toKeep);
}
}
}
throw new ERR_PACKAGE_IMPORT_NOT_DEFINED(specifier, manifest.path, manifest.base);
}
function resolvePackagePattern(map, conditions) {
if (typeof map === 'string')
return map;
if (Array.isArray(map)) {
for (const item of map) {
const result = resolvePackagePattern(item, conditions);
if (result)
return result;
}
return undefined;
}
for (const key of Object.keys(map)) {
if (conditions.has(key))
return resolvePackagePattern(map[key], conditions);
}
return map.default;
}
function noRecursive(value) {
assert(!value?.startsWith('#'), 'recursive imports are not allowed');
return value;
}
function getExpensionKeys(keys) {
return keys.filter(k => k.indexOf('*') >= 0).sort(patternKeyCompare);
}