MergeDuplicateChunksPlugin.js 4.3 KB

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