ResolverFactory.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. // eslint-disable-next-line n/prefer-global/process
  7. const { versions } = require("process");
  8. const AliasFieldPlugin = require("./AliasFieldPlugin");
  9. const AliasPlugin = require("./AliasPlugin");
  10. const AppendPlugin = require("./AppendPlugin");
  11. const ConditionalPlugin = require("./ConditionalPlugin");
  12. const DescriptionFilePlugin = require("./DescriptionFilePlugin");
  13. const DirectoryExistsPlugin = require("./DirectoryExistsPlugin");
  14. const ExportsFieldPlugin = require("./ExportsFieldPlugin");
  15. const ExtensionAliasPlugin = require("./ExtensionAliasPlugin");
  16. const FileExistsPlugin = require("./FileExistsPlugin");
  17. const ImportsFieldPlugin = require("./ImportsFieldPlugin");
  18. const JoinRequestPartPlugin = require("./JoinRequestPartPlugin");
  19. const JoinRequestPlugin = require("./JoinRequestPlugin");
  20. const MainFieldPlugin = require("./MainFieldPlugin");
  21. const ModulesInHierarchicalDirectoriesPlugin = require("./ModulesInHierarchicalDirectoriesPlugin");
  22. const ModulesInRootPlugin = require("./ModulesInRootPlugin");
  23. const NextPlugin = require("./NextPlugin");
  24. const ParsePlugin = require("./ParsePlugin");
  25. const PnpPlugin = require("./PnpPlugin");
  26. const Resolver = require("./Resolver");
  27. const RestrictionsPlugin = require("./RestrictionsPlugin");
  28. const ResultPlugin = require("./ResultPlugin");
  29. const RootsPlugin = require("./RootsPlugin");
  30. const SelfReferencePlugin = require("./SelfReferencePlugin");
  31. const SymlinkPlugin = require("./SymlinkPlugin");
  32. const SyncAsyncFileSystemDecorator = require("./SyncAsyncFileSystemDecorator");
  33. const TryNextPlugin = require("./TryNextPlugin");
  34. const TsconfigPathsPlugin = require("./TsconfigPathsPlugin");
  35. const UnsafeCachePlugin = require("./UnsafeCachePlugin");
  36. const UseFilePlugin = require("./UseFilePlugin");
  37. const { PathType, getType } = require("./util/path");
  38. /** @typedef {import("./AliasPlugin").AliasOption} AliasOptionEntry */
  39. /** @typedef {import("./ExtensionAliasPlugin").ExtensionAliasOption} ExtensionAliasOption */
  40. /** @typedef {import("./PnpPlugin").PnpApiImpl} PnpApi */
  41. /** @typedef {import("./Resolver").EnsuredHooks} EnsuredHooks */
  42. /** @typedef {import("./Resolver").FileSystem} FileSystem */
  43. /** @typedef {import("./Resolver").KnownHooks} KnownHooks */
  44. /** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
  45. /** @typedef {import("./Resolver").SyncFileSystem} SyncFileSystem */
  46. /** @typedef {import("./UnsafeCachePlugin").Cache} Cache */
  47. /** @typedef {string | string[] | false} AliasOptionNewRequest */
  48. /** @typedef {{ [k: string]: AliasOptionNewRequest }} AliasOptions */
  49. /** @typedef {{ [k: string]: string | string[] }} ExtensionAliasOptions */
  50. /** @typedef {false | 0 | "" | null | undefined} Falsy */
  51. /** @typedef {{ apply: (resolver: Resolver) => void } | ((this: Resolver, resolver: Resolver) => void) | Falsy} Plugin */
  52. /**
  53. * @typedef {object} TsconfigOptions
  54. * @property {string=} configFile A relative path to the tsconfig file based on cwd, or an absolute path of tsconfig file
  55. * @property {string[] | "auto"=} references References to other tsconfig files. 'auto' inherits from TypeScript config, or an array of relative/absolute paths
  56. * @property {string=} baseUrl Override baseUrl from tsconfig.json. If provided, this value will be used instead of the baseUrl in the tsconfig file
  57. */
  58. /**
  59. * @typedef {object} UserResolveOptions
  60. * @property {(AliasOptions | AliasOptionEntry[])=} alias A list of module alias configurations or an object which maps key to value
  61. * @property {(AliasOptions | AliasOptionEntry[])=} fallback A list of module alias configurations or an object which maps key to value, applied only after modules option
  62. * @property {ExtensionAliasOptions=} extensionAlias An object which maps extension to extension aliases
  63. * @property {boolean=} extensionAliasForExports Also apply `extensionAlias` to paths resolved through the package.json `exports` field. Off by default (Node.js-aligned); when enabled, matches TypeScript's behavior for packages that ship TS sources alongside compiled JS.
  64. * @property {(string | string[])[]=} aliasFields A list of alias fields in description files
  65. * @property {((predicate: ResolveRequest) => boolean)=} cachePredicate A function which decides whether a request should be cached or not. An object is passed with at least `path` and `request` properties.
  66. * @property {boolean=} cacheWithContext Whether or not the unsafeCache should include request context as part of the cache key.
  67. * @property {string[]=} descriptionFiles A list of description files to read from
  68. * @property {string[]=} conditionNames A list of exports field condition names.
  69. * @property {boolean=} enforceExtension Enforce that a extension from extensions must be used
  70. * @property {(string | string[])[]=} exportsFields A list of exports fields in description files
  71. * @property {(string | string[])[]=} importsFields A list of imports fields in description files
  72. * @property {string[]=} extensions A list of extensions which should be tried for files
  73. * @property {FileSystem} fileSystem The file system which should be used
  74. * @property {(Cache | boolean)=} unsafeCache Use this cache object to unsafely cache the successful requests
  75. * @property {boolean=} symlinks Resolve symlinks to their symlinked location
  76. * @property {Resolver=} resolver A prepared Resolver to which the plugins are attached
  77. * @property {string[] | string=} modules A list of directories to resolve modules from, can be absolute path or folder name
  78. * @property {(string | string[] | { name: string | string[], forceRelative: boolean })[]=} mainFields A list of main fields in description files
  79. * @property {string[]=} mainFiles A list of main files in directories
  80. * @property {Plugin[]=} plugins A list of additional resolve plugins which should be applied
  81. * @property {PnpApi | null=} pnpApi A PnP API that should be used - null is "never", undefined is "auto"
  82. * @property {string[]=} roots A list of root paths
  83. * @property {boolean=} fullySpecified The request is already fully specified and no extensions or directories are resolved for it
  84. * @property {boolean=} resolveToContext Resolve to a context instead of a file
  85. * @property {(string | RegExp)[]=} restrictions A list of resolve restrictions
  86. * @property {boolean=} useSyncFileSystemCalls Use only the sync constraints of the file system calls
  87. * @property {boolean=} preferRelative Prefer to resolve module requests as relative requests before falling back to modules
  88. * @property {boolean=} preferAbsolute Prefer to resolve server-relative urls as absolute paths before falling back to resolve in roots
  89. * @property {string | boolean | TsconfigOptions=} tsconfig TypeScript config file path or config object with configFile and references
  90. */
  91. /**
  92. * @typedef {object} ResolveOptions
  93. * @property {AliasOptionEntry[]} alias alias
  94. * @property {AliasOptionEntry[]} fallback fallback
  95. * @property {Set<string | string[]>} aliasFields alias fields
  96. * @property {ExtensionAliasOption[]} extensionAlias extension alias
  97. * @property {boolean} extensionAliasForExports apply extension alias to exports field targets
  98. * @property {(predicate: ResolveRequest) => boolean} cachePredicate cache predicate
  99. * @property {boolean} cacheWithContext cache with context
  100. * @property {Set<string>} conditionNames A list of exports field condition names.
  101. * @property {string[]} descriptionFiles description files
  102. * @property {boolean} enforceExtension enforce extension
  103. * @property {Set<string | string[]>} exportsFields exports fields
  104. * @property {Set<string | string[]>} importsFields imports fields
  105. * @property {Set<string>} extensions extensions
  106. * @property {FileSystem} fileSystem fileSystem
  107. * @property {Cache | false} unsafeCache unsafe cache
  108. * @property {boolean} symlinks symlinks
  109. * @property {Resolver=} resolver resolver
  110. * @property {(string | string[])[]} modules modules
  111. * @property {{ name: string[], forceRelative: boolean }[]} mainFields main fields
  112. * @property {Set<string>} mainFiles main files
  113. * @property {Plugin[]} plugins plugins
  114. * @property {PnpApi | null} pnpApi pnp API
  115. * @property {Set<string>} roots roots
  116. * @property {boolean} fullySpecified fully specified
  117. * @property {boolean} resolveToContext resolve to context
  118. * @property {Set<string | RegExp>} restrictions restrictions
  119. * @property {boolean} preferRelative prefer relative
  120. * @property {boolean} preferAbsolute prefer absolute
  121. * @property {string | boolean | TsconfigOptions} tsconfig tsconfig file path or config object
  122. */
  123. /**
  124. * @param {PnpApi | null=} option option
  125. * @returns {PnpApi | null} processed option
  126. */
  127. function processPnpApiOption(option) {
  128. if (
  129. option === undefined &&
  130. /** @type {NodeJS.ProcessVersions & { pnp: string }} */ versions.pnp
  131. ) {
  132. const _findPnpApi =
  133. /** @type {(issuer: string) => PnpApi | null}} */
  134. (
  135. // @ts-expect-error maybe nothing
  136. require("module").findPnpApi
  137. );
  138. if (_findPnpApi) {
  139. return {
  140. resolveToUnqualified(request, issuer, opts) {
  141. const pnpapi = _findPnpApi(issuer);
  142. if (!pnpapi) {
  143. // Issuer isn't managed by PnP
  144. return null;
  145. }
  146. return pnpapi.resolveToUnqualified(request, issuer, opts);
  147. },
  148. };
  149. }
  150. }
  151. return option || null;
  152. }
  153. /**
  154. * @param {AliasOptions | AliasOptionEntry[] | undefined} alias alias
  155. * @returns {AliasOptionEntry[]} normalized aliases
  156. */
  157. function normalizeAlias(alias) {
  158. return typeof alias === "object" && !Array.isArray(alias) && alias !== null
  159. ? Object.keys(alias).map((key) => {
  160. /** @type {AliasOptionEntry} */
  161. const obj = { name: key, onlyModule: false, alias: alias[key] };
  162. if (/\$$/.test(key)) {
  163. obj.onlyModule = true;
  164. obj.name = key.slice(0, -1);
  165. }
  166. return obj;
  167. })
  168. : /** @type {AliasOptionEntry[]} */ (alias) || [];
  169. }
  170. /**
  171. * Merging filtered elements
  172. * @param {string[]} array source array
  173. * @param {(item: string) => boolean} filter predicate
  174. * @returns {(string | string[])[]} merge result
  175. */
  176. function mergeFilteredToArray(array, filter) {
  177. /** @type {(string | string[])[]} */
  178. const result = [];
  179. const set = new Set(array);
  180. for (const item of set) {
  181. if (filter(item)) {
  182. const lastElement =
  183. result.length > 0 ? result[result.length - 1] : undefined;
  184. if (Array.isArray(lastElement)) {
  185. lastElement.push(item);
  186. } else {
  187. result.push([item]);
  188. }
  189. } else {
  190. result.push(item);
  191. }
  192. }
  193. return result;
  194. }
  195. /**
  196. * @param {UserResolveOptions} options input options
  197. * @returns {ResolveOptions} output options
  198. */
  199. function createOptions(options) {
  200. const mainFieldsSet = new Set(options.mainFields || ["main"]);
  201. /** @type {ResolveOptions["mainFields"]} */
  202. const mainFields = [];
  203. for (const item of mainFieldsSet) {
  204. if (typeof item === "string") {
  205. mainFields.push({
  206. name: [item],
  207. forceRelative: true,
  208. });
  209. } else if (Array.isArray(item)) {
  210. mainFields.push({
  211. name: item,
  212. forceRelative: true,
  213. });
  214. } else {
  215. mainFields.push({
  216. name: Array.isArray(item.name) ? item.name : [item.name],
  217. forceRelative: item.forceRelative,
  218. });
  219. }
  220. }
  221. return {
  222. alias: normalizeAlias(options.alias),
  223. fallback: normalizeAlias(options.fallback),
  224. aliasFields: new Set(options.aliasFields),
  225. cachePredicate:
  226. options.cachePredicate ||
  227. function trueFn() {
  228. return true;
  229. },
  230. cacheWithContext:
  231. typeof options.cacheWithContext !== "undefined"
  232. ? options.cacheWithContext
  233. : true,
  234. exportsFields: new Set(options.exportsFields || ["exports"]),
  235. importsFields: new Set(options.importsFields || ["imports"]),
  236. conditionNames: new Set(options.conditionNames),
  237. descriptionFiles: [
  238. ...new Set(options.descriptionFiles || ["package.json"]),
  239. ],
  240. enforceExtension:
  241. options.enforceExtension === undefined
  242. ? Boolean(options.extensions && options.extensions.includes(""))
  243. : options.enforceExtension,
  244. extensions: new Set(options.extensions || [".js", ".json", ".node"]),
  245. extensionAlias: options.extensionAlias
  246. ? Object.keys(options.extensionAlias).map((k) => ({
  247. extension: k,
  248. alias: /** @type {ExtensionAliasOptions} */ (options.extensionAlias)[
  249. k
  250. ],
  251. }))
  252. : [],
  253. extensionAliasForExports: options.extensionAliasForExports || false,
  254. fileSystem: options.useSyncFileSystemCalls
  255. ? new SyncAsyncFileSystemDecorator(
  256. /** @type {SyncFileSystem} */ (
  257. /** @type {unknown} */ (options.fileSystem)
  258. ),
  259. )
  260. : options.fileSystem,
  261. unsafeCache:
  262. options.unsafeCache && typeof options.unsafeCache !== "object"
  263. ? /** @type {Cache} */ ({})
  264. : options.unsafeCache || false,
  265. symlinks: typeof options.symlinks !== "undefined" ? options.symlinks : true,
  266. resolver: options.resolver,
  267. modules: mergeFilteredToArray(
  268. Array.isArray(options.modules)
  269. ? options.modules
  270. : options.modules
  271. ? [options.modules]
  272. : ["node_modules"],
  273. (item) => {
  274. const type = getType(item);
  275. return type === PathType.Normal || type === PathType.Relative;
  276. },
  277. ),
  278. mainFields,
  279. mainFiles: new Set(options.mainFiles || ["index"]),
  280. plugins: options.plugins || [],
  281. pnpApi: processPnpApiOption(options.pnpApi),
  282. roots: new Set(options.roots || undefined),
  283. fullySpecified: options.fullySpecified || false,
  284. resolveToContext: options.resolveToContext || false,
  285. preferRelative: options.preferRelative || false,
  286. preferAbsolute: options.preferAbsolute || false,
  287. restrictions: new Set(options.restrictions),
  288. tsconfig:
  289. typeof options.tsconfig === "undefined" ? false : options.tsconfig,
  290. };
  291. }
  292. /**
  293. * @param {UserResolveOptions} options resolve options
  294. * @returns {Resolver} created resolver
  295. */
  296. module.exports.createResolver = function createResolver(options) {
  297. const normalizedOptions = createOptions(options);
  298. const {
  299. alias,
  300. fallback,
  301. aliasFields,
  302. extensionAliasForExports,
  303. cachePredicate,
  304. cacheWithContext,
  305. conditionNames,
  306. descriptionFiles,
  307. enforceExtension,
  308. exportsFields,
  309. extensionAlias,
  310. importsFields,
  311. extensions,
  312. fileSystem,
  313. fullySpecified,
  314. mainFields,
  315. mainFiles,
  316. modules,
  317. plugins: userPlugins,
  318. pnpApi,
  319. resolveToContext,
  320. preferRelative,
  321. preferAbsolute,
  322. symlinks,
  323. unsafeCache,
  324. resolver: customResolver,
  325. restrictions,
  326. roots,
  327. tsconfig,
  328. } = normalizedOptions;
  329. const plugins = [...userPlugins];
  330. const resolver =
  331. customResolver || new Resolver(fileSystem, normalizedOptions);
  332. // // pipeline ////
  333. resolver.ensureHook("resolve");
  334. resolver.ensureHook("internalResolve");
  335. resolver.ensureHook("newInternalResolve");
  336. resolver.ensureHook("importsResolve");
  337. resolver.ensureHook("parsedResolve");
  338. resolver.ensureHook("describedResolve");
  339. resolver.ensureHook("rawResolve");
  340. resolver.ensureHook("normalResolve");
  341. resolver.ensureHook("internal");
  342. resolver.ensureHook("rawModule");
  343. resolver.ensureHook("alternateRawModule");
  344. resolver.ensureHook("module");
  345. resolver.ensureHook("resolveAsModule");
  346. resolver.ensureHook("undescribedResolveInPackage");
  347. resolver.ensureHook("resolveInPackage");
  348. resolver.ensureHook("resolveInExistingDirectory");
  349. resolver.ensureHook("importsFieldRelative");
  350. if (extensionAliasForExports) {
  351. resolver.ensureHook("exportsFieldRelative");
  352. }
  353. resolver.ensureHook("relative");
  354. resolver.ensureHook("describedRelative");
  355. resolver.ensureHook("directory");
  356. resolver.ensureHook("undescribedExistingDirectory");
  357. resolver.ensureHook("existingDirectory");
  358. resolver.ensureHook("undescribedRawFile");
  359. resolver.ensureHook("rawFile");
  360. resolver.ensureHook("file");
  361. resolver.ensureHook("finalFile");
  362. resolver.ensureHook("existingFile");
  363. resolver.ensureHook("resolved");
  364. // TODO remove in next major
  365. // cspell:word Interal
  366. // Backward-compat
  367. // @ts-expect-error
  368. resolver.hooks.newInteralResolve = resolver.hooks.newInternalResolve;
  369. // resolve
  370. for (const { source, resolveOptions } of [
  371. { source: "resolve", resolveOptions: { fullySpecified } },
  372. { source: "internal-resolve", resolveOptions: { fullySpecified: false } },
  373. // Entry point for non-relative targets from the imports field.
  374. // Sets internal: false to prevent re-entering imports resolution,
  375. // aligning with the Node.js ESM spec where PACKAGE_IMPORTS_RESOLVE
  376. // does not recursively resolve # specifiers.
  377. // https://nodejs.org/api/esm.html#resolution-algorithm-specification
  378. {
  379. source: "imports-resolve",
  380. resolveOptions: { fullySpecified: false, internal: false },
  381. },
  382. ]) {
  383. plugins.push(new ParsePlugin(source, resolveOptions, "parsed-resolve"));
  384. }
  385. // parsed-resolve
  386. plugins.push(
  387. new DescriptionFilePlugin(
  388. "parsed-resolve",
  389. descriptionFiles,
  390. false,
  391. "described-resolve",
  392. ),
  393. );
  394. plugins.push(new NextPlugin("after-parsed-resolve", "described-resolve"));
  395. // described-resolve
  396. if (unsafeCache) {
  397. plugins.push(
  398. new UnsafeCachePlugin(
  399. "described-resolve",
  400. cachePredicate,
  401. /** @type {import("./UnsafeCachePlugin").Cache} */ (unsafeCache),
  402. cacheWithContext,
  403. "raw-resolve",
  404. ),
  405. );
  406. } else {
  407. plugins.push(new NextPlugin("described-resolve", "raw-resolve"));
  408. }
  409. if (fallback.length > 0) {
  410. plugins.push(
  411. new AliasPlugin("described-resolve", fallback, "internal-resolve"),
  412. );
  413. }
  414. // raw-resolve
  415. if (alias.length > 0) {
  416. plugins.push(new AliasPlugin("raw-resolve", alias, "internal-resolve"));
  417. }
  418. if (tsconfig) {
  419. plugins.push(new TsconfigPathsPlugin(tsconfig));
  420. }
  421. for (const item of aliasFields) {
  422. plugins.push(new AliasFieldPlugin("raw-resolve", item, "internal-resolve"));
  423. }
  424. for (const item of extensionAlias) {
  425. plugins.push(
  426. new ExtensionAliasPlugin("raw-resolve", item, "normal-resolve"),
  427. );
  428. }
  429. plugins.push(new NextPlugin("raw-resolve", "normal-resolve"));
  430. // normal-resolve
  431. if (preferRelative) {
  432. plugins.push(new JoinRequestPlugin("after-normal-resolve", "relative"));
  433. }
  434. plugins.push(
  435. new ConditionalPlugin(
  436. "after-normal-resolve",
  437. { module: true },
  438. "resolve as module",
  439. false,
  440. "raw-module",
  441. ),
  442. );
  443. plugins.push(
  444. new ConditionalPlugin(
  445. "after-normal-resolve",
  446. { internal: true },
  447. "resolve as internal import",
  448. false,
  449. "internal",
  450. ),
  451. );
  452. if (preferAbsolute) {
  453. plugins.push(new JoinRequestPlugin("after-normal-resolve", "relative"));
  454. }
  455. if (roots.size > 0) {
  456. plugins.push(new RootsPlugin("after-normal-resolve", roots, "relative"));
  457. }
  458. if (!preferRelative && !preferAbsolute) {
  459. plugins.push(new JoinRequestPlugin("after-normal-resolve", "relative"));
  460. }
  461. // internal
  462. for (const importsField of importsFields) {
  463. plugins.push(
  464. new ImportsFieldPlugin(
  465. "internal",
  466. conditionNames,
  467. importsField,
  468. "imports-field-relative",
  469. "imports-resolve",
  470. ),
  471. );
  472. }
  473. // imports-field-relative: apply extensionAlias to paths produced by the
  474. // imports field (TypeScript-style extension substitution for self-package
  475. // imports like `#foo` -> `./foo.js` -> `./foo.ts`). Unlike the exports
  476. // field, the imports field is internal to the package, so applying the
  477. // consumer's extension aliases does not expose files the package author
  478. // did not intend to export. See issue #413.
  479. for (const item of extensionAlias) {
  480. plugins.push(
  481. new ExtensionAliasPlugin("imports-field-relative", item, "relative"),
  482. );
  483. }
  484. plugins.push(new NextPlugin("imports-field-relative", "relative"));
  485. // raw-module
  486. for (const exportsField of exportsFields) {
  487. plugins.push(
  488. new SelfReferencePlugin("raw-module", exportsField, "resolve-as-module"),
  489. );
  490. }
  491. for (const item of modules) {
  492. if (Array.isArray(item)) {
  493. if (item.includes("node_modules") && pnpApi) {
  494. plugins.push(
  495. new ModulesInHierarchicalDirectoriesPlugin(
  496. "raw-module",
  497. item.filter((i) => i !== "node_modules"),
  498. "module",
  499. ),
  500. );
  501. plugins.push(
  502. new PnpPlugin(
  503. "raw-module",
  504. pnpApi,
  505. "undescribed-resolve-in-package",
  506. "alternate-raw-module",
  507. ),
  508. );
  509. plugins.push(
  510. new ModulesInHierarchicalDirectoriesPlugin(
  511. "alternate-raw-module",
  512. ["node_modules"],
  513. "module",
  514. ),
  515. );
  516. } else {
  517. plugins.push(
  518. new ModulesInHierarchicalDirectoriesPlugin(
  519. "raw-module",
  520. item,
  521. "module",
  522. ),
  523. );
  524. }
  525. } else {
  526. plugins.push(new ModulesInRootPlugin("raw-module", item, "module"));
  527. }
  528. }
  529. // module
  530. plugins.push(new JoinRequestPartPlugin("module", "resolve-as-module"));
  531. // resolve-as-module
  532. if (!resolveToContext) {
  533. plugins.push(
  534. new ConditionalPlugin(
  535. "resolve-as-module",
  536. { directory: false, request: "." },
  537. "single file module",
  538. true,
  539. "undescribed-raw-file",
  540. ),
  541. );
  542. }
  543. plugins.push(
  544. new DirectoryExistsPlugin(
  545. "resolve-as-module",
  546. "undescribed-resolve-in-package",
  547. ),
  548. );
  549. // undescribed-resolve-in-package
  550. plugins.push(
  551. new DescriptionFilePlugin(
  552. "undescribed-resolve-in-package",
  553. descriptionFiles,
  554. false,
  555. "resolve-in-package",
  556. ),
  557. );
  558. plugins.push(
  559. new NextPlugin(
  560. "after-undescribed-resolve-in-package",
  561. "resolve-in-package",
  562. ),
  563. );
  564. // resolve-in-package
  565. const exportsFieldTarget = extensionAliasForExports
  566. ? "exports-field-relative"
  567. : "relative";
  568. for (const exportsField of exportsFields) {
  569. plugins.push(
  570. new ExportsFieldPlugin(
  571. "resolve-in-package",
  572. conditionNames,
  573. exportsField,
  574. exportsFieldTarget,
  575. ),
  576. );
  577. }
  578. plugins.push(
  579. new NextPlugin("resolve-in-package", "resolve-in-existing-directory"),
  580. );
  581. // exports-field-relative (opt-in via `extensionAliasForExports`):
  582. // apply `extensionAlias` to paths produced by the exports field. This is
  583. // off by default to match Node.js (which does not substitute extensions on
  584. // bare-module targets), and on opt-in aligns with TypeScript for packages
  585. // that ship TS sources alongside the compiled JS they list in `exports`.
  586. if (extensionAliasForExports) {
  587. for (const item of extensionAlias) {
  588. plugins.push(
  589. new ExtensionAliasPlugin("exports-field-relative", item, "relative"),
  590. );
  591. }
  592. plugins.push(new NextPlugin("exports-field-relative", "relative"));
  593. }
  594. // resolve-in-existing-directory
  595. plugins.push(
  596. new JoinRequestPlugin("resolve-in-existing-directory", "relative"),
  597. );
  598. // relative
  599. plugins.push(
  600. new DescriptionFilePlugin(
  601. "relative",
  602. descriptionFiles,
  603. true,
  604. "described-relative",
  605. ),
  606. );
  607. plugins.push(new NextPlugin("after-relative", "described-relative"));
  608. // described-relative
  609. if (resolveToContext) {
  610. plugins.push(new NextPlugin("described-relative", "directory"));
  611. } else {
  612. plugins.push(
  613. new ConditionalPlugin(
  614. "described-relative",
  615. { directory: false },
  616. null,
  617. true,
  618. "raw-file",
  619. ),
  620. );
  621. plugins.push(
  622. new ConditionalPlugin(
  623. "described-relative",
  624. { fullySpecified: false },
  625. "as directory",
  626. true,
  627. "directory",
  628. ),
  629. );
  630. }
  631. // directory
  632. plugins.push(
  633. new DirectoryExistsPlugin("directory", "undescribed-existing-directory"),
  634. );
  635. if (resolveToContext) {
  636. // undescribed-existing-directory
  637. plugins.push(new NextPlugin("undescribed-existing-directory", "resolved"));
  638. } else {
  639. // undescribed-existing-directory
  640. plugins.push(
  641. new DescriptionFilePlugin(
  642. "undescribed-existing-directory",
  643. descriptionFiles,
  644. false,
  645. "existing-directory",
  646. ),
  647. );
  648. for (const item of mainFiles) {
  649. plugins.push(
  650. new UseFilePlugin(
  651. "undescribed-existing-directory",
  652. item,
  653. "undescribed-raw-file",
  654. ),
  655. );
  656. }
  657. // described-existing-directory
  658. for (const item of mainFields) {
  659. plugins.push(
  660. new MainFieldPlugin(
  661. "existing-directory",
  662. item,
  663. "resolve-in-existing-directory",
  664. ),
  665. );
  666. }
  667. for (const item of mainFiles) {
  668. plugins.push(
  669. new UseFilePlugin("existing-directory", item, "undescribed-raw-file"),
  670. );
  671. }
  672. // undescribed-raw-file
  673. plugins.push(
  674. new DescriptionFilePlugin(
  675. "undescribed-raw-file",
  676. descriptionFiles,
  677. true,
  678. "raw-file",
  679. ),
  680. );
  681. plugins.push(new NextPlugin("after-undescribed-raw-file", "raw-file"));
  682. // raw-file
  683. plugins.push(
  684. new ConditionalPlugin(
  685. "raw-file",
  686. { fullySpecified: true },
  687. null,
  688. false,
  689. "file",
  690. ),
  691. );
  692. if (!enforceExtension) {
  693. plugins.push(new TryNextPlugin("raw-file", "no extension", "file"));
  694. }
  695. for (const item of extensions) {
  696. plugins.push(new AppendPlugin("raw-file", item, "file"));
  697. }
  698. // file
  699. if (alias.length > 0) {
  700. plugins.push(new AliasPlugin("file", alias, "internal-resolve"));
  701. }
  702. for (const item of aliasFields) {
  703. plugins.push(new AliasFieldPlugin("file", item, "internal-resolve"));
  704. }
  705. plugins.push(new NextPlugin("file", "final-file"));
  706. // final-file
  707. plugins.push(new FileExistsPlugin("final-file", "existing-file"));
  708. // existing-file
  709. if (symlinks) {
  710. plugins.push(new SymlinkPlugin("existing-file", "existing-file"));
  711. }
  712. plugins.push(new NextPlugin("existing-file", "resolved"));
  713. }
  714. const { resolved } =
  715. /** @type {KnownHooks & EnsuredHooks} */
  716. (resolver.hooks);
  717. // resolved
  718. if (restrictions.size > 0) {
  719. plugins.push(new RestrictionsPlugin(resolved, restrictions));
  720. }
  721. plugins.push(new ResultPlugin(resolved));
  722. // // RESOLVER ////
  723. for (const plugin of plugins) {
  724. if (typeof plugin === "function") {
  725. /** @type {(this: Resolver, resolver: Resolver) => void} */
  726. (plugin).call(resolver, resolver);
  727. } else if (plugin) {
  728. plugin.apply(resolver);
  729. }
  730. }
  731. return resolver;
  732. };