resolve.js 34 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301
  1. // Manually “tree shaken” from:
  2. // <https://github.com/nodejs/node/blob/81a9a97/lib/internal/modules/esm/resolve.js>
  3. // Last checked on: Apr 29, 2024.
  4. /**
  5. * @import {Stats} from 'node:fs'
  6. * @import {ErrnoException} from './errors.js'
  7. * @import {PackageConfig} from './package-json-reader.js'
  8. */
  9. import assert from 'node:assert'
  10. import {statSync, realpathSync} from 'node:fs'
  11. import process from 'node:process'
  12. import {fileURLToPath, pathToFileURL} from 'node:url'
  13. import path from 'node:path'
  14. import {builtinModules} from 'node:module'
  15. import {defaultGetFormatWithoutErrors} from './get-format.js'
  16. import {codes} from './errors.js'
  17. import {getPackageScopeConfig, read} from './package-json-reader.js'
  18. import {getConditionsSet} from './utils.js'
  19. const RegExpPrototypeSymbolReplace = RegExp.prototype[Symbol.replace]
  20. const {
  21. ERR_NETWORK_IMPORT_DISALLOWED,
  22. ERR_INVALID_MODULE_SPECIFIER,
  23. ERR_INVALID_PACKAGE_CONFIG,
  24. ERR_INVALID_PACKAGE_TARGET,
  25. ERR_MODULE_NOT_FOUND,
  26. ERR_PACKAGE_IMPORT_NOT_DEFINED,
  27. ERR_PACKAGE_PATH_NOT_EXPORTED,
  28. ERR_UNSUPPORTED_DIR_IMPORT,
  29. ERR_UNSUPPORTED_RESOLVE_REQUEST
  30. } = codes
  31. const own = {}.hasOwnProperty
  32. const invalidSegmentRegEx =
  33. /(^|\\|\/)((\.|%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
  34. const deprecatedInvalidSegmentRegEx =
  35. /(^|\\|\/)((\.|%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
  36. const invalidPackageNameRegEx = /^\.|%|\\/
  37. const patternRegEx = /\*/g
  38. const encodedSeparatorRegEx = /%2f|%5c/i
  39. /** @type {Set<string>} */
  40. const emittedPackageWarnings = new Set()
  41. const doubleSlashRegEx = /[/\\]{2}/
  42. /**
  43. *
  44. * @param {string} target
  45. * @param {string} request
  46. * @param {string} match
  47. * @param {URL} packageJsonUrl
  48. * @param {boolean} internal
  49. * @param {URL} base
  50. * @param {boolean} isTarget
  51. */
  52. function emitInvalidSegmentDeprecation(
  53. target,
  54. request,
  55. match,
  56. packageJsonUrl,
  57. internal,
  58. base,
  59. isTarget
  60. ) {
  61. // @ts-expect-error: apparently it does exist, TS.
  62. if (process.noDeprecation) {
  63. return
  64. }
  65. const pjsonPath = fileURLToPath(packageJsonUrl)
  66. const double = doubleSlashRegEx.exec(isTarget ? target : request) !== null
  67. process.emitWarning(
  68. `Use of deprecated ${
  69. double ? 'double slash' : 'leading or trailing slash matching'
  70. } resolving "${target}" for module ` +
  71. `request "${request}" ${
  72. request === match ? '' : `matched to "${match}" `
  73. }in the "${
  74. internal ? 'imports' : 'exports'
  75. }" field module resolution of the package at ${pjsonPath}${
  76. base ? ` imported from ${fileURLToPath(base)}` : ''
  77. }.`,
  78. 'DeprecationWarning',
  79. 'DEP0166'
  80. )
  81. }
  82. /**
  83. * @param {URL} url
  84. * @param {URL} packageJsonUrl
  85. * @param {URL} base
  86. * @param {string} [main]
  87. * @returns {void}
  88. */
  89. function emitLegacyIndexDeprecation(url, packageJsonUrl, base, main) {
  90. // @ts-expect-error: apparently it does exist, TS.
  91. if (process.noDeprecation) {
  92. return
  93. }
  94. const format = defaultGetFormatWithoutErrors(url, {parentURL: base.href})
  95. if (format !== 'module') return
  96. const urlPath = fileURLToPath(url.href)
  97. const packagePath = fileURLToPath(new URL('.', packageJsonUrl))
  98. const basePath = fileURLToPath(base)
  99. if (!main) {
  100. process.emitWarning(
  101. `No "main" or "exports" field defined in the package.json for ${packagePath} resolving the main entry point "${urlPath.slice(
  102. packagePath.length
  103. )}", imported from ${basePath}.\nDefault "index" lookups for the main are deprecated for ES modules.`,
  104. 'DeprecationWarning',
  105. 'DEP0151'
  106. )
  107. } else if (path.resolve(packagePath, main) !== urlPath) {
  108. process.emitWarning(
  109. `Package ${packagePath} has a "main" field set to "${main}", ` +
  110. `excluding the full filename and extension to the resolved file at "${urlPath.slice(
  111. packagePath.length
  112. )}", imported from ${basePath}.\n Automatic extension resolution of the "main" field is ` +
  113. 'deprecated for ES modules.',
  114. 'DeprecationWarning',
  115. 'DEP0151'
  116. )
  117. }
  118. }
  119. /**
  120. * @param {string} path
  121. * @returns {Stats | undefined}
  122. */
  123. function tryStatSync(path) {
  124. // Note: from Node 15 onwards we can use `throwIfNoEntry: false` instead.
  125. try {
  126. return statSync(path)
  127. } catch {
  128. // Note: in Node code this returns `new Stats`,
  129. // but in Node 22 that’s marked as a deprecated internal API.
  130. // Which, well, we kinda are, but still to prevent that warning,
  131. // just yield `undefined`.
  132. }
  133. }
  134. /**
  135. * Legacy CommonJS main resolution:
  136. * 1. let M = pkg_url + (json main field)
  137. * 2. TRY(M, M.js, M.json, M.node)
  138. * 3. TRY(M/index.js, M/index.json, M/index.node)
  139. * 4. TRY(pkg_url/index.js, pkg_url/index.json, pkg_url/index.node)
  140. * 5. NOT_FOUND
  141. *
  142. * @param {URL} url
  143. * @returns {boolean}
  144. */
  145. function fileExists(url) {
  146. const stats = statSync(url, {throwIfNoEntry: false})
  147. const isFile = stats ? stats.isFile() : undefined
  148. return isFile === null || isFile === undefined ? false : isFile
  149. }
  150. /**
  151. * @param {URL} packageJsonUrl
  152. * @param {PackageConfig} packageConfig
  153. * @param {URL} base
  154. * @returns {URL}
  155. */
  156. function legacyMainResolve(packageJsonUrl, packageConfig, base) {
  157. /** @type {URL | undefined} */
  158. let guess
  159. if (packageConfig.main !== undefined) {
  160. guess = new URL(packageConfig.main, packageJsonUrl)
  161. // Note: fs check redundances will be handled by Descriptor cache here.
  162. if (fileExists(guess)) return guess
  163. const tries = [
  164. `./${packageConfig.main}.js`,
  165. `./${packageConfig.main}.json`,
  166. `./${packageConfig.main}.node`,
  167. `./${packageConfig.main}/index.js`,
  168. `./${packageConfig.main}/index.json`,
  169. `./${packageConfig.main}/index.node`
  170. ]
  171. let i = -1
  172. while (++i < tries.length) {
  173. guess = new URL(tries[i], packageJsonUrl)
  174. if (fileExists(guess)) break
  175. guess = undefined
  176. }
  177. if (guess) {
  178. emitLegacyIndexDeprecation(
  179. guess,
  180. packageJsonUrl,
  181. base,
  182. packageConfig.main
  183. )
  184. return guess
  185. }
  186. // Fallthrough.
  187. }
  188. const tries = ['./index.js', './index.json', './index.node']
  189. let i = -1
  190. while (++i < tries.length) {
  191. guess = new URL(tries[i], packageJsonUrl)
  192. if (fileExists(guess)) break
  193. guess = undefined
  194. }
  195. if (guess) {
  196. emitLegacyIndexDeprecation(guess, packageJsonUrl, base, packageConfig.main)
  197. return guess
  198. }
  199. // Not found.
  200. throw new ERR_MODULE_NOT_FOUND(
  201. fileURLToPath(new URL('.', packageJsonUrl)),
  202. fileURLToPath(base)
  203. )
  204. }
  205. /**
  206. * @param {URL} resolved
  207. * @param {URL} base
  208. * @param {boolean} [preserveSymlinks]
  209. * @returns {URL}
  210. */
  211. function finalizeResolution(resolved, base, preserveSymlinks) {
  212. if (encodedSeparatorRegEx.exec(resolved.pathname) !== null) {
  213. throw new ERR_INVALID_MODULE_SPECIFIER(
  214. resolved.pathname,
  215. 'must not include encoded "/" or "\\" characters',
  216. fileURLToPath(base)
  217. )
  218. }
  219. /** @type {string} */
  220. let filePath
  221. try {
  222. filePath = fileURLToPath(resolved)
  223. } catch (error) {
  224. const cause = /** @type {ErrnoException} */ (error)
  225. Object.defineProperty(cause, 'input', {value: String(resolved)})
  226. Object.defineProperty(cause, 'module', {value: String(base)})
  227. throw cause
  228. }
  229. const stats = tryStatSync(
  230. filePath.endsWith('/') ? filePath.slice(-1) : filePath
  231. )
  232. if (stats && stats.isDirectory()) {
  233. const error = new ERR_UNSUPPORTED_DIR_IMPORT(filePath, fileURLToPath(base))
  234. // @ts-expect-error Add this for `import.meta.resolve`.
  235. error.url = String(resolved)
  236. throw error
  237. }
  238. if (!stats || !stats.isFile()) {
  239. const error = new ERR_MODULE_NOT_FOUND(
  240. filePath || resolved.pathname,
  241. base && fileURLToPath(base),
  242. true
  243. )
  244. // @ts-expect-error Add this for `import.meta.resolve`.
  245. error.url = String(resolved)
  246. throw error
  247. }
  248. if (!preserveSymlinks) {
  249. const real = realpathSync(filePath)
  250. const {search, hash} = resolved
  251. resolved = pathToFileURL(real + (filePath.endsWith(path.sep) ? '/' : ''))
  252. resolved.search = search
  253. resolved.hash = hash
  254. }
  255. return resolved
  256. }
  257. /**
  258. * @param {string} specifier
  259. * @param {URL | undefined} packageJsonUrl
  260. * @param {URL} base
  261. * @returns {Error}
  262. */
  263. function importNotDefined(specifier, packageJsonUrl, base) {
  264. return new ERR_PACKAGE_IMPORT_NOT_DEFINED(
  265. specifier,
  266. packageJsonUrl && fileURLToPath(new URL('.', packageJsonUrl)),
  267. fileURLToPath(base)
  268. )
  269. }
  270. /**
  271. * @param {string} subpath
  272. * @param {URL} packageJsonUrl
  273. * @param {URL} base
  274. * @returns {Error}
  275. */
  276. function exportsNotFound(subpath, packageJsonUrl, base) {
  277. return new ERR_PACKAGE_PATH_NOT_EXPORTED(
  278. fileURLToPath(new URL('.', packageJsonUrl)),
  279. subpath,
  280. base && fileURLToPath(base)
  281. )
  282. }
  283. /**
  284. * @param {string} request
  285. * @param {string} match
  286. * @param {URL} packageJsonUrl
  287. * @param {boolean} internal
  288. * @param {URL} [base]
  289. * @returns {never}
  290. */
  291. function throwInvalidSubpath(request, match, packageJsonUrl, internal, base) {
  292. const reason = `request is not a valid match in pattern "${match}" for the "${
  293. internal ? 'imports' : 'exports'
  294. }" resolution of ${fileURLToPath(packageJsonUrl)}`
  295. throw new ERR_INVALID_MODULE_SPECIFIER(
  296. request,
  297. reason,
  298. base && fileURLToPath(base)
  299. )
  300. }
  301. /**
  302. * @param {string} subpath
  303. * @param {unknown} target
  304. * @param {URL} packageJsonUrl
  305. * @param {boolean} internal
  306. * @param {URL} [base]
  307. * @returns {Error}
  308. */
  309. function invalidPackageTarget(subpath, target, packageJsonUrl, internal, base) {
  310. target =
  311. typeof target === 'object' && target !== null
  312. ? JSON.stringify(target, null, '')
  313. : `${target}`
  314. return new ERR_INVALID_PACKAGE_TARGET(
  315. fileURLToPath(new URL('.', packageJsonUrl)),
  316. subpath,
  317. target,
  318. internal,
  319. base && fileURLToPath(base)
  320. )
  321. }
  322. /**
  323. * @param {string} target
  324. * @param {string} subpath
  325. * @param {string} match
  326. * @param {URL} packageJsonUrl
  327. * @param {URL} base
  328. * @param {boolean} pattern
  329. * @param {boolean} internal
  330. * @param {boolean} isPathMap
  331. * @param {Set<string> | undefined} conditions
  332. * @returns {URL}
  333. */
  334. function resolvePackageTargetString(
  335. target,
  336. subpath,
  337. match,
  338. packageJsonUrl,
  339. base,
  340. pattern,
  341. internal,
  342. isPathMap,
  343. conditions
  344. ) {
  345. if (subpath !== '' && !pattern && target[target.length - 1] !== '/')
  346. throw invalidPackageTarget(match, target, packageJsonUrl, internal, base)
  347. if (!target.startsWith('./')) {
  348. if (internal && !target.startsWith('../') && !target.startsWith('/')) {
  349. let isURL = false
  350. try {
  351. new URL(target)
  352. isURL = true
  353. } catch {
  354. // Continue regardless of error.
  355. }
  356. if (!isURL) {
  357. const exportTarget = pattern
  358. ? RegExpPrototypeSymbolReplace.call(
  359. patternRegEx,
  360. target,
  361. () => subpath
  362. )
  363. : target + subpath
  364. return packageResolve(exportTarget, packageJsonUrl, conditions)
  365. }
  366. }
  367. throw invalidPackageTarget(match, target, packageJsonUrl, internal, base)
  368. }
  369. if (invalidSegmentRegEx.exec(target.slice(2)) !== null) {
  370. if (deprecatedInvalidSegmentRegEx.exec(target.slice(2)) === null) {
  371. if (!isPathMap) {
  372. const request = pattern
  373. ? match.replace('*', () => subpath)
  374. : match + subpath
  375. const resolvedTarget = pattern
  376. ? RegExpPrototypeSymbolReplace.call(
  377. patternRegEx,
  378. target,
  379. () => subpath
  380. )
  381. : target
  382. emitInvalidSegmentDeprecation(
  383. resolvedTarget,
  384. request,
  385. match,
  386. packageJsonUrl,
  387. internal,
  388. base,
  389. true
  390. )
  391. }
  392. } else {
  393. throw invalidPackageTarget(match, target, packageJsonUrl, internal, base)
  394. }
  395. }
  396. const resolved = new URL(target, packageJsonUrl)
  397. const resolvedPath = resolved.pathname
  398. const packagePath = new URL('.', packageJsonUrl).pathname
  399. if (!resolvedPath.startsWith(packagePath))
  400. throw invalidPackageTarget(match, target, packageJsonUrl, internal, base)
  401. if (subpath === '') return resolved
  402. if (invalidSegmentRegEx.exec(subpath) !== null) {
  403. const request = pattern
  404. ? match.replace('*', () => subpath)
  405. : match + subpath
  406. if (deprecatedInvalidSegmentRegEx.exec(subpath) === null) {
  407. if (!isPathMap) {
  408. const resolvedTarget = pattern
  409. ? RegExpPrototypeSymbolReplace.call(
  410. patternRegEx,
  411. target,
  412. () => subpath
  413. )
  414. : target
  415. emitInvalidSegmentDeprecation(
  416. resolvedTarget,
  417. request,
  418. match,
  419. packageJsonUrl,
  420. internal,
  421. base,
  422. false
  423. )
  424. }
  425. } else {
  426. throwInvalidSubpath(request, match, packageJsonUrl, internal, base)
  427. }
  428. }
  429. if (pattern) {
  430. return new URL(
  431. RegExpPrototypeSymbolReplace.call(
  432. patternRegEx,
  433. resolved.href,
  434. () => subpath
  435. )
  436. )
  437. }
  438. return new URL(subpath, resolved)
  439. }
  440. /**
  441. * @param {string} key
  442. * @returns {boolean}
  443. */
  444. function isArrayIndex(key) {
  445. const keyNumber = Number(key)
  446. if (`${keyNumber}` !== key) return false
  447. return keyNumber >= 0 && keyNumber < 0xff_ff_ff_ff
  448. }
  449. /**
  450. * @param {URL} packageJsonUrl
  451. * @param {unknown} target
  452. * @param {string} subpath
  453. * @param {string} packageSubpath
  454. * @param {URL} base
  455. * @param {boolean} pattern
  456. * @param {boolean} internal
  457. * @param {boolean} isPathMap
  458. * @param {Set<string> | undefined} conditions
  459. * @returns {URL | null}
  460. */
  461. function resolvePackageTarget(
  462. packageJsonUrl,
  463. target,
  464. subpath,
  465. packageSubpath,
  466. base,
  467. pattern,
  468. internal,
  469. isPathMap,
  470. conditions
  471. ) {
  472. if (typeof target === 'string') {
  473. return resolvePackageTargetString(
  474. target,
  475. subpath,
  476. packageSubpath,
  477. packageJsonUrl,
  478. base,
  479. pattern,
  480. internal,
  481. isPathMap,
  482. conditions
  483. )
  484. }
  485. if (Array.isArray(target)) {
  486. /** @type {Array<unknown>} */
  487. const targetList = target
  488. if (targetList.length === 0) return null
  489. /** @type {ErrnoException | null | undefined} */
  490. let lastException
  491. let i = -1
  492. while (++i < targetList.length) {
  493. const targetItem = targetList[i]
  494. /** @type {URL | null} */
  495. let resolveResult
  496. try {
  497. resolveResult = resolvePackageTarget(
  498. packageJsonUrl,
  499. targetItem,
  500. subpath,
  501. packageSubpath,
  502. base,
  503. pattern,
  504. internal,
  505. isPathMap,
  506. conditions
  507. )
  508. } catch (error) {
  509. const exception = /** @type {ErrnoException} */ (error)
  510. lastException = exception
  511. if (exception.code === 'ERR_INVALID_PACKAGE_TARGET') continue
  512. throw error
  513. }
  514. if (resolveResult === undefined) continue
  515. if (resolveResult === null) {
  516. lastException = null
  517. continue
  518. }
  519. return resolveResult
  520. }
  521. if (lastException === undefined || lastException === null) {
  522. return null
  523. }
  524. throw lastException
  525. }
  526. if (typeof target === 'object' && target !== null) {
  527. const keys = Object.getOwnPropertyNames(target)
  528. let i = -1
  529. while (++i < keys.length) {
  530. const key = keys[i]
  531. if (isArrayIndex(key)) {
  532. throw new ERR_INVALID_PACKAGE_CONFIG(
  533. fileURLToPath(packageJsonUrl),
  534. base,
  535. '"exports" cannot contain numeric property keys.'
  536. )
  537. }
  538. }
  539. i = -1
  540. while (++i < keys.length) {
  541. const key = keys[i]
  542. if (key === 'default' || (conditions && conditions.has(key))) {
  543. // @ts-expect-error: indexable.
  544. const conditionalTarget = /** @type {unknown} */ (target[key])
  545. const resolveResult = resolvePackageTarget(
  546. packageJsonUrl,
  547. conditionalTarget,
  548. subpath,
  549. packageSubpath,
  550. base,
  551. pattern,
  552. internal,
  553. isPathMap,
  554. conditions
  555. )
  556. if (resolveResult === undefined) continue
  557. return resolveResult
  558. }
  559. }
  560. return null
  561. }
  562. if (target === null) {
  563. return null
  564. }
  565. throw invalidPackageTarget(
  566. packageSubpath,
  567. target,
  568. packageJsonUrl,
  569. internal,
  570. base
  571. )
  572. }
  573. /**
  574. * @param {unknown} exports
  575. * @param {URL} packageJsonUrl
  576. * @param {URL} base
  577. * @returns {boolean}
  578. */
  579. function isConditionalExportsMainSugar(exports, packageJsonUrl, base) {
  580. if (typeof exports === 'string' || Array.isArray(exports)) return true
  581. if (typeof exports !== 'object' || exports === null) return false
  582. const keys = Object.getOwnPropertyNames(exports)
  583. let isConditionalSugar = false
  584. let i = 0
  585. let keyIndex = -1
  586. while (++keyIndex < keys.length) {
  587. const key = keys[keyIndex]
  588. const currentIsConditionalSugar = key === '' || key[0] !== '.'
  589. if (i++ === 0) {
  590. isConditionalSugar = currentIsConditionalSugar
  591. } else if (isConditionalSugar !== currentIsConditionalSugar) {
  592. throw new ERR_INVALID_PACKAGE_CONFIG(
  593. fileURLToPath(packageJsonUrl),
  594. base,
  595. '"exports" cannot contain some keys starting with \'.\' and some not.' +
  596. ' The exports object must either be an object of package subpath keys' +
  597. ' or an object of main entry condition name keys only.'
  598. )
  599. }
  600. }
  601. return isConditionalSugar
  602. }
  603. /**
  604. * @param {string} match
  605. * @param {URL} pjsonUrl
  606. * @param {URL} base
  607. */
  608. function emitTrailingSlashPatternDeprecation(match, pjsonUrl, base) {
  609. // @ts-expect-error: apparently it does exist, TS.
  610. if (process.noDeprecation) {
  611. return
  612. }
  613. const pjsonPath = fileURLToPath(pjsonUrl)
  614. if (emittedPackageWarnings.has(pjsonPath + '|' + match)) return
  615. emittedPackageWarnings.add(pjsonPath + '|' + match)
  616. process.emitWarning(
  617. `Use of deprecated trailing slash pattern mapping "${match}" in the ` +
  618. `"exports" field module resolution of the package at ${pjsonPath}${
  619. base ? ` imported from ${fileURLToPath(base)}` : ''
  620. }. Mapping specifiers ending in "/" is no longer supported.`,
  621. 'DeprecationWarning',
  622. 'DEP0155'
  623. )
  624. }
  625. /**
  626. * @param {URL} packageJsonUrl
  627. * @param {string} packageSubpath
  628. * @param {Record<string, unknown>} packageConfig
  629. * @param {URL} base
  630. * @param {Set<string> | undefined} conditions
  631. * @returns {URL}
  632. */
  633. function packageExportsResolve(
  634. packageJsonUrl,
  635. packageSubpath,
  636. packageConfig,
  637. base,
  638. conditions
  639. ) {
  640. let exports = packageConfig.exports
  641. if (isConditionalExportsMainSugar(exports, packageJsonUrl, base)) {
  642. exports = {'.': exports}
  643. }
  644. if (
  645. own.call(exports, packageSubpath) &&
  646. !packageSubpath.includes('*') &&
  647. !packageSubpath.endsWith('/')
  648. ) {
  649. // @ts-expect-error: indexable.
  650. const target = exports[packageSubpath]
  651. const resolveResult = resolvePackageTarget(
  652. packageJsonUrl,
  653. target,
  654. '',
  655. packageSubpath,
  656. base,
  657. false,
  658. false,
  659. false,
  660. conditions
  661. )
  662. if (resolveResult === null || resolveResult === undefined) {
  663. throw exportsNotFound(packageSubpath, packageJsonUrl, base)
  664. }
  665. return resolveResult
  666. }
  667. let bestMatch = ''
  668. let bestMatchSubpath = ''
  669. const keys = Object.getOwnPropertyNames(exports)
  670. let i = -1
  671. while (++i < keys.length) {
  672. const key = keys[i]
  673. const patternIndex = key.indexOf('*')
  674. if (
  675. patternIndex !== -1 &&
  676. packageSubpath.startsWith(key.slice(0, patternIndex))
  677. ) {
  678. // When this reaches EOL, this can throw at the top of the whole function:
  679. //
  680. // if (StringPrototypeEndsWith(packageSubpath, '/'))
  681. // throwInvalidSubpath(packageSubpath)
  682. //
  683. // To match "imports" and the spec.
  684. if (packageSubpath.endsWith('/')) {
  685. emitTrailingSlashPatternDeprecation(
  686. packageSubpath,
  687. packageJsonUrl,
  688. base
  689. )
  690. }
  691. const patternTrailer = key.slice(patternIndex + 1)
  692. if (
  693. packageSubpath.length >= key.length &&
  694. packageSubpath.endsWith(patternTrailer) &&
  695. patternKeyCompare(bestMatch, key) === 1 &&
  696. key.lastIndexOf('*') === patternIndex
  697. ) {
  698. bestMatch = key
  699. bestMatchSubpath = packageSubpath.slice(
  700. patternIndex,
  701. packageSubpath.length - patternTrailer.length
  702. )
  703. }
  704. }
  705. }
  706. if (bestMatch) {
  707. // @ts-expect-error: indexable.
  708. const target = /** @type {unknown} */ (exports[bestMatch])
  709. const resolveResult = resolvePackageTarget(
  710. packageJsonUrl,
  711. target,
  712. bestMatchSubpath,
  713. bestMatch,
  714. base,
  715. true,
  716. false,
  717. packageSubpath.endsWith('/'),
  718. conditions
  719. )
  720. if (resolveResult === null || resolveResult === undefined) {
  721. throw exportsNotFound(packageSubpath, packageJsonUrl, base)
  722. }
  723. return resolveResult
  724. }
  725. throw exportsNotFound(packageSubpath, packageJsonUrl, base)
  726. }
  727. /**
  728. * @param {string} a
  729. * @param {string} b
  730. */
  731. function patternKeyCompare(a, b) {
  732. const aPatternIndex = a.indexOf('*')
  733. const bPatternIndex = b.indexOf('*')
  734. const baseLengthA = aPatternIndex === -1 ? a.length : aPatternIndex + 1
  735. const baseLengthB = bPatternIndex === -1 ? b.length : bPatternIndex + 1
  736. if (baseLengthA > baseLengthB) return -1
  737. if (baseLengthB > baseLengthA) return 1
  738. if (aPatternIndex === -1) return 1
  739. if (bPatternIndex === -1) return -1
  740. if (a.length > b.length) return -1
  741. if (b.length > a.length) return 1
  742. return 0
  743. }
  744. /**
  745. * @param {string} name
  746. * @param {URL} base
  747. * @param {Set<string>} [conditions]
  748. * @returns {URL}
  749. */
  750. function packageImportsResolve(name, base, conditions) {
  751. if (name === '#' || name.startsWith('#/') || name.endsWith('/')) {
  752. const reason = 'is not a valid internal imports specifier name'
  753. throw new ERR_INVALID_MODULE_SPECIFIER(name, reason, fileURLToPath(base))
  754. }
  755. /** @type {URL | undefined} */
  756. let packageJsonUrl
  757. const packageConfig = getPackageScopeConfig(base)
  758. if (packageConfig.exists) {
  759. packageJsonUrl = pathToFileURL(packageConfig.pjsonPath)
  760. const imports = packageConfig.imports
  761. if (imports) {
  762. if (own.call(imports, name) && !name.includes('*')) {
  763. const resolveResult = resolvePackageTarget(
  764. packageJsonUrl,
  765. imports[name],
  766. '',
  767. name,
  768. base,
  769. false,
  770. true,
  771. false,
  772. conditions
  773. )
  774. if (resolveResult !== null && resolveResult !== undefined) {
  775. return resolveResult
  776. }
  777. } else {
  778. let bestMatch = ''
  779. let bestMatchSubpath = ''
  780. const keys = Object.getOwnPropertyNames(imports)
  781. let i = -1
  782. while (++i < keys.length) {
  783. const key = keys[i]
  784. const patternIndex = key.indexOf('*')
  785. if (patternIndex !== -1 && name.startsWith(key.slice(0, -1))) {
  786. const patternTrailer = key.slice(patternIndex + 1)
  787. if (
  788. name.length >= key.length &&
  789. name.endsWith(patternTrailer) &&
  790. patternKeyCompare(bestMatch, key) === 1 &&
  791. key.lastIndexOf('*') === patternIndex
  792. ) {
  793. bestMatch = key
  794. bestMatchSubpath = name.slice(
  795. patternIndex,
  796. name.length - patternTrailer.length
  797. )
  798. }
  799. }
  800. }
  801. if (bestMatch) {
  802. const target = imports[bestMatch]
  803. const resolveResult = resolvePackageTarget(
  804. packageJsonUrl,
  805. target,
  806. bestMatchSubpath,
  807. bestMatch,
  808. base,
  809. true,
  810. true,
  811. false,
  812. conditions
  813. )
  814. if (resolveResult !== null && resolveResult !== undefined) {
  815. return resolveResult
  816. }
  817. }
  818. }
  819. }
  820. }
  821. throw importNotDefined(name, packageJsonUrl, base)
  822. }
  823. /**
  824. * @param {string} specifier
  825. * @param {URL} base
  826. */
  827. function parsePackageName(specifier, base) {
  828. let separatorIndex = specifier.indexOf('/')
  829. let validPackageName = true
  830. let isScoped = false
  831. if (specifier[0] === '@') {
  832. isScoped = true
  833. if (separatorIndex === -1 || specifier.length === 0) {
  834. validPackageName = false
  835. } else {
  836. separatorIndex = specifier.indexOf('/', separatorIndex + 1)
  837. }
  838. }
  839. const packageName =
  840. separatorIndex === -1 ? specifier : specifier.slice(0, separatorIndex)
  841. // Package name cannot have leading . and cannot have percent-encoding or
  842. // \\ separators.
  843. if (invalidPackageNameRegEx.exec(packageName) !== null) {
  844. validPackageName = false
  845. }
  846. if (!validPackageName) {
  847. throw new ERR_INVALID_MODULE_SPECIFIER(
  848. specifier,
  849. 'is not a valid package name',
  850. fileURLToPath(base)
  851. )
  852. }
  853. const packageSubpath =
  854. '.' + (separatorIndex === -1 ? '' : specifier.slice(separatorIndex))
  855. return {packageName, packageSubpath, isScoped}
  856. }
  857. /**
  858. * @param {string} specifier
  859. * @param {URL} base
  860. * @param {Set<string> | undefined} conditions
  861. * @returns {URL}
  862. */
  863. function packageResolve(specifier, base, conditions) {
  864. if (builtinModules.includes(specifier)) {
  865. return new URL('node:' + specifier)
  866. }
  867. const {packageName, packageSubpath, isScoped} = parsePackageName(
  868. specifier,
  869. base
  870. )
  871. // ResolveSelf
  872. const packageConfig = getPackageScopeConfig(base)
  873. // Can’t test.
  874. /* c8 ignore next 16 */
  875. if (packageConfig.exists) {
  876. const packageJsonUrl = pathToFileURL(packageConfig.pjsonPath)
  877. if (
  878. packageConfig.name === packageName &&
  879. packageConfig.exports !== undefined &&
  880. packageConfig.exports !== null
  881. ) {
  882. return packageExportsResolve(
  883. packageJsonUrl,
  884. packageSubpath,
  885. packageConfig,
  886. base,
  887. conditions
  888. )
  889. }
  890. }
  891. let packageJsonUrl = new URL(
  892. './node_modules/' + packageName + '/package.json',
  893. base
  894. )
  895. let packageJsonPath = fileURLToPath(packageJsonUrl)
  896. /** @type {string} */
  897. let lastPath
  898. do {
  899. const stat = tryStatSync(packageJsonPath.slice(0, -13))
  900. if (!stat || !stat.isDirectory()) {
  901. lastPath = packageJsonPath
  902. packageJsonUrl = new URL(
  903. (isScoped ? '../../../../node_modules/' : '../../../node_modules/') +
  904. packageName +
  905. '/package.json',
  906. packageJsonUrl
  907. )
  908. packageJsonPath = fileURLToPath(packageJsonUrl)
  909. continue
  910. }
  911. // Package match.
  912. const packageConfig = read(packageJsonPath, {base, specifier})
  913. if (packageConfig.exports !== undefined && packageConfig.exports !== null) {
  914. return packageExportsResolve(
  915. packageJsonUrl,
  916. packageSubpath,
  917. packageConfig,
  918. base,
  919. conditions
  920. )
  921. }
  922. if (packageSubpath === '.') {
  923. return legacyMainResolve(packageJsonUrl, packageConfig, base)
  924. }
  925. return new URL(packageSubpath, packageJsonUrl)
  926. // Cross-platform root check.
  927. } while (packageJsonPath.length !== lastPath.length)
  928. throw new ERR_MODULE_NOT_FOUND(packageName, fileURLToPath(base), false)
  929. }
  930. /**
  931. * @param {string} specifier
  932. * @returns {boolean}
  933. */
  934. function isRelativeSpecifier(specifier) {
  935. if (specifier[0] === '.') {
  936. if (specifier.length === 1 || specifier[1] === '/') return true
  937. if (
  938. specifier[1] === '.' &&
  939. (specifier.length === 2 || specifier[2] === '/')
  940. ) {
  941. return true
  942. }
  943. }
  944. return false
  945. }
  946. /**
  947. * @param {string} specifier
  948. * @returns {boolean}
  949. */
  950. function shouldBeTreatedAsRelativeOrAbsolutePath(specifier) {
  951. if (specifier === '') return false
  952. if (specifier[0] === '/') return true
  953. return isRelativeSpecifier(specifier)
  954. }
  955. /**
  956. * The “Resolver Algorithm Specification” as detailed in the Node docs (which is
  957. * sync and slightly lower-level than `resolve`).
  958. *
  959. * @param {string} specifier
  960. * `/example.js`, `./example.js`, `../example.js`, `some-package`, `fs`, etc.
  961. * @param {URL} base
  962. * Full URL (to a file) that `specifier` is resolved relative from.
  963. * @param {Set<string>} [conditions]
  964. * Conditions.
  965. * @param {boolean} [preserveSymlinks]
  966. * Keep symlinks instead of resolving them.
  967. * @returns {URL}
  968. * A URL object to the found thing.
  969. */
  970. export function moduleResolve(specifier, base, conditions, preserveSymlinks) {
  971. // Note: The Node code supports `base` as a string (in this internal API) too,
  972. // we don’t.
  973. if (conditions === undefined) {
  974. conditions = getConditionsSet()
  975. }
  976. const protocol = base.protocol
  977. const isData = protocol === 'data:'
  978. const isRemote = isData || protocol === 'http:' || protocol === 'https:'
  979. // Order swapped from spec for minor perf gain.
  980. // Ok since relative URLs cannot parse as URLs.
  981. /** @type {URL | undefined} */
  982. let resolved
  983. if (shouldBeTreatedAsRelativeOrAbsolutePath(specifier)) {
  984. try {
  985. resolved = new URL(specifier, base)
  986. } catch (error_) {
  987. const error = new ERR_UNSUPPORTED_RESOLVE_REQUEST(specifier, base)
  988. error.cause = error_
  989. throw error
  990. }
  991. } else if (protocol === 'file:' && specifier[0] === '#') {
  992. resolved = packageImportsResolve(specifier, base, conditions)
  993. } else {
  994. try {
  995. resolved = new URL(specifier)
  996. } catch (error_) {
  997. // Note: actual code uses `canBeRequiredWithoutScheme`.
  998. if (isRemote && !builtinModules.includes(specifier)) {
  999. const error = new ERR_UNSUPPORTED_RESOLVE_REQUEST(specifier, base)
  1000. error.cause = error_
  1001. throw error
  1002. }
  1003. resolved = packageResolve(specifier, base, conditions)
  1004. }
  1005. }
  1006. assert.ok(resolved !== undefined, 'expected to be defined')
  1007. if (resolved.protocol !== 'file:') {
  1008. return resolved
  1009. }
  1010. return finalizeResolution(resolved, base, preserveSymlinks)
  1011. }
  1012. /**
  1013. * @param {string} specifier
  1014. * @param {URL | undefined} parsed
  1015. * @param {URL | undefined} parsedParentURL
  1016. */
  1017. function checkIfDisallowedImport(specifier, parsed, parsedParentURL) {
  1018. if (parsedParentURL) {
  1019. // Avoid accessing the `protocol` property due to the lazy getters.
  1020. const parentProtocol = parsedParentURL.protocol
  1021. if (parentProtocol === 'http:' || parentProtocol === 'https:') {
  1022. if (shouldBeTreatedAsRelativeOrAbsolutePath(specifier)) {
  1023. // Avoid accessing the `protocol` property due to the lazy getters.
  1024. const parsedProtocol = parsed?.protocol
  1025. // `data:` and `blob:` disallowed due to allowing file: access via
  1026. // indirection
  1027. if (
  1028. parsedProtocol &&
  1029. parsedProtocol !== 'https:' &&
  1030. parsedProtocol !== 'http:'
  1031. ) {
  1032. throw new ERR_NETWORK_IMPORT_DISALLOWED(
  1033. specifier,
  1034. parsedParentURL,
  1035. 'remote imports cannot import from a local location.'
  1036. )
  1037. }
  1038. return {url: parsed?.href || ''}
  1039. }
  1040. if (builtinModules.includes(specifier)) {
  1041. throw new ERR_NETWORK_IMPORT_DISALLOWED(
  1042. specifier,
  1043. parsedParentURL,
  1044. 'remote imports cannot import from a local location.'
  1045. )
  1046. }
  1047. throw new ERR_NETWORK_IMPORT_DISALLOWED(
  1048. specifier,
  1049. parsedParentURL,
  1050. 'only relative and absolute specifiers are supported.'
  1051. )
  1052. }
  1053. }
  1054. }
  1055. // Note: this is from:
  1056. // <https://github.com/nodejs/node/blob/3e74590/lib/internal/url.js#L687>
  1057. /**
  1058. * Checks if a value has the shape of a WHATWG URL object.
  1059. *
  1060. * Using a symbol or instanceof would not be able to recognize URL objects
  1061. * coming from other implementations (e.g. in Electron), so instead we are
  1062. * checking some well known properties for a lack of a better test.
  1063. *
  1064. * We use `href` and `protocol` as they are the only properties that are
  1065. * easy to retrieve and calculate due to the lazy nature of the getters.
  1066. *
  1067. * @template {unknown} Value
  1068. * @param {Value} self
  1069. * @returns {Value is URL}
  1070. */
  1071. function isURL(self) {
  1072. return Boolean(
  1073. self &&
  1074. typeof self === 'object' &&
  1075. 'href' in self &&
  1076. typeof self.href === 'string' &&
  1077. 'protocol' in self &&
  1078. typeof self.protocol === 'string' &&
  1079. self.href &&
  1080. self.protocol
  1081. )
  1082. }
  1083. /**
  1084. * Validate user-input in `context` supplied by a custom loader.
  1085. *
  1086. * @param {unknown} parentURL
  1087. * @returns {asserts parentURL is URL | string | undefined}
  1088. */
  1089. function throwIfInvalidParentURL(parentURL) {
  1090. if (parentURL === undefined) {
  1091. return // Main entry point, so no parent
  1092. }
  1093. if (typeof parentURL !== 'string' && !isURL(parentURL)) {
  1094. throw new codes.ERR_INVALID_ARG_TYPE(
  1095. 'parentURL',
  1096. ['string', 'URL'],
  1097. parentURL
  1098. )
  1099. }
  1100. }
  1101. /**
  1102. * @param {string} specifier
  1103. * @param {{parentURL?: string, conditions?: Array<string>}} context
  1104. * @returns {{url: string, format?: string | null}}
  1105. */
  1106. export function defaultResolve(specifier, context = {}) {
  1107. const {parentURL} = context
  1108. assert.ok(parentURL !== undefined, 'expected `parentURL` to be defined')
  1109. throwIfInvalidParentURL(parentURL)
  1110. /** @type {URL | undefined} */
  1111. let parsedParentURL
  1112. if (parentURL) {
  1113. try {
  1114. parsedParentURL = new URL(parentURL)
  1115. } catch {
  1116. // Ignore exception
  1117. }
  1118. }
  1119. /** @type {URL | undefined} */
  1120. let parsed
  1121. /** @type {string | undefined} */
  1122. let protocol
  1123. try {
  1124. parsed = shouldBeTreatedAsRelativeOrAbsolutePath(specifier)
  1125. ? new URL(specifier, parsedParentURL)
  1126. : new URL(specifier)
  1127. // Avoid accessing the `protocol` property due to the lazy getters.
  1128. protocol = parsed.protocol
  1129. if (protocol === 'data:') {
  1130. return {url: parsed.href, format: null}
  1131. }
  1132. } catch {
  1133. // Ignore exception
  1134. }
  1135. // There are multiple deep branches that can either throw or return; instead
  1136. // of duplicating that deeply nested logic for the possible returns, DRY and
  1137. // check for a return. This seems the least gnarly.
  1138. const maybeReturn = checkIfDisallowedImport(
  1139. specifier,
  1140. parsed,
  1141. parsedParentURL
  1142. )
  1143. if (maybeReturn) return maybeReturn
  1144. // This must come after checkIfDisallowedImport
  1145. if (protocol === undefined && parsed) {
  1146. protocol = parsed.protocol
  1147. }
  1148. if (protocol === 'node:') {
  1149. return {url: specifier}
  1150. }
  1151. // This must come after checkIfDisallowedImport
  1152. if (parsed && parsed.protocol === 'node:') return {url: specifier}
  1153. const conditions = getConditionsSet(context.conditions)
  1154. const url = moduleResolve(specifier, new URL(parentURL), conditions, false)
  1155. return {
  1156. // Do NOT cast `url` to a string: that will work even when there are real
  1157. // problems, silencing them
  1158. url: url.href,
  1159. format: defaultGetFormatWithoutErrors(url, {parentURL})
  1160. }
  1161. }