MemoryWithGcCachePlugin.js 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const Cache = require("../Cache");
  7. /** @typedef {import("webpack-sources").Source} Source */
  8. /** @typedef {import("../Cache").Etag} Etag */
  9. /** @typedef {import("../Compiler")} Compiler */
  10. /** @typedef {import("../Module")} Module */
  11. /**
  12. * @typedef {object} MemoryWithGcCachePluginOptions
  13. * @property {number} maxGenerations max generations
  14. */
  15. class MemoryWithGcCachePlugin {
  16. /**
  17. * @param {MemoryWithGcCachePluginOptions} options options
  18. */
  19. constructor({ maxGenerations }) {
  20. this._maxGenerations = maxGenerations;
  21. }
  22. /**
  23. * Apply the plugin
  24. * @param {Compiler} compiler the compiler instance
  25. * @returns {void}
  26. */
  27. apply(compiler) {
  28. const maxGenerations = this._maxGenerations;
  29. /** @type {Map<string, { etag: Etag | null, data: any } | undefined | null>} */
  30. const cache = new Map();
  31. /** @type {Map<string, { entry: { etag: Etag | null, data: any } | null, until: number }>} */
  32. const oldCache = new Map();
  33. let generation = 0;
  34. let cachePosition = 0;
  35. const logger = compiler.getInfrastructureLogger("MemoryWithGcCachePlugin");
  36. compiler.hooks.afterDone.tap("MemoryWithGcCachePlugin", () => {
  37. generation++;
  38. let clearedEntries = 0;
  39. let lastClearedIdentifier;
  40. // Avoid coverage problems due indirect changes
  41. /* istanbul ignore next */
  42. for (const [identifier, entry] of oldCache) {
  43. if (entry.until > generation) break;
  44. oldCache.delete(identifier);
  45. if (cache.get(identifier) === undefined) {
  46. cache.delete(identifier);
  47. clearedEntries++;
  48. lastClearedIdentifier = identifier;
  49. }
  50. }
  51. if (clearedEntries > 0 || oldCache.size > 0) {
  52. logger.log(
  53. `${cache.size - oldCache.size} active entries, ${
  54. oldCache.size
  55. } recently unused cached entries${
  56. clearedEntries > 0
  57. ? `, ${clearedEntries} old unused cache entries removed e. g. ${lastClearedIdentifier}`
  58. : ""
  59. }`
  60. );
  61. }
  62. let i = (cache.size / maxGenerations) | 0;
  63. let j = cachePosition >= cache.size ? 0 : cachePosition;
  64. cachePosition = j + i;
  65. for (const [identifier, entry] of cache) {
  66. if (j !== 0) {
  67. j--;
  68. continue;
  69. }
  70. if (entry !== undefined) {
  71. // We don't delete the cache entry, but set it to undefined instead
  72. // This reserves the location in the data table and avoids rehashing
  73. // when constantly adding and removing entries.
  74. // It will be deleted when removed from oldCache.
  75. cache.set(identifier, undefined);
  76. oldCache.delete(identifier);
  77. oldCache.set(identifier, {
  78. entry,
  79. until: generation + maxGenerations
  80. });
  81. if (i-- === 0) break;
  82. }
  83. }
  84. });
  85. compiler.cache.hooks.store.tap(
  86. { name: "MemoryWithGcCachePlugin", stage: Cache.STAGE_MEMORY },
  87. (identifier, etag, data) => {
  88. cache.set(identifier, { etag, data });
  89. }
  90. );
  91. compiler.cache.hooks.get.tap(
  92. { name: "MemoryWithGcCachePlugin", stage: Cache.STAGE_MEMORY },
  93. (identifier, etag, gotHandlers) => {
  94. const cacheEntry = cache.get(identifier);
  95. if (cacheEntry === null) {
  96. return null;
  97. } else if (cacheEntry !== undefined) {
  98. return cacheEntry.etag === etag ? cacheEntry.data : null;
  99. }
  100. const oldCacheEntry = oldCache.get(identifier);
  101. if (oldCacheEntry !== undefined) {
  102. const cacheEntry = oldCacheEntry.entry;
  103. if (cacheEntry === null) {
  104. oldCache.delete(identifier);
  105. cache.set(identifier, cacheEntry);
  106. return null;
  107. }
  108. if (cacheEntry.etag !== etag) return null;
  109. oldCache.delete(identifier);
  110. cache.set(identifier, cacheEntry);
  111. return cacheEntry.data;
  112. }
  113. gotHandlers.push((result, callback) => {
  114. if (result === undefined) {
  115. cache.set(identifier, null);
  116. } else {
  117. cache.set(identifier, { etag, data: result });
  118. }
  119. return callback();
  120. });
  121. }
  122. );
  123. compiler.cache.hooks.shutdown.tap(
  124. { name: "MemoryWithGcCachePlugin", stage: Cache.STAGE_MEMORY },
  125. () => {
  126. cache.clear();
  127. oldCache.clear();
  128. }
  129. );
  130. }
  131. }
  132. module.exports = MemoryWithGcCachePlugin;