AggressiveMergingPlugin.js 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const { STAGE_ADVANCED } = require("../OptimizationStages");
  7. /** @import Chunk from "../Chunk" */
  8. /** @import Compiler from "../Compiler" */
  9. /**
  10. * Defines the aggressive merging plugin options type used by this module.
  11. * @typedef {object} AggressiveMergingPluginOptions
  12. * @property {number=} minSizeReduce minimal size reduction to trigger merging
  13. */
  14. const PLUGIN_NAME = "AggressiveMergingPlugin";
  15. class AggressiveMergingPlugin {
  16. /**
  17. * Creates an instance of AggressiveMergingPlugin.
  18. * @param {AggressiveMergingPluginOptions=} options options object
  19. */
  20. constructor(options) {
  21. if (
  22. (options !== undefined && typeof options !== "object") ||
  23. Array.isArray(options)
  24. ) {
  25. throw new Error(
  26. "Argument should be an options object. To use defaults, pass in nothing.\nFor more info on options, see https://webpack.js.org/plugins/"
  27. );
  28. }
  29. /** @type {AggressiveMergingPluginOptions} */
  30. this.options = options || {};
  31. }
  32. /**
  33. * Applies the plugin by registering its hooks on the compiler.
  34. * @param {Compiler} compiler the compiler instance
  35. * @returns {void}
  36. */
  37. apply(compiler) {
  38. const options = this.options;
  39. const minSizeReduce = options.minSizeReduce || 1.5;
  40. compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => {
  41. compilation.hooks.optimizeChunks.tap(
  42. {
  43. name: PLUGIN_NAME,
  44. stage: STAGE_ADVANCED
  45. },
  46. (chunks) => {
  47. const chunkGraph = compilation.chunkGraph;
  48. // Only the single best pair is merged per pass (the hook re-runs
  49. // after each merge), so track the max directly instead of building
  50. // and sorting the full O(chunks²) list of pairs every pass. A strict
  51. // `>` keeps the first-encountered best, matching the previous stable
  52. // descending sort's `[0]`.
  53. /** @type {Chunk | undefined} */
  54. let bestA;
  55. /** @type {Chunk | undefined} */
  56. let bestB;
  57. let bestImprovement = -Infinity;
  58. for (const a of chunks) {
  59. if (a.canBeInitial()) continue;
  60. for (const b of chunks) {
  61. if (b.canBeInitial()) continue;
  62. if (b === a) break;
  63. if (!chunkGraph.canChunksBeIntegrated(a, b)) {
  64. continue;
  65. }
  66. const aSize = chunkGraph.getChunkSize(b, {
  67. chunkOverhead: 0
  68. });
  69. const bSize = chunkGraph.getChunkSize(a, {
  70. chunkOverhead: 0
  71. });
  72. const abSize = chunkGraph.getIntegratedChunksSize(b, a, {
  73. chunkOverhead: 0
  74. });
  75. const improvement = (aSize + bSize) / abSize;
  76. if (improvement > bestImprovement) {
  77. bestImprovement = improvement;
  78. bestA = a;
  79. bestB = b;
  80. }
  81. }
  82. }
  83. if (bestA === undefined) return;
  84. if (bestImprovement < minSizeReduce) return;
  85. chunkGraph.integrateChunks(/** @type {Chunk} */ (bestB), bestA);
  86. compilation.chunks.delete(bestA);
  87. return true;
  88. }
  89. );
  90. });
  91. }
  92. }
  93. module.exports = AggressiveMergingPlugin;