FlagIncludedChunksPlugin.js 4.6 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 { compareIds } = require("../util/comparators");
  7. /** @import Chunk, { ChunkId } from "../Chunk" */
  8. /** @import Compiler from "../Compiler" */
  9. /** @import Module from "../Module" */
  10. const PLUGIN_NAME = "FlagIncludedChunksPlugin";
  11. class FlagIncludedChunksPlugin {
  12. /**
  13. * Applies the plugin by registering its hooks on the compiler.
  14. * @param {Compiler} compiler the compiler instance
  15. * @returns {void}
  16. */
  17. apply(compiler) {
  18. compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
  19. compilation.hooks.optimizeChunkIds.tap(PLUGIN_NAME, (chunks) => {
  20. const chunkGraph = compilation.chunkGraph;
  21. // prepare two bit integers for each module
  22. // 2^31 is the max number represented as SMI in v8
  23. // we want the bits distributed this way:
  24. // the bit 2^31 is pretty rar and only one module should get it
  25. // so it has a probability of 1 / modulesCount
  26. // the first bit (2^0) is the easiest and every module could get it
  27. // if it doesn't get a better bit
  28. // from bit 2^n to 2^(n+1) there is a probability of p
  29. // so 1 / modulesCount == p^31
  30. // <=> p = sqrt31(1 / modulesCount)
  31. // so we use a modulo of 1 / sqrt31(1 / modulesCount)
  32. /** @type {WeakMap<Module, number>} */
  33. const moduleBits = new WeakMap();
  34. const modulesCount = compilation.modules.size;
  35. // precalculate the modulo values for each bit
  36. const modulo = 1 / (1 / modulesCount) ** (1 / 31);
  37. /** @type {number[]} */
  38. const modulos = Array.from(
  39. { length: 31 },
  40. /**
  41. * Handles the callback logic for this hook.
  42. * @param {number} x x
  43. * @param {number} i i
  44. * @returns {number} result
  45. */
  46. (x, i) => (modulo ** i) | 0
  47. );
  48. // iterate all modules to generate bit values
  49. let i = 0;
  50. for (const module of compilation.modules) {
  51. let bit = 30;
  52. while (i % modulos[bit] !== 0) {
  53. bit--;
  54. }
  55. moduleBits.set(module, 1 << bit);
  56. i++;
  57. }
  58. // iterate all chunks to generate bitmaps
  59. /** @type {WeakMap<Chunk, number>} */
  60. const chunkModulesHash = new WeakMap();
  61. for (const chunk of chunks) {
  62. let hash = 0;
  63. for (const module of chunkGraph.getChunkModulesIterable(chunk)) {
  64. hash |= /** @type {number} */ (moduleBits.get(module));
  65. }
  66. chunkModulesHash.set(chunk, hash);
  67. }
  68. // Chunks that received an included id and need their `ids` re-sorted
  69. // once, after all pushes — instead of re-sorting on every push
  70. // inside the nested loop below (https://github.com/webpack/webpack/issues/18837).
  71. /** @type {Set<Chunk>} */
  72. const chunksWithIncludedIds = new Set();
  73. for (const chunkA of chunks) {
  74. const chunkAHash =
  75. /** @type {number} */
  76. (chunkModulesHash.get(chunkA));
  77. const chunkAModulesCount = chunkGraph.getNumberOfChunkModules(chunkA);
  78. if (chunkAModulesCount === 0) continue;
  79. /** @type {undefined | Module} */
  80. let bestModule;
  81. for (const module of chunkGraph.getChunkModulesIterable(chunkA)) {
  82. if (
  83. bestModule === undefined ||
  84. chunkGraph.getNumberOfModuleChunks(bestModule) >
  85. chunkGraph.getNumberOfModuleChunks(module)
  86. ) {
  87. bestModule = module;
  88. }
  89. }
  90. loopB: for (const chunkB of chunkGraph.getModuleChunksIterable(
  91. /** @type {Module} */ (bestModule)
  92. )) {
  93. // as we iterate the same iterables twice
  94. // skip if we find ourselves
  95. if (chunkA === chunkB) continue;
  96. const chunkBModulesCount =
  97. chunkGraph.getNumberOfChunkModules(chunkB);
  98. // ids for empty chunks are not included
  99. if (chunkBModulesCount === 0) continue;
  100. // instead of swapping A and B just bail
  101. // as we loop twice the current A will be B and B then A
  102. if (chunkAModulesCount > chunkBModulesCount) continue;
  103. // is chunkA in chunkB?
  104. // we do a cheap check for the hash value
  105. const chunkBHash =
  106. /** @type {number} */
  107. (chunkModulesHash.get(chunkB));
  108. if ((chunkBHash & chunkAHash) !== chunkAHash) continue;
  109. // compare all modules
  110. for (const m of chunkGraph.getChunkModulesIterable(chunkA)) {
  111. if (!chunkGraph.isModuleInChunk(m, chunkB)) continue loopB;
  112. }
  113. /** @type {ChunkId[]} */
  114. (chunkB.ids).push(/** @type {ChunkId} */ (chunkA.id));
  115. chunksWithIncludedIds.add(chunkB);
  116. }
  117. }
  118. // Sort each affected chunk's ids once (https://github.com/webpack/webpack/issues/18837).
  119. for (const chunk of chunksWithIncludedIds) {
  120. /** @type {ChunkId[]} */
  121. (chunk.ids).sort(compareIds);
  122. }
  123. });
  124. });
  125. }
  126. }
  127. module.exports = FlagIncludedChunksPlugin;