ResolverFactory.js 27 KB

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