DuplicateModulesPlugin.js 2.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Alexander Akait @alexander-akait
  4. */
  5. "use strict";
  6. const DuplicateModulesWarning = require("../errors/DuplicateModulesWarning");
  7. const { compareStrings } = require("../util/comparators");
  8. const getModuleSize = require("./getModuleSize");
  9. /** @import { PerformanceOptions } from "../../declarations/WebpackOptions" */
  10. /** @import Compiler from "../Compiler" */
  11. /**
  12. * @import {
  13. * DuplicateModuleDetails
  14. * } from "../errors/DuplicateModulesWarning"
  15. */
  16. const PLUGIN_NAME = "DuplicateModulesPlugin";
  17. // Enough to name the offenders without printing the module graph.
  18. const MAX_REPORTED_MODULES = 5;
  19. class DuplicateModulesPlugin {
  20. /**
  21. * Creates an instance of DuplicateModulesPlugin.
  22. * @param {PerformanceOptions} options the plugin options
  23. */
  24. constructor(options) {
  25. /** @type {PerformanceOptions["hints"]} */
  26. this.hints = options.hints;
  27. }
  28. /**
  29. * Applies the plugin by registering its hooks on the compiler.
  30. * @param {Compiler} compiler the compiler instance
  31. * @returns {void}
  32. */
  33. apply(compiler) {
  34. const hints = this.hints;
  35. if (!hints) return;
  36. compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
  37. compilation.hooks.afterSeal.tap(PLUGIN_NAME, () => {
  38. const { chunkGraph, requestShortener } = compilation;
  39. /** @type {DuplicateModuleDetails[]} */
  40. const duplicated = [];
  41. let wasted = 0;
  42. for (const module of compilation.modules) {
  43. const chunks = chunkGraph.getNumberOfModuleChunks(module);
  44. if (chunks < 2) continue;
  45. // The first copy is the one that had to be emitted; the rest are
  46. // what a shared chunk would save.
  47. const extra = getModuleSize(module) * (chunks - 1);
  48. wasted += extra;
  49. duplicated.push({
  50. name: module.readableIdentifier(requestShortener),
  51. chunks,
  52. wasted: extra
  53. });
  54. }
  55. if (duplicated.length === 0) return;
  56. // Ties break by name: which modules finish first is not stable.
  57. duplicated.sort(
  58. (a, b) => b.wasted - a.wasted || compareStrings(a.name, b.name)
  59. );
  60. const warning = new DuplicateModulesWarning(
  61. duplicated.slice(0, MAX_REPORTED_MODULES),
  62. wasted
  63. );
  64. if (hints === "error") {
  65. compilation.errors.push(warning);
  66. } else if (hints === "stats") {
  67. compilation.hints.push(warning);
  68. } else {
  69. compilation.warnings.push(warning);
  70. }
  71. });
  72. });
  73. }
  74. }
  75. module.exports = DuplicateModulesPlugin;