ResolverFactory.js 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  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").ResolveContext} ResolveContext */
  14. /** @typedef {import("enhanced-resolve").ResolveOptions} ResolveOptions */
  15. /** @typedef {import("enhanced-resolve").ResolveRequest} ResolveRequest */
  16. /** @typedef {import("enhanced-resolve").Resolver} Resolver */
  17. /** @typedef {import("../declarations/WebpackOptions").ResolveOptions} WebpackResolveOptions */
  18. /** @typedef {import("../declarations/WebpackOptions").ResolvePluginInstance} ResolvePluginInstance */
  19. /** @typedef {WebpackResolveOptions & { dependencyType?: string, resolveToContext?: boolean }} ResolveOptionsWithDependencyType */
  20. /**
  21. * @typedef {object} WithOptions
  22. * @property {(options: Partial<ResolveOptionsWithDependencyType>) => ResolverWithOptions} withOptions create a resolver with additional/different options
  23. */
  24. /** @typedef {Resolver & WithOptions} ResolverWithOptions */
  25. // need to be hoisted on module level for caching identity
  26. /** @type {ResolveOptionsWithDependencyType} */
  27. const EMPTY_RESOLVE_OPTIONS = {};
  28. /**
  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. * @typedef {object} ResolverCache
  64. * @property {WeakMap<ResolveOptionsWithDependencyType, ResolverWithOptions>} direct
  65. * @property {Map<string, ResolverWithOptions>} stringified
  66. */
  67. module.exports = class ResolverFactory {
  68. constructor() {
  69. this.hooks = Object.freeze({
  70. /** @type {HookMap<SyncWaterfallHook<[ResolveOptionsWithDependencyType]>>} */
  71. resolveOptions: new HookMap(
  72. () => new SyncWaterfallHook(["resolveOptions"])
  73. ),
  74. /** @type {HookMap<SyncHook<[Resolver, ResolveOptions, ResolveOptionsWithDependencyType]>>} */
  75. resolver: new HookMap(
  76. () => new SyncHook(["resolver", "resolveOptions", "userResolveOptions"])
  77. )
  78. });
  79. /** @type {Map<string, ResolverCache>} */
  80. this.cache = new Map();
  81. }
  82. /**
  83. * @param {string} type type of resolver
  84. * @param {ResolveOptionsWithDependencyType=} resolveOptions options
  85. * @returns {ResolverWithOptions} the resolver
  86. */
  87. get(type, resolveOptions = EMPTY_RESOLVE_OPTIONS) {
  88. let typedCaches = this.cache.get(type);
  89. if (!typedCaches) {
  90. typedCaches = {
  91. direct: new WeakMap(),
  92. stringified: new Map()
  93. };
  94. this.cache.set(type, typedCaches);
  95. }
  96. const cachedResolver = typedCaches.direct.get(resolveOptions);
  97. if (cachedResolver) {
  98. return cachedResolver;
  99. }
  100. const ident = JSON.stringify(resolveOptions);
  101. const resolver = typedCaches.stringified.get(ident);
  102. if (resolver) {
  103. typedCaches.direct.set(resolveOptions, resolver);
  104. return resolver;
  105. }
  106. const newResolver = this._create(type, resolveOptions);
  107. typedCaches.direct.set(resolveOptions, newResolver);
  108. typedCaches.stringified.set(ident, newResolver);
  109. return newResolver;
  110. }
  111. /**
  112. * @param {string} type type of resolver
  113. * @param {ResolveOptionsWithDependencyType} resolveOptionsWithDepType options
  114. * @returns {ResolverWithOptions} the resolver
  115. */
  116. _create(type, resolveOptionsWithDepType) {
  117. /** @type {ResolveOptionsWithDependencyType} */
  118. const originalResolveOptions = { ...resolveOptionsWithDepType };
  119. const resolveOptions = convertToResolveOptions(
  120. this.hooks.resolveOptions.for(type).call(resolveOptionsWithDepType)
  121. );
  122. const resolver = /** @type {ResolverWithOptions} */ (
  123. Factory.createResolver(resolveOptions)
  124. );
  125. if (!resolver) {
  126. throw new Error("No resolver created");
  127. }
  128. /** @type {WeakMap<Partial<ResolveOptionsWithDependencyType>, ResolverWithOptions>} */
  129. const childCache = new WeakMap();
  130. resolver.withOptions = options => {
  131. const cacheEntry = childCache.get(options);
  132. if (cacheEntry !== undefined) return cacheEntry;
  133. const mergedOptions = cachedCleverMerge(originalResolveOptions, options);
  134. const resolver = this.get(type, mergedOptions);
  135. childCache.set(options, resolver);
  136. return resolver;
  137. };
  138. this.hooks.resolver
  139. .for(type)
  140. .call(resolver, resolveOptions, originalResolveOptions);
  141. return resolver;
  142. }
  143. };