diff --git a/lib/internal/modules/esm/resolve.js b/lib/internal/modules/esm/resolve.js index 8b6f23bb485d8b..0a0da107682469 100644 --- a/lib/internal/modules/esm/resolve.js +++ b/lib/internal/modules/esm/resolve.js @@ -59,7 +59,26 @@ const userConditions = getOptionValue('--conditions'); const DEFAULT_CONDITIONS = ObjectFreeze(['node', 'import', ...userConditions]); const DEFAULT_CONDITIONS_SET = new SafeSet(DEFAULT_CONDITIONS); +/** + * @typedef {string | string[] | Record} Exports + * @typedef {'module' | 'commonjs'} PackageType + * @typedef {{ + * exports?: ExportConfig; + * name?: string; + * main?: string; + * type?: PackageType; + * }} PackageConfig + */ + const emittedPackageWarnings = new SafeSet(); + +/** + * @param {string} match + * @param {URL} pjsonUrl + * @param {boolean} isExports + * @param {string | URL | undefined} base + * @returns {void} + */ function emitFolderMapDeprecation(match, pjsonUrl, isExports, base) { const pjsonPath = fileURLToPath(pjsonUrl); @@ -76,6 +95,13 @@ function emitFolderMapDeprecation(match, pjsonUrl, isExports, base) { ); } +/** + * @param {URL} url + * @param {URL} packageJSONUrl + * @param {string | URL | undefined} base + * @param {string} main + * @returns + */ function emitLegacyIndexDeprecation(url, packageJSONUrl, base, main) { const { format } = defaultGetFormat(url); if (format !== 'module') @@ -104,6 +130,10 @@ function emitLegacyIndexDeprecation(url, packageJSONUrl, base, main) { ); } +/** + * @param {string[]} [conditions] + * @returns {Set} + */ function getConditionsSet(conditions) { if (conditions !== undefined && conditions !== DEFAULT_CONDITIONS) { if (!ArrayIsArray(conditions)) { @@ -118,9 +148,19 @@ function getConditionsSet(conditions) { const realpathCache = new SafeMap(); const packageJSONCache = new SafeMap(); /* string -> PackageConfig */ +/** + * @param {string | URL} path + * @returns {import('fs').Stats} + */ const tryStatSync = (path) => statSync(path, { throwIfNoEntry: false }) ?? new Stats(); +/** + * @param {string} path + * @param {string} specifier + * @param {string | URL | undefined} base + * @returns {PackageConfig} + */ function getPackageConfig(path, specifier, base) { const existing = packageJSONCache.get(path); if (existing !== undefined) { @@ -173,6 +213,10 @@ function getPackageConfig(path, specifier, base) { return packageConfig; } +/** + * @param {URL | string} resolved + * @returns {PackageConfig} + */ function getPackageScopeConfig(resolved) { let packageJSONUrl = new URL('./package.json', resolved); while (true) { @@ -205,12 +249,6 @@ function getPackageScopeConfig(resolved) { } /** - * Legacy CommonJS main resolution: - * 1. let M = pkg_url + (json main field) - * 2. TRY(M, M.js, M.json, M.node) - * 3. TRY(M/index.js, M/index.json, M/index.node) - * 4. TRY(pkg_url/index.js, pkg_url/index.json, pkg_url/index.node) - * 5. NOT_FOUND * @param {string | URL} url * @returns {boolean} */ @@ -218,6 +256,18 @@ function fileExists(url) { return statSync(url, { throwIfNoEntry: false })?.isFile() ?? false; } +/** + * Legacy CommonJS main resolution: + * 1. let M = pkg_url + (json main field) + * 2. TRY(M, M.js, M.json, M.node) + * 3. TRY(M/index.js, M/index.json, M/index.node) + * 4. TRY(pkg_url/index.js, pkg_url/index.json, pkg_url/index.node) + * 5. NOT_FOUND + * @param {URL} packageJSONUrl + * @param {PackageConfig} packageConfig + * @param {string | URL | undefined} base + * @returns {URL} + */ function legacyMainResolve(packageJSONUrl, packageConfig, base) { let guess; if (packageConfig.main !== undefined) { @@ -259,12 +309,21 @@ function legacyMainResolve(packageJSONUrl, packageConfig, base) { fileURLToPath(new URL('.', packageJSONUrl)), fileURLToPath(base)); } +/** + * @param {URL} search + * @returns {URL | undefined} + */ function resolveExtensionsWithTryExactName(search) { if (fileExists(search)) return search; return resolveExtensions(search); } const extensions = ['.js', '.json', '.node', '.mjs']; + +/** + * @param {URL} search + * @returns {URL | undefined} + */ function resolveExtensions(search) { for (let i = 0; i < extensions.length; i++) { const extension = extensions[i]; @@ -274,6 +333,10 @@ function resolveExtensions(search) { return undefined; } +/** + * @param {URL} search + * @returns {URL | undefined} + */ function resolveDirectoryEntry(search) { const dirPath = fileURLToPath(search); const pkgJsonPath = resolve(dirPath, 'package.json'); @@ -291,6 +354,11 @@ function resolveDirectoryEntry(search) { } const encodedSepRegEx = /%2F|%2C/i; +/** + * @param {URL} resolved + * @param {string | URL | undefined} base + * @returns {URL | undefined} + */ function finalizeResolution(resolved, base) { if (RegExpPrototypeTest(encodedSepRegEx, resolved.pathname)) throw new ERR_INVALID_MODULE_SPECIFIER( @@ -325,18 +393,35 @@ function finalizeResolution(resolved, base) { return resolved; } +/** + * @param {string} specifier + * @param {URL} packageJSONUrl + * @param {string | URL | undefined} base + */ function throwImportNotDefined(specifier, packageJSONUrl, base) { throw new ERR_PACKAGE_IMPORT_NOT_DEFINED( specifier, packageJSONUrl && fileURLToPath(new URL('.', packageJSONUrl)), fileURLToPath(base)); } +/** + * @param {string} specifier + * @param {URL} packageJSONUrl + * @param {string | URL | undefined} base + */ function throwExportsNotFound(subpath, packageJSONUrl, base) { throw new ERR_PACKAGE_PATH_NOT_EXPORTED( fileURLToPath(new URL('.', packageJSONUrl)), subpath, base && fileURLToPath(base)); } +/** + * + * @param {string | URL} subpath + * @param {URL} packageJSONUrl + * @param {boolean} internal + * @param {string | URL | undefined} base + */ function throwInvalidSubpath(subpath, packageJSONUrl, internal, base) { const reason = `request is not a valid subpath for the "${internal ? 'imports' : 'exports'}" resolution of ${fileURLToPath(packageJSONUrl)}`; @@ -478,6 +563,13 @@ function resolvePackageTarget(packageJSONUrl, target, subpath, packageSubpath, base); } +/** + * + * @param {Exports} exports + * @param {URL} packageJSONUrl + * @param {string | URL | undefined} base + * @returns + */ function isConditionalExportsMainSugar(exports, packageJSONUrl, base) { if (typeof exports === 'string' || ArrayIsArray(exports)) return true; if (typeof exports !== 'object' || exports === null) return false; @@ -504,8 +596,8 @@ function isConditionalExportsMainSugar(exports, packageJSONUrl, base) { /** * @param {URL} packageJSONUrl * @param {string} packageSubpath - * @param {object} packageConfig - * @param {string} base + * @param {PackageConfig} packageConfig + * @param {string | URL | undefined} base * @param {Set} conditions * @returns {URL} */ @@ -560,6 +652,12 @@ function packageExportsResolve( throwExportsNotFound(packageSubpath, packageJSONUrl, base); } +/** + * @param {string} name + * @param {string | URL | undefined} base + * @param {Set} conditions + * @returns + */ function packageImportsResolve(name, base, conditions) { if (name === '#' || StringPrototypeStartsWith(name, '#/')) { const reason = 'is not a valid internal imports specifier name'; @@ -615,11 +713,20 @@ function packageImportsResolve(name, base, conditions) { throwImportNotDefined(name, packageJSONUrl, base); } +/** + * @param {URL} url + * @returns {PackageType} + */ function getPackageType(url) { const packageConfig = getPackageScopeConfig(url); return packageConfig.type; } +/** + * @param {string} specifier + * @param {string | URL | undefined} base + * @returns {{ packageName: string, packageSubpath: string, isScoped: boolean }} + */ function parsePackageName(specifier, base) { let separatorIndex = StringPrototypeIndexOf(specifier, '/'); let validPackageName = true; @@ -659,7 +766,7 @@ function parsePackageName(specifier, base) { /** * @param {string} specifier - * @param {URL} base + * @param {string | URL | undefined} base * @param {Set} conditions * @returns {URL} */ @@ -712,6 +819,10 @@ function packageResolve(specifier, base, conditions) { throw new ERR_MODULE_NOT_FOUND(packageName, fileURLToPath(base)); } +/** + * @param {string} specifier + * @returns {boolean} + */ function isBareSpecifier(specifier) { return specifier[0] && specifier[0] !== '/' && specifier[0] !== '.'; } @@ -734,7 +845,7 @@ function shouldBeTreatedAsRelativeOrAbsolutePath(specifier) { /** * @param {string} specifier - * @param {URL} base + * @param {string | URL | undefined} base * @param {Set} conditions * @returns {URL} */