MergeDuplicateChunksPlugin.js 4.4 KB

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