UnsafeCachePlugin.js 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const { isRelativeRequest } = 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. /**
  13. * @param {string} relativePath relative path from package root
  14. * @param {string} request relative request
  15. * @param {Resolver} resolver resolver instance
  16. * @returns {string} normalized request with a preserved leading dot
  17. */
  18. function joinRelativePreservingLeadingDot(relativePath, request, resolver) {
  19. const normalized = resolver.join(relativePath, request);
  20. return isRelativeRequest(normalized) ? normalized : `./${normalized}`;
  21. }
  22. /**
  23. * @param {ResolveRequest} request request
  24. * @returns {string | false | undefined} normalized path
  25. */
  26. function getCachePath(request) {
  27. if (request.descriptionFileRoot && !request.module) {
  28. return request.descriptionFileRoot;
  29. }
  30. return request.path;
  31. }
  32. /**
  33. * @param {ResolveRequest} request request
  34. * @param {Resolver} resolver resolver instance
  35. * @returns {string | undefined} normalized request string
  36. */
  37. function getCacheRequest(request, resolver) {
  38. const requestString = request.request;
  39. if (
  40. !requestString ||
  41. !request.relativePath ||
  42. !isRelativeRequest(requestString)
  43. ) {
  44. return requestString;
  45. }
  46. return joinRelativePreservingLeadingDot(
  47. request.relativePath,
  48. requestString,
  49. resolver,
  50. );
  51. }
  52. // Cache-key separator: `\0` is safe because paths, requests, queries and
  53. // fragments produced by `parseIdentifier` never contain a raw NUL (the
  54. // \0-escape in identifier.js is decoded back to the original char), and the
  55. // context, when included, is passed through `JSON.stringify`, which escapes
  56. // any NUL to \u0000.
  57. // const SEP = "\0";
  58. /**
  59. * Build the cache id for a request. Called on every `described-resolve`
  60. * invocation when `unsafeCache` is on, so it's a hot path.
  61. *
  62. * Equivalent in meaning to the previous `JSON.stringify({ ... })` form, but
  63. * ~3–5× faster since we avoid the object allocation and JSON serializer for
  64. * the fields that are already plain strings.
  65. * @param {string} type type of cache
  66. * @param {ResolveRequest} request request
  67. * @param {boolean} withContext cache with context?
  68. * @param {Resolver} resolver resolver instance
  69. * @returns {string} cache id
  70. */
  71. function getCacheId(type, request, withContext, resolver) {
  72. // TODO use it in the next major release, it is faster
  73. // const contextPart = withContext ? JSON.stringify(request.context) : "";
  74. // const path = getCachePath(request);
  75. // const cacheRequest = getCacheRequest(request, resolver);
  76. // return (
  77. // type +
  78. // SEP +
  79. // contextPart +
  80. // SEP +
  81. // (path || "") +
  82. // SEP +
  83. // (request.query || "") +
  84. // SEP +
  85. // (request.fragment || "") +
  86. // SEP +
  87. // (cacheRequest || "")
  88. // );
  89. return JSON.stringify({
  90. type,
  91. context: withContext ? request.context : "",
  92. path: getCachePath(request),
  93. query: request.query,
  94. fragment: request.fragment,
  95. request: getCacheRequest(request, resolver),
  96. });
  97. }
  98. module.exports = class UnsafeCachePlugin {
  99. /**
  100. * @param {string | ResolveStepHook} source source
  101. * @param {(request: ResolveRequest) => boolean} filterPredicate filterPredicate
  102. * @param {Cache} cache cache
  103. * @param {boolean} withContext withContext
  104. * @param {string | ResolveStepHook} target target
  105. */
  106. constructor(source, filterPredicate, cache, withContext, target) {
  107. this.source = source;
  108. this.filterPredicate = filterPredicate;
  109. this.withContext = withContext;
  110. this.cache = cache;
  111. this.target = target;
  112. }
  113. /**
  114. * @param {Resolver} resolver the resolver
  115. * @returns {void}
  116. */
  117. apply(resolver) {
  118. const target = resolver.ensureHook(this.target);
  119. resolver
  120. .getHook(this.source)
  121. .tapAsync("UnsafeCachePlugin", (request, resolveContext, callback) => {
  122. if (!this.filterPredicate(request)) {
  123. return resolver.doResolve(
  124. target,
  125. request,
  126. null,
  127. resolveContext,
  128. callback,
  129. );
  130. }
  131. const isYield = typeof resolveContext.yield === "function";
  132. const cacheId = getCacheId(
  133. isYield ? "yield" : "default",
  134. request,
  135. this.withContext,
  136. resolver,
  137. );
  138. const cacheEntry = this.cache[cacheId];
  139. if (cacheEntry) {
  140. if (isYield) {
  141. const yield_ =
  142. /** @type {ResolveContextYield} */
  143. (resolveContext.yield);
  144. if (Array.isArray(cacheEntry)) {
  145. for (const result of cacheEntry) yield_(result);
  146. } else {
  147. yield_(cacheEntry);
  148. }
  149. return callback(null, null);
  150. }
  151. return callback(null, /** @type {ResolveRequest} */ (cacheEntry));
  152. }
  153. /** @type {ResolveContextYield | undefined} */
  154. let yieldFn;
  155. /** @type {ResolveContextYield | undefined} */
  156. let yield_;
  157. /** @type {ResolveRequest[]} */
  158. const yieldResult = [];
  159. if (isYield) {
  160. yieldFn = resolveContext.yield;
  161. yield_ = (result) => {
  162. yieldResult.push(result);
  163. };
  164. }
  165. resolver.doResolve(
  166. target,
  167. request,
  168. null,
  169. yield_ ? { ...resolveContext, yield: yield_ } : resolveContext,
  170. (err, result) => {
  171. if (err) return callback(err);
  172. if (isYield) {
  173. if (result) yieldResult.push(result);
  174. for (const result of yieldResult) {
  175. /** @type {ResolveContextYield} */
  176. (yieldFn)(result);
  177. }
  178. this.cache[cacheId] = yieldResult;
  179. return callback(null, null);
  180. }
  181. if (result) return callback(null, (this.cache[cacheId] = result));
  182. callback();
  183. },
  184. );
  185. });
  186. }
  187. };