ResolverFactory.js 22 KB

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