ResolverFactory.js 21 KB

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