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