UnsafeCachePlugin.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const { cachedJoin } = require("./util/path");
  7. /** @typedef {import("./Resolver")} Resolver */
  8. /** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
  9. /** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
  10. /** @typedef {import("./Resolver").ResolveContextYield} ResolveContextYield */
  11. /** @typedef {{ [k: string]: undefined | ResolveRequest | ResolveRequest[] }} Cache */
  12. const RELATIVE_REQUEST_REGEXP = /^\.\.?(?:\/|$)/;
  13. /**
  14. * @param {string} relativePath relative path from package root
  15. * @param {string} request relative request
  16. * @returns {string} normalized request with a preserved leading dot
  17. */
  18. function joinRelativePreservingLeadingDot(relativePath, request) {
  19. const normalized = cachedJoin(relativePath, request);
  20. return RELATIVE_REQUEST_REGEXP.test(normalized)
  21. ? normalized
  22. : `./${normalized}`;
  23. }
  24. /**
  25. * @param {ResolveRequest} request request
  26. * @returns {string | false | undefined} normalized path
  27. */
  28. function getCachePath(request) {
  29. if (request.descriptionFileRoot && !request.module) {
  30. return request.descriptionFileRoot;
  31. }
  32. return request.path;
  33. }
  34. /**
  35. * @param {ResolveRequest} request request
  36. * @returns {string | undefined} normalized request string
  37. */
  38. function getCacheRequest(request) {
  39. const requestString = request.request;
  40. if (
  41. !requestString ||
  42. !request.relativePath ||
  43. !RELATIVE_REQUEST_REGEXP.test(requestString)
  44. ) {
  45. return requestString;
  46. }
  47. return joinRelativePreservingLeadingDot(request.relativePath, requestString);
  48. }
  49. /**
  50. * @param {string} type type of cache
  51. * @param {ResolveRequest} request request
  52. * @param {boolean} withContext cache with context?
  53. * @returns {string} cache id
  54. */
  55. function getCacheId(type, request, withContext) {
  56. return JSON.stringify({
  57. type,
  58. context: withContext ? request.context : "",
  59. path: getCachePath(request),
  60. query: request.query,
  61. fragment: request.fragment,
  62. request: getCacheRequest(request),
  63. });
  64. }
  65. module.exports = class UnsafeCachePlugin {
  66. /**
  67. * @param {string | ResolveStepHook} source source
  68. * @param {(request: ResolveRequest) => boolean} filterPredicate filterPredicate
  69. * @param {Cache} cache cache
  70. * @param {boolean} withContext withContext
  71. * @param {string | ResolveStepHook} target target
  72. */
  73. constructor(source, filterPredicate, cache, withContext, target) {
  74. this.source = source;
  75. this.filterPredicate = filterPredicate;
  76. this.withContext = withContext;
  77. this.cache = cache;
  78. this.target = target;
  79. }
  80. /**
  81. * @param {Resolver} resolver the resolver
  82. * @returns {void}
  83. */
  84. apply(resolver) {
  85. const target = resolver.ensureHook(this.target);
  86. resolver
  87. .getHook(this.source)
  88. .tapAsync("UnsafeCachePlugin", (request, resolveContext, callback) => {
  89. if (!this.filterPredicate(request)) {
  90. return resolver.doResolve(
  91. target,
  92. request,
  93. null,
  94. resolveContext,
  95. callback,
  96. );
  97. }
  98. const isYield = typeof resolveContext.yield === "function";
  99. const cacheId = getCacheId(
  100. isYield ? "yield" : "default",
  101. request,
  102. this.withContext,
  103. );
  104. const cacheEntry = this.cache[cacheId];
  105. if (cacheEntry) {
  106. if (isYield) {
  107. const yield_ =
  108. /** @type {ResolveContextYield} */
  109. (resolveContext.yield);
  110. if (Array.isArray(cacheEntry)) {
  111. for (const result of cacheEntry) yield_(result);
  112. } else {
  113. yield_(cacheEntry);
  114. }
  115. return callback(null, null);
  116. }
  117. return callback(null, /** @type {ResolveRequest} */ (cacheEntry));
  118. }
  119. /** @type {ResolveContextYield | undefined} */
  120. let yieldFn;
  121. /** @type {ResolveContextYield | undefined} */
  122. let yield_;
  123. /** @type {ResolveRequest[]} */
  124. const yieldResult = [];
  125. if (isYield) {
  126. yieldFn = resolveContext.yield;
  127. yield_ = (result) => {
  128. yieldResult.push(result);
  129. };
  130. }
  131. resolver.doResolve(
  132. target,
  133. request,
  134. null,
  135. yield_ ? { ...resolveContext, yield: yield_ } : resolveContext,
  136. (err, result) => {
  137. if (err) return callback(err);
  138. if (isYield) {
  139. if (result) yieldResult.push(result);
  140. for (const result of yieldResult) {
  141. /** @type {ResolveContextYield} */
  142. (yieldFn)(result);
  143. }
  144. this.cache[cacheId] = yieldResult;
  145. return callback(null, null);
  146. }
  147. if (result) return callback(null, (this.cache[cacheId] = result));
  148. callback();
  149. },
  150. );
  151. });
  152. }
  153. };