ResolverFactory.js 22 KB

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