FlagIncludedChunksPlugin.js 4.1 KB

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