LargeModulesPlugin.js 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Alexander Akait @alexander-akait
  4. */
  5. "use strict";
  6. const LargeModulesWarning = require("../errors/LargeModulesWarning");
  7. const { compareStrings } = require("../util/comparators");
  8. const getModuleSize = require("./getModuleSize");
  9. const getSourceModules = require("./getSourceModules");
  10. /** @typedef {import("../../declarations/WebpackOptions").PerformanceOptions} PerformanceOptions */
  11. /** @typedef {import("../Compiler")} Compiler */
  12. /** @typedef {import("../Module")} Module */
  13. /** @typedef {import("../errors/LargeModulesWarning").LargeModuleDetails} LargeModuleDetails */
  14. const PLUGIN_NAME = "LargeModulesPlugin";
  15. // Enough to name the offenders without listing the module graph.
  16. const MAX_REPORTED_MODULES = 5;
  17. // Half of a chunk in a single module is where the chunk really is that module.
  18. // Below it the weight is spread around and there is nothing single to act on.
  19. const DOMINANT_SHARE = 0.5;
  20. // Scaled to the asset budget rather than fixed, so raising `maxAssetSize`
  21. // raises what counts as big enough to be worth naming.
  22. const MIN_SHARE_OF_ASSET_LIMIT = 0.2;
  23. class LargeModulesPlugin {
  24. /**
  25. * Creates an instance of LargeModulesPlugin.
  26. * @param {PerformanceOptions} options the plugin options
  27. */
  28. constructor(options) {
  29. /** @type {PerformanceOptions["hints"]} */
  30. this.hints = options.hints;
  31. /** @type {PerformanceOptions["maxAssetSize"]} */
  32. this.maxAssetSize = options.maxAssetSize;
  33. }
  34. /**
  35. * Applies the plugin by registering its hooks on the compiler.
  36. * @param {Compiler} compiler the compiler instance
  37. * @returns {void}
  38. */
  39. apply(compiler) {
  40. const hints = this.hints;
  41. if (!hints) return;
  42. const minSize =
  43. /** @type {number} */ (this.maxAssetSize) * MIN_SHARE_OF_ASSET_LIMIT;
  44. compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
  45. // `afterSeal` is past the hash, which folds every message into it — a
  46. // hint reported earlier would change the build's identity.
  47. compilation.hooks.afterSeal.tap(PLUGIN_NAME, () => {
  48. const { chunkGraph, requestShortener } = compilation;
  49. /** @type {Map<Module, LargeModuleDetails>} */
  50. const worst = new Map();
  51. for (const chunk of compilation.chunks) {
  52. const chunkName = chunk.name || `${chunk.id}`;
  53. /** @type {Map<Module, number>} */
  54. const sizes = new Map();
  55. let chunkSize = 0;
  56. for (const module of chunkGraph.getChunkModulesIterable(chunk)) {
  57. // Through scope hoisting one module stands for the several it
  58. // absorbed, and it is those the report has to name.
  59. for (const inner of getSourceModules(module)) {
  60. const size = getModuleSize(inner);
  61. sizes.set(inner, (sizes.get(inner) || 0) + size);
  62. chunkSize += size;
  63. }
  64. }
  65. for (const [module, size] of sizes) {
  66. if (size < minSize) continue;
  67. if (size < chunkSize * DOMINANT_SHARE) continue;
  68. const previous = worst.get(module);
  69. // A module weighs the same in every chunk holding it, so the one
  70. // named is the first by name — chunk order is not stable.
  71. if (previous && compareStrings(previous.chunk, chunkName) <= 0) {
  72. continue;
  73. }
  74. worst.set(module, {
  75. name: module.readableIdentifier(requestShortener),
  76. chunk: chunkName,
  77. size,
  78. chunkSize
  79. });
  80. }
  81. }
  82. if (worst.size === 0) return;
  83. const large = [...worst.values()];
  84. // Ties break by name: chunk iteration order is not stable.
  85. large.sort((a, b) => b.size - a.size || compareStrings(a.name, b.name));
  86. const warning = new LargeModulesWarning(
  87. large.slice(0, MAX_REPORTED_MODULES),
  88. large.length
  89. );
  90. if (hints === "error") {
  91. compilation.errors.push(warning);
  92. } else if (hints === "stats") {
  93. compilation.hints.push(warning);
  94. } else {
  95. compilation.warnings.push(warning);
  96. }
  97. });
  98. });
  99. }
  100. }
  101. module.exports = LargeModulesPlugin;