ResolverFactory.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const Factory = require("enhanced-resolve").ResolverFactory;
  7. const { HookMap, SyncHook, SyncWaterfallHook } = require("tapable");
  8. const {
  9. cachedCleverMerge,
  10. removeOperations,
  11. resolveByProperty
  12. } = require("./util/cleverMerge");
  13. /** @typedef {import("enhanced-resolve").ResolveOptions} ResolveOptions */
  14. /** @typedef {import("enhanced-resolve").Resolver} Resolver */
  15. /** @typedef {import("../declarations/WebpackOptions").ResolveOptions} WebpackResolveOptions */
  16. /** @typedef {import("../declarations/WebpackOptions").ResolvePluginInstance} ResolvePluginInstance */
  17. /** @typedef {WebpackResolveOptions & { dependencyType?: string, resolveToContext?: boolean }} ResolveOptionsWithDependencyType */
  18. /**
  19. * Defines the with options type used by this module.
  20. * @typedef {object} WithOptions
  21. * @property {(options: Partial<ResolveOptionsWithDependencyType>) => ResolverWithOptions} withOptions create a resolver with additional/different options
  22. */
  23. /** @typedef {Resolver & WithOptions} ResolverWithOptions */
  24. // need to be hoisted on module level for caching identity
  25. /** @type {ResolveOptionsWithDependencyType} */
  26. const EMPTY_RESOLVE_OPTIONS = {};
  27. /**
  28. * Convert to resolve options.
  29. * @param {ResolveOptionsWithDependencyType} resolveOptionsWithDepType enhanced options
  30. * @returns {ResolveOptions} merged options
  31. */
  32. const convertToResolveOptions = (resolveOptionsWithDepType) => {
  33. const { dependencyType, plugins, ...remaining } = resolveOptionsWithDepType;
  34. // check type compat
  35. /** @type {Partial<ResolveOptionsWithDependencyType>} */
  36. const partialOptions = {
  37. ...remaining,
  38. plugins:
  39. plugins &&
  40. /** @type {ResolvePluginInstance[]} */ (
  41. plugins.filter((item) => item !== "...")
  42. )
  43. };
  44. if (!partialOptions.fileSystem) {
  45. throw new Error(
  46. "fileSystem is missing in resolveOptions, but it's required for enhanced-resolve"
  47. );
  48. }
  49. // These weird types validate that we checked all non-optional properties
  50. const options =
  51. /** @type {Partial<ResolveOptionsWithDependencyType> & Pick<ResolveOptionsWithDependencyType, "fileSystem">} */ (
  52. partialOptions
  53. );
  54. return /** @type {ResolveOptions} */ (
  55. removeOperations(
  56. resolveByProperty(options, "byDependency", dependencyType),
  57. // Keep the `unsafeCache` because it can be a `Proxy`
  58. ["unsafeCache"]
  59. )
  60. );
  61. };
  62. /**
  63. * Represents the resolver factory runtime component.
  64. * @typedef {object} ResolverCache
  65. * @property {WeakMap<ResolveOptionsWithDependencyType, ResolverWithOptions>} direct
  66. * @property {Map<string, ResolverWithOptions>} stringified
  67. */
  68. module.exports = class ResolverFactory {
  69. constructor() {
  70. this.hooks = Object.freeze({
  71. /** @type {HookMap<SyncWaterfallHook<[ResolveOptionsWithDependencyType]>>} */
  72. resolveOptions: new HookMap(
  73. () => new SyncWaterfallHook(["resolveOptions"])
  74. ),
  75. /** @type {HookMap<SyncHook<[Resolver, ResolveOptions, ResolveOptionsWithDependencyType]>>} */
  76. resolver: new HookMap(
  77. () => new SyncHook(["resolver", "resolveOptions", "userResolveOptions"])
  78. )
  79. });
  80. /** @type {Map<string, ResolverCache>} */
  81. this.cache = new Map();
  82. }
  83. /**
  84. * Returns the resolver.
  85. * @param {string} type type of resolver
  86. * @param {ResolveOptionsWithDependencyType=} resolveOptions options
  87. * @returns {ResolverWithOptions} the resolver
  88. */
  89. get(type, resolveOptions = EMPTY_RESOLVE_OPTIONS) {
  90. let typedCaches = this.cache.get(type);
  91. if (!typedCaches) {
  92. typedCaches = {
  93. direct: new WeakMap(),
  94. stringified: new Map()
  95. };
  96. this.cache.set(type, typedCaches);
  97. }
  98. const cachedResolver = typedCaches.direct.get(resolveOptions);
  99. if (cachedResolver) {
  100. return cachedResolver;
  101. }
  102. const ident = JSON.stringify(resolveOptions);
  103. const resolver = typedCaches.stringified.get(ident);
  104. if (resolver) {
  105. typedCaches.direct.set(resolveOptions, resolver);
  106. return resolver;
  107. }
  108. const newResolver = this._create(type, resolveOptions);
  109. typedCaches.direct.set(resolveOptions, newResolver);
  110. typedCaches.stringified.set(ident, newResolver);
  111. return newResolver;
  112. }
  113. /**
  114. * Returns the resolver.
  115. * @param {string} type type of resolver
  116. * @param {ResolveOptionsWithDependencyType} resolveOptionsWithDepType options
  117. * @returns {ResolverWithOptions} the resolver
  118. */
  119. _create(type, resolveOptionsWithDepType) {
  120. /** @type {ResolveOptionsWithDependencyType} */
  121. const originalResolveOptions = { ...resolveOptionsWithDepType };
  122. const resolveOptions = convertToResolveOptions(
  123. this.hooks.resolveOptions.for(type).call(resolveOptionsWithDepType)
  124. );
  125. const resolver = /** @type {ResolverWithOptions} */ (
  126. Factory.createResolver(resolveOptions)
  127. );
  128. if (!resolver) {
  129. throw new Error("No resolver created");
  130. }
  131. /** @type {WeakMap<Partial<ResolveOptionsWithDependencyType>, ResolverWithOptions>} */
  132. const childCache = new WeakMap();
  133. resolver.withOptions = (options) => {
  134. const cacheEntry = childCache.get(options);
  135. if (cacheEntry !== undefined) return cacheEntry;
  136. const mergedOptions = cachedCleverMerge(originalResolveOptions, options);
  137. const resolver = this.get(type, mergedOptions);
  138. childCache.set(options, resolver);
  139. return resolver;
  140. };
  141. this.hooks.resolver
  142. .for(type)
  143. .call(resolver, resolveOptions, originalResolveOptions);
  144. return resolver;
  145. }
  146. };