| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301 |
- // Manually “tree shaken” from:
- // <https://github.com/nodejs/node/blob/81a9a97/lib/internal/modules/esm/resolve.js>
- // Last checked on: Apr 29, 2024.
- /**
- * @import {Stats} from 'node:fs'
- * @import {ErrnoException} from './errors.js'
- * @import {PackageConfig} from './package-json-reader.js'
- */
- import assert from 'node:assert'
- import {statSync, realpathSync} from 'node:fs'
- import process from 'node:process'
- import {fileURLToPath, pathToFileURL} from 'node:url'
- import path from 'node:path'
- import {builtinModules} from 'node:module'
- import {defaultGetFormatWithoutErrors} from './get-format.js'
- import {codes} from './errors.js'
- import {getPackageScopeConfig, read} from './package-json-reader.js'
- import {getConditionsSet} from './utils.js'
- const RegExpPrototypeSymbolReplace = RegExp.prototype[Symbol.replace]
- const {
- ERR_NETWORK_IMPORT_DISALLOWED,
- ERR_INVALID_MODULE_SPECIFIER,
- ERR_INVALID_PACKAGE_CONFIG,
- ERR_INVALID_PACKAGE_TARGET,
- ERR_MODULE_NOT_FOUND,
- ERR_PACKAGE_IMPORT_NOT_DEFINED,
- ERR_PACKAGE_PATH_NOT_EXPORTED,
- ERR_UNSUPPORTED_DIR_IMPORT,
- ERR_UNSUPPORTED_RESOLVE_REQUEST
- } = codes
- const own = {}.hasOwnProperty
- const invalidSegmentRegEx =
- /(^|\\|\/)((\.|%2e)(\.|%2e)?|(n|%6e|%4e)(o|%6f|%4f)(d|%64|%44)(e|%65|%45)(_|%5f)(m|%6d|%4d)(o|%6f|%4f)(d|%64|%44)(u|%75|%55)(l|%6c|%4c)(e|%65|%45)(s|%73|%53))?(\\|\/|$)/i
- const deprecatedInvalidSegmentRegEx =
- /(^|\\|\/)((\.|%2e)(\.|%2e)?|(n|%6e|%4e)(o|%6f|%4f)(d|%64|%44)(e|%65|%45)(_|%5f)(m|%6d|%4d)(o|%6f|%4f)(d|%64|%44)(u|%75|%55)(l|%6c|%4c)(e|%65|%45)(s|%73|%53))(\\|\/|$)/i
- const invalidPackageNameRegEx = /^\.|%|\\/
- const patternRegEx = /\*/g
- const encodedSeparatorRegEx = /%2f|%5c/i
- /** @type {Set<string>} */
- const emittedPackageWarnings = new Set()
- const doubleSlashRegEx = /[/\\]{2}/
- /**
- *
- * @param {string} target
- * @param {string} request
- * @param {string} match
- * @param {URL} packageJsonUrl
- * @param {boolean} internal
- * @param {URL} base
- * @param {boolean} isTarget
- */
- function emitInvalidSegmentDeprecation(
- target,
- request,
- match,
- packageJsonUrl,
- internal,
- base,
- isTarget
- ) {
- // @ts-expect-error: apparently it does exist, TS.
- if (process.noDeprecation) {
- return
- }
- const pjsonPath = fileURLToPath(packageJsonUrl)
- const double = doubleSlashRegEx.exec(isTarget ? target : request) !== null
- process.emitWarning(
- `Use of deprecated ${
- double ? 'double slash' : 'leading or trailing slash matching'
- } resolving "${target}" for module ` +
- `request "${request}" ${
- request === match ? '' : `matched to "${match}" `
- }in the "${
- internal ? 'imports' : 'exports'
- }" field module resolution of the package at ${pjsonPath}${
- base ? ` imported from ${fileURLToPath(base)}` : ''
- }.`,
- 'DeprecationWarning',
- 'DEP0166'
- )
- }
- /**
- * @param {URL} url
- * @param {URL} packageJsonUrl
- * @param {URL} base
- * @param {string} [main]
- * @returns {void}
- */
- function emitLegacyIndexDeprecation(url, packageJsonUrl, base, main) {
- // @ts-expect-error: apparently it does exist, TS.
- if (process.noDeprecation) {
- return
- }
- const format = defaultGetFormatWithoutErrors(url, {parentURL: base.href})
- if (format !== 'module') return
- const urlPath = fileURLToPath(url.href)
- const packagePath = fileURLToPath(new URL('.', packageJsonUrl))
- const basePath = fileURLToPath(base)
- if (!main) {
- process.emitWarning(
- `No "main" or "exports" field defined in the package.json for ${packagePath} resolving the main entry point "${urlPath.slice(
- packagePath.length
- )}", imported from ${basePath}.\nDefault "index" lookups for the main are deprecated for ES modules.`,
- 'DeprecationWarning',
- 'DEP0151'
- )
- } else if (path.resolve(packagePath, main) !== urlPath) {
- process.emitWarning(
- `Package ${packagePath} has a "main" field set to "${main}", ` +
- `excluding the full filename and extension to the resolved file at "${urlPath.slice(
- packagePath.length
- )}", imported from ${basePath}.\n Automatic extension resolution of the "main" field is ` +
- 'deprecated for ES modules.',
- 'DeprecationWarning',
- 'DEP0151'
- )
- }
- }
- /**
- * @param {string} path
- * @returns {Stats | undefined}
- */
- function tryStatSync(path) {
- // Note: from Node 15 onwards we can use `throwIfNoEntry: false` instead.
- try {
- return statSync(path)
- } catch {
- // Note: in Node code this returns `new Stats`,
- // but in Node 22 that’s marked as a deprecated internal API.
- // Which, well, we kinda are, but still to prevent that warning,
- // just yield `undefined`.
- }
- }
- /**
- * 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} url
- * @returns {boolean}
- */
- function fileExists(url) {
- const stats = statSync(url, {throwIfNoEntry: false})
- const isFile = stats ? stats.isFile() : undefined
- return isFile === null || isFile === undefined ? false : isFile
- }
- /**
- * @param {URL} packageJsonUrl
- * @param {PackageConfig} packageConfig
- * @param {URL} base
- * @returns {URL}
- */
- function legacyMainResolve(packageJsonUrl, packageConfig, base) {
- /** @type {URL | undefined} */
- let guess
- if (packageConfig.main !== undefined) {
- guess = new URL(packageConfig.main, packageJsonUrl)
- // Note: fs check redundances will be handled by Descriptor cache here.
- if (fileExists(guess)) return guess
- const tries = [
- `./${packageConfig.main}.js`,
- `./${packageConfig.main}.json`,
- `./${packageConfig.main}.node`,
- `./${packageConfig.main}/index.js`,
- `./${packageConfig.main}/index.json`,
- `./${packageConfig.main}/index.node`
- ]
- let i = -1
- while (++i < tries.length) {
- guess = new URL(tries[i], packageJsonUrl)
- if (fileExists(guess)) break
- guess = undefined
- }
- if (guess) {
- emitLegacyIndexDeprecation(
- guess,
- packageJsonUrl,
- base,
- packageConfig.main
- )
- return guess
- }
- // Fallthrough.
- }
- const tries = ['./index.js', './index.json', './index.node']
- let i = -1
- while (++i < tries.length) {
- guess = new URL(tries[i], packageJsonUrl)
- if (fileExists(guess)) break
- guess = undefined
- }
- if (guess) {
- emitLegacyIndexDeprecation(guess, packageJsonUrl, base, packageConfig.main)
- return guess
- }
- // Not found.
- throw new ERR_MODULE_NOT_FOUND(
- fileURLToPath(new URL('.', packageJsonUrl)),
- fileURLToPath(base)
- )
- }
- /**
- * @param {URL} resolved
- * @param {URL} base
- * @param {boolean} [preserveSymlinks]
- * @returns {URL}
- */
- function finalizeResolution(resolved, base, preserveSymlinks) {
- if (encodedSeparatorRegEx.exec(resolved.pathname) !== null) {
- throw new ERR_INVALID_MODULE_SPECIFIER(
- resolved.pathname,
- 'must not include encoded "/" or "\\" characters',
- fileURLToPath(base)
- )
- }
- /** @type {string} */
- let filePath
- try {
- filePath = fileURLToPath(resolved)
- } catch (error) {
- const cause = /** @type {ErrnoException} */ (error)
- Object.defineProperty(cause, 'input', {value: String(resolved)})
- Object.defineProperty(cause, 'module', {value: String(base)})
- throw cause
- }
- const stats = tryStatSync(
- filePath.endsWith('/') ? filePath.slice(-1) : filePath
- )
- if (stats && stats.isDirectory()) {
- const error = new ERR_UNSUPPORTED_DIR_IMPORT(filePath, fileURLToPath(base))
- // @ts-expect-error Add this for `import.meta.resolve`.
- error.url = String(resolved)
- throw error
- }
- if (!stats || !stats.isFile()) {
- const error = new ERR_MODULE_NOT_FOUND(
- filePath || resolved.pathname,
- base && fileURLToPath(base),
- true
- )
- // @ts-expect-error Add this for `import.meta.resolve`.
- error.url = String(resolved)
- throw error
- }
- if (!preserveSymlinks) {
- const real = realpathSync(filePath)
- const {search, hash} = resolved
- resolved = pathToFileURL(real + (filePath.endsWith(path.sep) ? '/' : ''))
- resolved.search = search
- resolved.hash = hash
- }
- return resolved
- }
- /**
- * @param {string} specifier
- * @param {URL | undefined} packageJsonUrl
- * @param {URL} base
- * @returns {Error}
- */
- function importNotDefined(specifier, packageJsonUrl, base) {
- return new ERR_PACKAGE_IMPORT_NOT_DEFINED(
- specifier,
- packageJsonUrl && fileURLToPath(new URL('.', packageJsonUrl)),
- fileURLToPath(base)
- )
- }
- /**
- * @param {string} subpath
- * @param {URL} packageJsonUrl
- * @param {URL} base
- * @returns {Error}
- */
- function exportsNotFound(subpath, packageJsonUrl, base) {
- return new ERR_PACKAGE_PATH_NOT_EXPORTED(
- fileURLToPath(new URL('.', packageJsonUrl)),
- subpath,
- base && fileURLToPath(base)
- )
- }
- /**
- * @param {string} request
- * @param {string} match
- * @param {URL} packageJsonUrl
- * @param {boolean} internal
- * @param {URL} [base]
- * @returns {never}
- */
- function throwInvalidSubpath(request, match, packageJsonUrl, internal, base) {
- const reason = `request is not a valid match in pattern "${match}" for the "${
- internal ? 'imports' : 'exports'
- }" resolution of ${fileURLToPath(packageJsonUrl)}`
- throw new ERR_INVALID_MODULE_SPECIFIER(
- request,
- reason,
- base && fileURLToPath(base)
- )
- }
- /**
- * @param {string} subpath
- * @param {unknown} target
- * @param {URL} packageJsonUrl
- * @param {boolean} internal
- * @param {URL} [base]
- * @returns {Error}
- */
- function invalidPackageTarget(subpath, target, packageJsonUrl, internal, base) {
- target =
- typeof target === 'object' && target !== null
- ? JSON.stringify(target, null, '')
- : `${target}`
- return new ERR_INVALID_PACKAGE_TARGET(
- fileURLToPath(new URL('.', packageJsonUrl)),
- subpath,
- target,
- internal,
- base && fileURLToPath(base)
- )
- }
- /**
- * @param {string} target
- * @param {string} subpath
- * @param {string} match
- * @param {URL} packageJsonUrl
- * @param {URL} base
- * @param {boolean} pattern
- * @param {boolean} internal
- * @param {boolean} isPathMap
- * @param {Set<string> | undefined} conditions
- * @returns {URL}
- */
- function resolvePackageTargetString(
- target,
- subpath,
- match,
- packageJsonUrl,
- base,
- pattern,
- internal,
- isPathMap,
- conditions
- ) {
- if (subpath !== '' && !pattern && target[target.length - 1] !== '/')
- throw invalidPackageTarget(match, target, packageJsonUrl, internal, base)
- if (!target.startsWith('./')) {
- if (internal && !target.startsWith('../') && !target.startsWith('/')) {
- let isURL = false
- try {
- new URL(target)
- isURL = true
- } catch {
- // Continue regardless of error.
- }
- if (!isURL) {
- const exportTarget = pattern
- ? RegExpPrototypeSymbolReplace.call(
- patternRegEx,
- target,
- () => subpath
- )
- : target + subpath
- return packageResolve(exportTarget, packageJsonUrl, conditions)
- }
- }
- throw invalidPackageTarget(match, target, packageJsonUrl, internal, base)
- }
- if (invalidSegmentRegEx.exec(target.slice(2)) !== null) {
- if (deprecatedInvalidSegmentRegEx.exec(target.slice(2)) === null) {
- if (!isPathMap) {
- const request = pattern
- ? match.replace('*', () => subpath)
- : match + subpath
- const resolvedTarget = pattern
- ? RegExpPrototypeSymbolReplace.call(
- patternRegEx,
- target,
- () => subpath
- )
- : target
- emitInvalidSegmentDeprecation(
- resolvedTarget,
- request,
- match,
- packageJsonUrl,
- internal,
- base,
- true
- )
- }
- } else {
- throw invalidPackageTarget(match, target, packageJsonUrl, internal, base)
- }
- }
- const resolved = new URL(target, packageJsonUrl)
- const resolvedPath = resolved.pathname
- const packagePath = new URL('.', packageJsonUrl).pathname
- if (!resolvedPath.startsWith(packagePath))
- throw invalidPackageTarget(match, target, packageJsonUrl, internal, base)
- if (subpath === '') return resolved
- if (invalidSegmentRegEx.exec(subpath) !== null) {
- const request = pattern
- ? match.replace('*', () => subpath)
- : match + subpath
- if (deprecatedInvalidSegmentRegEx.exec(subpath) === null) {
- if (!isPathMap) {
- const resolvedTarget = pattern
- ? RegExpPrototypeSymbolReplace.call(
- patternRegEx,
- target,
- () => subpath
- )
- : target
- emitInvalidSegmentDeprecation(
- resolvedTarget,
- request,
- match,
- packageJsonUrl,
- internal,
- base,
- false
- )
- }
- } else {
- throwInvalidSubpath(request, match, packageJsonUrl, internal, base)
- }
- }
- if (pattern) {
- return new URL(
- RegExpPrototypeSymbolReplace.call(
- patternRegEx,
- resolved.href,
- () => subpath
- )
- )
- }
- return new URL(subpath, resolved)
- }
- /**
- * @param {string} key
- * @returns {boolean}
- */
- function isArrayIndex(key) {
- const keyNumber = Number(key)
- if (`${keyNumber}` !== key) return false
- return keyNumber >= 0 && keyNumber < 0xff_ff_ff_ff
- }
- /**
- * @param {URL} packageJsonUrl
- * @param {unknown} target
- * @param {string} subpath
- * @param {string} packageSubpath
- * @param {URL} base
- * @param {boolean} pattern
- * @param {boolean} internal
- * @param {boolean} isPathMap
- * @param {Set<string> | undefined} conditions
- * @returns {URL | null}
- */
- function resolvePackageTarget(
- packageJsonUrl,
- target,
- subpath,
- packageSubpath,
- base,
- pattern,
- internal,
- isPathMap,
- conditions
- ) {
- if (typeof target === 'string') {
- return resolvePackageTargetString(
- target,
- subpath,
- packageSubpath,
- packageJsonUrl,
- base,
- pattern,
- internal,
- isPathMap,
- conditions
- )
- }
- if (Array.isArray(target)) {
- /** @type {Array<unknown>} */
- const targetList = target
- if (targetList.length === 0) return null
- /** @type {ErrnoException | null | undefined} */
- let lastException
- let i = -1
- while (++i < targetList.length) {
- const targetItem = targetList[i]
- /** @type {URL | null} */
- let resolveResult
- try {
- resolveResult = resolvePackageTarget(
- packageJsonUrl,
- targetItem,
- subpath,
- packageSubpath,
- base,
- pattern,
- internal,
- isPathMap,
- conditions
- )
- } catch (error) {
- const exception = /** @type {ErrnoException} */ (error)
- lastException = exception
- if (exception.code === 'ERR_INVALID_PACKAGE_TARGET') continue
- throw error
- }
- if (resolveResult === undefined) continue
- if (resolveResult === null) {
- lastException = null
- continue
- }
- return resolveResult
- }
- if (lastException === undefined || lastException === null) {
- return null
- }
- throw lastException
- }
- if (typeof target === 'object' && target !== null) {
- const keys = Object.getOwnPropertyNames(target)
- let i = -1
- while (++i < keys.length) {
- const key = keys[i]
- if (isArrayIndex(key)) {
- throw new ERR_INVALID_PACKAGE_CONFIG(
- fileURLToPath(packageJsonUrl),
- base,
- '"exports" cannot contain numeric property keys.'
- )
- }
- }
- i = -1
- while (++i < keys.length) {
- const key = keys[i]
- if (key === 'default' || (conditions && conditions.has(key))) {
- // @ts-expect-error: indexable.
- const conditionalTarget = /** @type {unknown} */ (target[key])
- const resolveResult = resolvePackageTarget(
- packageJsonUrl,
- conditionalTarget,
- subpath,
- packageSubpath,
- base,
- pattern,
- internal,
- isPathMap,
- conditions
- )
- if (resolveResult === undefined) continue
- return resolveResult
- }
- }
- return null
- }
- if (target === null) {
- return null
- }
- throw invalidPackageTarget(
- packageSubpath,
- target,
- packageJsonUrl,
- internal,
- base
- )
- }
- /**
- * @param {unknown} exports
- * @param {URL} packageJsonUrl
- * @param {URL} base
- * @returns {boolean}
- */
- function isConditionalExportsMainSugar(exports, packageJsonUrl, base) {
- if (typeof exports === 'string' || Array.isArray(exports)) return true
- if (typeof exports !== 'object' || exports === null) return false
- const keys = Object.getOwnPropertyNames(exports)
- let isConditionalSugar = false
- let i = 0
- let keyIndex = -1
- while (++keyIndex < keys.length) {
- const key = keys[keyIndex]
- const currentIsConditionalSugar = key === '' || key[0] !== '.'
- if (i++ === 0) {
- isConditionalSugar = currentIsConditionalSugar
- } else if (isConditionalSugar !== currentIsConditionalSugar) {
- throw new ERR_INVALID_PACKAGE_CONFIG(
- fileURLToPath(packageJsonUrl),
- base,
- '"exports" cannot contain some keys starting with \'.\' and some not.' +
- ' The exports object must either be an object of package subpath keys' +
- ' or an object of main entry condition name keys only.'
- )
- }
- }
- return isConditionalSugar
- }
- /**
- * @param {string} match
- * @param {URL} pjsonUrl
- * @param {URL} base
- */
- function emitTrailingSlashPatternDeprecation(match, pjsonUrl, base) {
- // @ts-expect-error: apparently it does exist, TS.
- if (process.noDeprecation) {
- return
- }
- const pjsonPath = fileURLToPath(pjsonUrl)
- if (emittedPackageWarnings.has(pjsonPath + '|' + match)) return
- emittedPackageWarnings.add(pjsonPath + '|' + match)
- process.emitWarning(
- `Use of deprecated trailing slash pattern mapping "${match}" in the ` +
- `"exports" field module resolution of the package at ${pjsonPath}${
- base ? ` imported from ${fileURLToPath(base)}` : ''
- }. Mapping specifiers ending in "/" is no longer supported.`,
- 'DeprecationWarning',
- 'DEP0155'
- )
- }
- /**
- * @param {URL} packageJsonUrl
- * @param {string} packageSubpath
- * @param {Record<string, unknown>} packageConfig
- * @param {URL} base
- * @param {Set<string> | undefined} conditions
- * @returns {URL}
- */
- function packageExportsResolve(
- packageJsonUrl,
- packageSubpath,
- packageConfig,
- base,
- conditions
- ) {
- let exports = packageConfig.exports
- if (isConditionalExportsMainSugar(exports, packageJsonUrl, base)) {
- exports = {'.': exports}
- }
- if (
- own.call(exports, packageSubpath) &&
- !packageSubpath.includes('*') &&
- !packageSubpath.endsWith('/')
- ) {
- // @ts-expect-error: indexable.
- const target = exports[packageSubpath]
- const resolveResult = resolvePackageTarget(
- packageJsonUrl,
- target,
- '',
- packageSubpath,
- base,
- false,
- false,
- false,
- conditions
- )
- if (resolveResult === null || resolveResult === undefined) {
- throw exportsNotFound(packageSubpath, packageJsonUrl, base)
- }
- return resolveResult
- }
- let bestMatch = ''
- let bestMatchSubpath = ''
- const keys = Object.getOwnPropertyNames(exports)
- let i = -1
- while (++i < keys.length) {
- const key = keys[i]
- const patternIndex = key.indexOf('*')
- if (
- patternIndex !== -1 &&
- packageSubpath.startsWith(key.slice(0, patternIndex))
- ) {
- // When this reaches EOL, this can throw at the top of the whole function:
- //
- // if (StringPrototypeEndsWith(packageSubpath, '/'))
- // throwInvalidSubpath(packageSubpath)
- //
- // To match "imports" and the spec.
- if (packageSubpath.endsWith('/')) {
- emitTrailingSlashPatternDeprecation(
- packageSubpath,
- packageJsonUrl,
- base
- )
- }
- const patternTrailer = key.slice(patternIndex + 1)
- if (
- packageSubpath.length >= key.length &&
- packageSubpath.endsWith(patternTrailer) &&
- patternKeyCompare(bestMatch, key) === 1 &&
- key.lastIndexOf('*') === patternIndex
- ) {
- bestMatch = key
- bestMatchSubpath = packageSubpath.slice(
- patternIndex,
- packageSubpath.length - patternTrailer.length
- )
- }
- }
- }
- if (bestMatch) {
- // @ts-expect-error: indexable.
- const target = /** @type {unknown} */ (exports[bestMatch])
- const resolveResult = resolvePackageTarget(
- packageJsonUrl,
- target,
- bestMatchSubpath,
- bestMatch,
- base,
- true,
- false,
- packageSubpath.endsWith('/'),
- conditions
- )
- if (resolveResult === null || resolveResult === undefined) {
- throw exportsNotFound(packageSubpath, packageJsonUrl, base)
- }
- return resolveResult
- }
- throw exportsNotFound(packageSubpath, packageJsonUrl, base)
- }
- /**
- * @param {string} a
- * @param {string} b
- */
- function patternKeyCompare(a, b) {
- const aPatternIndex = a.indexOf('*')
- const bPatternIndex = b.indexOf('*')
- const baseLengthA = aPatternIndex === -1 ? a.length : aPatternIndex + 1
- const baseLengthB = bPatternIndex === -1 ? b.length : bPatternIndex + 1
- if (baseLengthA > baseLengthB) return -1
- if (baseLengthB > baseLengthA) return 1
- if (aPatternIndex === -1) return 1
- if (bPatternIndex === -1) return -1
- if (a.length > b.length) return -1
- if (b.length > a.length) return 1
- return 0
- }
- /**
- * @param {string} name
- * @param {URL} base
- * @param {Set<string>} [conditions]
- * @returns {URL}
- */
- function packageImportsResolve(name, base, conditions) {
- if (name === '#' || name.startsWith('#/') || name.endsWith('/')) {
- const reason = 'is not a valid internal imports specifier name'
- throw new ERR_INVALID_MODULE_SPECIFIER(name, reason, fileURLToPath(base))
- }
- /** @type {URL | undefined} */
- let packageJsonUrl
- const packageConfig = getPackageScopeConfig(base)
- if (packageConfig.exists) {
- packageJsonUrl = pathToFileURL(packageConfig.pjsonPath)
- const imports = packageConfig.imports
- if (imports) {
- if (own.call(imports, name) && !name.includes('*')) {
- const resolveResult = resolvePackageTarget(
- packageJsonUrl,
- imports[name],
- '',
- name,
- base,
- false,
- true,
- false,
- conditions
- )
- if (resolveResult !== null && resolveResult !== undefined) {
- return resolveResult
- }
- } else {
- let bestMatch = ''
- let bestMatchSubpath = ''
- const keys = Object.getOwnPropertyNames(imports)
- let i = -1
- while (++i < keys.length) {
- const key = keys[i]
- const patternIndex = key.indexOf('*')
- if (patternIndex !== -1 && name.startsWith(key.slice(0, -1))) {
- const patternTrailer = key.slice(patternIndex + 1)
- if (
- name.length >= key.length &&
- name.endsWith(patternTrailer) &&
- patternKeyCompare(bestMatch, key) === 1 &&
- key.lastIndexOf('*') === patternIndex
- ) {
- bestMatch = key
- bestMatchSubpath = name.slice(
- patternIndex,
- name.length - patternTrailer.length
- )
- }
- }
- }
- if (bestMatch) {
- const target = imports[bestMatch]
- const resolveResult = resolvePackageTarget(
- packageJsonUrl,
- target,
- bestMatchSubpath,
- bestMatch,
- base,
- true,
- true,
- false,
- conditions
- )
- if (resolveResult !== null && resolveResult !== undefined) {
- return resolveResult
- }
- }
- }
- }
- }
- throw importNotDefined(name, packageJsonUrl, base)
- }
- /**
- * @param {string} specifier
- * @param {URL} base
- */
- function parsePackageName(specifier, base) {
- let separatorIndex = specifier.indexOf('/')
- let validPackageName = true
- let isScoped = false
- if (specifier[0] === '@') {
- isScoped = true
- if (separatorIndex === -1 || specifier.length === 0) {
- validPackageName = false
- } else {
- separatorIndex = specifier.indexOf('/', separatorIndex + 1)
- }
- }
- const packageName =
- separatorIndex === -1 ? specifier : specifier.slice(0, separatorIndex)
- // Package name cannot have leading . and cannot have percent-encoding or
- // \\ separators.
- if (invalidPackageNameRegEx.exec(packageName) !== null) {
- validPackageName = false
- }
- if (!validPackageName) {
- throw new ERR_INVALID_MODULE_SPECIFIER(
- specifier,
- 'is not a valid package name',
- fileURLToPath(base)
- )
- }
- const packageSubpath =
- '.' + (separatorIndex === -1 ? '' : specifier.slice(separatorIndex))
- return {packageName, packageSubpath, isScoped}
- }
- /**
- * @param {string} specifier
- * @param {URL} base
- * @param {Set<string> | undefined} conditions
- * @returns {URL}
- */
- function packageResolve(specifier, base, conditions) {
- if (builtinModules.includes(specifier)) {
- return new URL('node:' + specifier)
- }
- const {packageName, packageSubpath, isScoped} = parsePackageName(
- specifier,
- base
- )
- // ResolveSelf
- const packageConfig = getPackageScopeConfig(base)
- // Can’t test.
- /* c8 ignore next 16 */
- if (packageConfig.exists) {
- const packageJsonUrl = pathToFileURL(packageConfig.pjsonPath)
- if (
- packageConfig.name === packageName &&
- packageConfig.exports !== undefined &&
- packageConfig.exports !== null
- ) {
- return packageExportsResolve(
- packageJsonUrl,
- packageSubpath,
- packageConfig,
- base,
- conditions
- )
- }
- }
- let packageJsonUrl = new URL(
- './node_modules/' + packageName + '/package.json',
- base
- )
- let packageJsonPath = fileURLToPath(packageJsonUrl)
- /** @type {string} */
- let lastPath
- do {
- const stat = tryStatSync(packageJsonPath.slice(0, -13))
- if (!stat || !stat.isDirectory()) {
- lastPath = packageJsonPath
- packageJsonUrl = new URL(
- (isScoped ? '../../../../node_modules/' : '../../../node_modules/') +
- packageName +
- '/package.json',
- packageJsonUrl
- )
- packageJsonPath = fileURLToPath(packageJsonUrl)
- continue
- }
- // Package match.
- const packageConfig = read(packageJsonPath, {base, specifier})
- if (packageConfig.exports !== undefined && packageConfig.exports !== null) {
- return packageExportsResolve(
- packageJsonUrl,
- packageSubpath,
- packageConfig,
- base,
- conditions
- )
- }
- if (packageSubpath === '.') {
- return legacyMainResolve(packageJsonUrl, packageConfig, base)
- }
- return new URL(packageSubpath, packageJsonUrl)
- // Cross-platform root check.
- } while (packageJsonPath.length !== lastPath.length)
- throw new ERR_MODULE_NOT_FOUND(packageName, fileURLToPath(base), false)
- }
- /**
- * @param {string} specifier
- * @returns {boolean}
- */
- function isRelativeSpecifier(specifier) {
- if (specifier[0] === '.') {
- if (specifier.length === 1 || specifier[1] === '/') return true
- if (
- specifier[1] === '.' &&
- (specifier.length === 2 || specifier[2] === '/')
- ) {
- return true
- }
- }
- return false
- }
- /**
- * @param {string} specifier
- * @returns {boolean}
- */
- function shouldBeTreatedAsRelativeOrAbsolutePath(specifier) {
- if (specifier === '') return false
- if (specifier[0] === '/') return true
- return isRelativeSpecifier(specifier)
- }
- /**
- * The “Resolver Algorithm Specification” as detailed in the Node docs (which is
- * sync and slightly lower-level than `resolve`).
- *
- * @param {string} specifier
- * `/example.js`, `./example.js`, `../example.js`, `some-package`, `fs`, etc.
- * @param {URL} base
- * Full URL (to a file) that `specifier` is resolved relative from.
- * @param {Set<string>} [conditions]
- * Conditions.
- * @param {boolean} [preserveSymlinks]
- * Keep symlinks instead of resolving them.
- * @returns {URL}
- * A URL object to the found thing.
- */
- export function moduleResolve(specifier, base, conditions, preserveSymlinks) {
- // Note: The Node code supports `base` as a string (in this internal API) too,
- // we don’t.
- if (conditions === undefined) {
- conditions = getConditionsSet()
- }
- const protocol = base.protocol
- const isData = protocol === 'data:'
- const isRemote = isData || protocol === 'http:' || protocol === 'https:'
- // Order swapped from spec for minor perf gain.
- // Ok since relative URLs cannot parse as URLs.
- /** @type {URL | undefined} */
- let resolved
- if (shouldBeTreatedAsRelativeOrAbsolutePath(specifier)) {
- try {
- resolved = new URL(specifier, base)
- } catch (error_) {
- const error = new ERR_UNSUPPORTED_RESOLVE_REQUEST(specifier, base)
- error.cause = error_
- throw error
- }
- } else if (protocol === 'file:' && specifier[0] === '#') {
- resolved = packageImportsResolve(specifier, base, conditions)
- } else {
- try {
- resolved = new URL(specifier)
- } catch (error_) {
- // Note: actual code uses `canBeRequiredWithoutScheme`.
- if (isRemote && !builtinModules.includes(specifier)) {
- const error = new ERR_UNSUPPORTED_RESOLVE_REQUEST(specifier, base)
- error.cause = error_
- throw error
- }
- resolved = packageResolve(specifier, base, conditions)
- }
- }
- assert.ok(resolved !== undefined, 'expected to be defined')
- if (resolved.protocol !== 'file:') {
- return resolved
- }
- return finalizeResolution(resolved, base, preserveSymlinks)
- }
- /**
- * @param {string} specifier
- * @param {URL | undefined} parsed
- * @param {URL | undefined} parsedParentURL
- */
- function checkIfDisallowedImport(specifier, parsed, parsedParentURL) {
- if (parsedParentURL) {
- // Avoid accessing the `protocol` property due to the lazy getters.
- const parentProtocol = parsedParentURL.protocol
- if (parentProtocol === 'http:' || parentProtocol === 'https:') {
- if (shouldBeTreatedAsRelativeOrAbsolutePath(specifier)) {
- // Avoid accessing the `protocol` property due to the lazy getters.
- const parsedProtocol = parsed?.protocol
- // `data:` and `blob:` disallowed due to allowing file: access via
- // indirection
- if (
- parsedProtocol &&
- parsedProtocol !== 'https:' &&
- parsedProtocol !== 'http:'
- ) {
- throw new ERR_NETWORK_IMPORT_DISALLOWED(
- specifier,
- parsedParentURL,
- 'remote imports cannot import from a local location.'
- )
- }
- return {url: parsed?.href || ''}
- }
- if (builtinModules.includes(specifier)) {
- throw new ERR_NETWORK_IMPORT_DISALLOWED(
- specifier,
- parsedParentURL,
- 'remote imports cannot import from a local location.'
- )
- }
- throw new ERR_NETWORK_IMPORT_DISALLOWED(
- specifier,
- parsedParentURL,
- 'only relative and absolute specifiers are supported.'
- )
- }
- }
- }
- // Note: this is from:
- // <https://github.com/nodejs/node/blob/3e74590/lib/internal/url.js#L687>
- /**
- * Checks if a value has the shape of a WHATWG URL object.
- *
- * Using a symbol or instanceof would not be able to recognize URL objects
- * coming from other implementations (e.g. in Electron), so instead we are
- * checking some well known properties for a lack of a better test.
- *
- * We use `href` and `protocol` as they are the only properties that are
- * easy to retrieve and calculate due to the lazy nature of the getters.
- *
- * @template {unknown} Value
- * @param {Value} self
- * @returns {Value is URL}
- */
- function isURL(self) {
- return Boolean(
- self &&
- typeof self === 'object' &&
- 'href' in self &&
- typeof self.href === 'string' &&
- 'protocol' in self &&
- typeof self.protocol === 'string' &&
- self.href &&
- self.protocol
- )
- }
- /**
- * Validate user-input in `context` supplied by a custom loader.
- *
- * @param {unknown} parentURL
- * @returns {asserts parentURL is URL | string | undefined}
- */
- function throwIfInvalidParentURL(parentURL) {
- if (parentURL === undefined) {
- return // Main entry point, so no parent
- }
- if (typeof parentURL !== 'string' && !isURL(parentURL)) {
- throw new codes.ERR_INVALID_ARG_TYPE(
- 'parentURL',
- ['string', 'URL'],
- parentURL
- )
- }
- }
- /**
- * @param {string} specifier
- * @param {{parentURL?: string, conditions?: Array<string>}} context
- * @returns {{url: string, format?: string | null}}
- */
- export function defaultResolve(specifier, context = {}) {
- const {parentURL} = context
- assert.ok(parentURL !== undefined, 'expected `parentURL` to be defined')
- throwIfInvalidParentURL(parentURL)
- /** @type {URL | undefined} */
- let parsedParentURL
- if (parentURL) {
- try {
- parsedParentURL = new URL(parentURL)
- } catch {
- // Ignore exception
- }
- }
- /** @type {URL | undefined} */
- let parsed
- /** @type {string | undefined} */
- let protocol
- try {
- parsed = shouldBeTreatedAsRelativeOrAbsolutePath(specifier)
- ? new URL(specifier, parsedParentURL)
- : new URL(specifier)
- // Avoid accessing the `protocol` property due to the lazy getters.
- protocol = parsed.protocol
- if (protocol === 'data:') {
- return {url: parsed.href, format: null}
- }
- } catch {
- // Ignore exception
- }
- // There are multiple deep branches that can either throw or return; instead
- // of duplicating that deeply nested logic for the possible returns, DRY and
- // check for a return. This seems the least gnarly.
- const maybeReturn = checkIfDisallowedImport(
- specifier,
- parsed,
- parsedParentURL
- )
- if (maybeReturn) return maybeReturn
- // This must come after checkIfDisallowedImport
- if (protocol === undefined && parsed) {
- protocol = parsed.protocol
- }
- if (protocol === 'node:') {
- return {url: specifier}
- }
- // This must come after checkIfDisallowedImport
- if (parsed && parsed.protocol === 'node:') return {url: specifier}
- const conditions = getConditionsSet(context.conditions)
- const url = moduleResolve(specifier, new URL(parentURL), conditions, false)
- return {
- // Do NOT cast `url` to a string: that will work even when there are real
- // problems, silencing them
- url: url.href,
- format: defaultGetFormatWithoutErrors(url, {parentURL})
- }
- }
|