CacheEffectivenessPlugin.js 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Alexander Akait @alexander-akait
  4. */
  5. "use strict";
  6. const RuntimeModule = require("../RuntimeModule");
  7. const CacheEffectivenessWarning = require("../errors/CacheEffectivenessWarning");
  8. /** @import { PerformanceOptions } from "../../declarations/WebpackOptions" */
  9. /** @import Compiler from "../Compiler" */
  10. /** @import { BuildInfo } from "../Module" */
  11. const PLUGIN_NAME = "CacheEffectivenessPlugin";
  12. // A reason shared by many modules is the one worth fixing, so report the
  13. // frequent ones and count the rest.
  14. const MAX_REPORTED_REASONS = 3;
  15. const UNSPECIFIED_REASON = "reason not stated";
  16. class CacheEffectivenessPlugin {
  17. /**
  18. * Creates an instance of CacheEffectivenessPlugin.
  19. * @param {PerformanceOptions} options the plugin options
  20. */
  21. constructor(options) {
  22. /** @type {PerformanceOptions["hints"]} */
  23. this.hints = options.hints;
  24. }
  25. /**
  26. * Applies the plugin by registering its hooks on the compiler.
  27. * @param {Compiler} compiler the compiler instance
  28. * @returns {void}
  29. */
  30. apply(compiler) {
  31. const hints = this.hints;
  32. if (!hints) return;
  33. compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
  34. compilation.hooks.afterSeal.tap(PLUGIN_NAME, () => {
  35. let total = 0;
  36. let rebuilt = 0;
  37. let uncacheable = 0;
  38. /** @type {Map<string, number>} */
  39. const modulesByReason = new Map();
  40. for (const module of compilation.modules) {
  41. // Runtime modules are generated per compilation, never built and
  42. // never cached, so counting them makes a cold build look warm.
  43. if (module instanceof RuntimeModule) continue;
  44. total++;
  45. if (compilation.builtModules.has(module)) rebuilt++;
  46. // Every built module carries one; `cacheable` being `undefined` is
  47. // the module never reporting either way, not it opting out.
  48. const buildInfo = /** @type {BuildInfo} */ (module.buildInfo);
  49. if (buildInfo.cacheable !== false) continue;
  50. uncacheable++;
  51. const reasons =
  52. buildInfo.notCacheableReasons &&
  53. buildInfo.notCacheableReasons.length > 0
  54. ? buildInfo.notCacheableReasons
  55. : [UNSPECIFIED_REASON];
  56. for (const reason of reasons) {
  57. modulesByReason.set(reason, (modulesByReason.get(reason) || 0) + 1);
  58. }
  59. }
  60. // Work redone despite a warm cache needs both ends: everything rebuilt
  61. // is a cold build, nothing rebuilt is the cache working.
  62. const warmRebuild = rebuilt > 0 && rebuilt < total;
  63. if (!warmRebuild && uncacheable === 0) return;
  64. // Most frequent first, and by reason where two are as frequent: the
  65. // map is keyed in the order the modules were walked, which is not the
  66. // same twice, and only three reasons are reported — so an unbroken tie
  67. // would vary which of them a build names at all.
  68. const sorted = [...modulesByReason].sort(
  69. (a, b) => b[1] - a[1] || (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0)
  70. );
  71. const reasons = sorted
  72. .slice(0, MAX_REPORTED_REASONS)
  73. .map(
  74. ([reason, modules]) =>
  75. `${reason} (${modules} ${modules === 1 ? "module" : "modules"})`
  76. );
  77. const remaining = sorted.length - reasons.length;
  78. if (remaining > 0) {
  79. reasons.push(
  80. `and ${remaining} other ${remaining === 1 ? "reason" : "reasons"}`
  81. );
  82. }
  83. const warning = new CacheEffectivenessWarning({
  84. total,
  85. rebuilt,
  86. uncacheable,
  87. reasons
  88. });
  89. if (hints === "error") {
  90. compilation.errors.push(warning);
  91. } else if (hints === "stats") {
  92. compilation.hints.push(warning);
  93. } else {
  94. compilation.warnings.push(warning);
  95. }
  96. });
  97. });
  98. }
  99. }
  100. module.exports = CacheEffectivenessPlugin;