ResolverFactory.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  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 {function(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. const EMPTY_RESOLVE_OPTIONS = {};
  27. /**
  28. * @param {ResolveOptionsWithDependencyType} resolveOptionsWithDepType enhanced options
  29. * @returns {ResolveOptions} merged options
  30. */
  31. const convertToResolveOptions = resolveOptionsWithDepType => {
  32. const { dependencyType, plugins, ...remaining } = resolveOptionsWithDepType;
  33. // check type compat
  34. /** @type {Partial<ResolveOptions>} */
  35. const partialOptions = {
  36. ...remaining,
  37. plugins:
  38. plugins &&
  39. /** @type {ResolvePluginInstance[]} */ (
  40. plugins.filter(item => item !== "...")
  41. )
  42. };
  43. if (!partialOptions.fileSystem) {
  44. throw new Error(
  45. "fileSystem is missing in resolveOptions, but it's required for enhanced-resolve"
  46. );
  47. }
  48. // These weird types validate that we checked all non-optional properties
  49. const options =
  50. /** @type {Partial<ResolveOptions> & Pick<ResolveOptions, "fileSystem">} */ (
  51. partialOptions
  52. );
  53. return removeOperations(
  54. resolveByProperty(options, "byDependency", dependencyType),
  55. // Keep the `unsafeCache` because it can be a `Proxy`
  56. ["unsafeCache"]
  57. );
  58. };
  59. /**
  60. * @typedef {object} ResolverCache
  61. * @property {WeakMap<object, ResolverWithOptions>} direct
  62. * @property {Map<string, ResolverWithOptions>} stringified
  63. */
  64. module.exports = class ResolverFactory {
  65. constructor() {
  66. this.hooks = Object.freeze({
  67. /** @type {HookMap<SyncWaterfallHook<[ResolveOptionsWithDependencyType]>>} */
  68. resolveOptions: new HookMap(
  69. () => new SyncWaterfallHook(["resolveOptions"])
  70. ),
  71. /** @type {HookMap<SyncHook<[Resolver, ResolveOptions, ResolveOptionsWithDependencyType]>>} */
  72. resolver: new HookMap(
  73. () => new SyncHook(["resolver", "resolveOptions", "userResolveOptions"])
  74. )
  75. });
  76. /** @type {Map<string, ResolverCache>} */
  77. this.cache = new Map();
  78. }
  79. /**
  80. * @param {string} type type of resolver
  81. * @param {ResolveOptionsWithDependencyType=} resolveOptions options
  82. * @returns {ResolverWithOptions} the resolver
  83. */
  84. get(type, resolveOptions = EMPTY_RESOLVE_OPTIONS) {
  85. let typedCaches = this.cache.get(type);
  86. if (!typedCaches) {
  87. typedCaches = {
  88. direct: new WeakMap(),
  89. stringified: new Map()
  90. };
  91. this.cache.set(type, typedCaches);
  92. }
  93. const cachedResolver = typedCaches.direct.get(resolveOptions);
  94. if (cachedResolver) {
  95. return cachedResolver;
  96. }
  97. const ident = JSON.stringify(resolveOptions);
  98. const resolver = typedCaches.stringified.get(ident);
  99. if (resolver) {
  100. typedCaches.direct.set(resolveOptions, resolver);
  101. return resolver;
  102. }
  103. const newResolver = this._create(type, resolveOptions);
  104. typedCaches.direct.set(resolveOptions, newResolver);
  105. typedCaches.stringified.set(ident, newResolver);
  106. return newResolver;
  107. }
  108. /**
  109. * @param {string} type type of resolver
  110. * @param {ResolveOptionsWithDependencyType} resolveOptionsWithDepType options
  111. * @returns {ResolverWithOptions} the resolver
  112. */
  113. _create(type, resolveOptionsWithDepType) {
  114. /** @type {ResolveOptionsWithDependencyType} */
  115. const originalResolveOptions = { ...resolveOptionsWithDepType };
  116. const resolveOptions = convertToResolveOptions(
  117. this.hooks.resolveOptions.for(type).call(resolveOptionsWithDepType)
  118. );
  119. const resolver = /** @type {ResolverWithOptions} */ (
  120. Factory.createResolver(resolveOptions)
  121. );
  122. if (!resolver) {
  123. throw new Error("No resolver created");
  124. }
  125. /** @type {WeakMap<Partial<ResolveOptionsWithDependencyType>, ResolverWithOptions>} */
  126. const childCache = new WeakMap();
  127. resolver.withOptions = options => {
  128. const cacheEntry = childCache.get(options);
  129. if (cacheEntry !== undefined) return cacheEntry;
  130. const mergedOptions = cachedCleverMerge(originalResolveOptions, options);
  131. const resolver = this.get(type, mergedOptions);
  132. childCache.set(options, resolver);
  133. return resolver;
  134. };
  135. this.hooks.resolver
  136. .for(type)
  137. .call(resolver, resolveOptions, originalResolveOptions);
  138. return resolver;
  139. }
  140. };