ResolverFactory.js 20 KB

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