MergeDuplicateChunksPlugin.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const { STAGE_BASIC } = require("../OptimizationStages");
  7. const { runtimeEqual } = require("../util/runtime");
  8. /**
  9. * @import {
  10. * MergeDuplicateChunksPluginOptions
  11. * } from "../../declarations/plugins/optimize/MergeDuplicateChunksPlugin"
  12. */
  13. /** @import Compiler from "../Compiler" */
  14. /** @import Chunk from "../Chunk" */
  15. const PLUGIN_NAME = "MergeDuplicateChunksPlugin";
  16. class MergeDuplicateChunksPlugin {
  17. /**
  18. * Creates an instance of MergeDuplicateChunksPlugin.
  19. * @param {MergeDuplicateChunksPluginOptions=} options options object
  20. */
  21. constructor(options = { stage: STAGE_BASIC }) {
  22. /** @type {MergeDuplicateChunksPluginOptions} */
  23. this.options = options;
  24. }
  25. /**
  26. * Applies the plugin by registering its hooks on the compiler.
  27. * @param {Compiler} compiler the compiler
  28. * @returns {void}
  29. */
  30. apply(compiler) {
  31. compiler.hooks.validate.tap(PLUGIN_NAME, () => {
  32. compiler.validate(
  33. () =>
  34. require("../../schemas/plugins/optimize/MergeDuplicateChunksPlugin.json"),
  35. this.options,
  36. {
  37. name: "Merge Duplicate Chunks Plugin",
  38. baseDataPath: "options"
  39. },
  40. (options) =>
  41. require("../../schemas/plugins/optimize/MergeDuplicateChunksPlugin.check")(
  42. options
  43. )
  44. );
  45. });
  46. compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
  47. compilation.hooks.optimizeChunks.tap(
  48. {
  49. name: PLUGIN_NAME,
  50. stage: this.options.stage
  51. },
  52. (chunks) => {
  53. const { chunkGraph, moduleGraph } = compilation;
  54. // remember already tested chunks for performance
  55. /** @type {Set<Chunk>} */
  56. const notDuplicates = new Set();
  57. // for each chunk
  58. for (const chunk of chunks) {
  59. // track a Set of all chunk that could be duplicates
  60. /** @type {Set<Chunk> | undefined} */
  61. let possibleDuplicates;
  62. for (const module of chunkGraph.getChunkModulesIterable(chunk)) {
  63. if (possibleDuplicates === undefined) {
  64. // when possibleDuplicates is not yet set,
  65. // create a new Set from chunks of the current module
  66. // including only chunks with the same number of modules
  67. for (const dup of chunkGraph.getModuleChunksIterable(module)) {
  68. if (
  69. dup !== chunk &&
  70. chunkGraph.getNumberOfChunkModules(chunk) ===
  71. chunkGraph.getNumberOfChunkModules(dup) &&
  72. !notDuplicates.has(dup)
  73. ) {
  74. // delay allocating the new Set until here, reduce memory pressure
  75. if (possibleDuplicates === undefined) {
  76. possibleDuplicates = new Set();
  77. }
  78. possibleDuplicates.add(dup);
  79. }
  80. }
  81. // when no chunk is possible we can break here
  82. if (possibleDuplicates === undefined) break;
  83. } else {
  84. // validate existing possible duplicates
  85. for (const dup of possibleDuplicates) {
  86. // remove possible duplicate when module is not contained
  87. if (!chunkGraph.isModuleInChunk(module, dup)) {
  88. possibleDuplicates.delete(dup);
  89. }
  90. }
  91. // when all chunks has been removed we can break here
  92. if (possibleDuplicates.size === 0) break;
  93. }
  94. }
  95. // when we found duplicates
  96. if (
  97. possibleDuplicates !== undefined &&
  98. possibleDuplicates.size > 0
  99. ) {
  100. outer: for (const otherChunk of possibleDuplicates) {
  101. if (otherChunk.hasRuntime() !== chunk.hasRuntime()) continue;
  102. if (chunkGraph.getNumberOfEntryModules(chunk) > 0) continue;
  103. if (chunkGraph.getNumberOfEntryModules(otherChunk) > 0) {
  104. continue;
  105. }
  106. if (!runtimeEqual(chunk.runtime, otherChunk.runtime)) {
  107. for (const module of chunkGraph.getChunkModulesIterable(
  108. chunk
  109. )) {
  110. const exportsInfo = moduleGraph.getExportsInfo(module);
  111. if (
  112. !exportsInfo.isEquallyUsed(
  113. chunk.runtime,
  114. otherChunk.runtime
  115. )
  116. ) {
  117. continue outer;
  118. }
  119. }
  120. }
  121. // merge them
  122. if (chunkGraph.canChunksBeIntegrated(chunk, otherChunk)) {
  123. chunkGraph.integrateChunks(chunk, otherChunk);
  124. compilation.chunks.delete(otherChunk);
  125. }
  126. }
  127. }
  128. // don't check already processed chunks twice
  129. notDuplicates.add(chunk);
  130. }
  131. }
  132. );
  133. });
  134. }
  135. }
  136. module.exports = MergeDuplicateChunksPlugin;