You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

99 lines
3.1 KiB

4 years ago
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. class FlagIncludedChunksPlugin {
  7. apply(compiler) {
  8. compiler.hooks.compilation.tap("FlagIncludedChunksPlugin", compilation => {
  9. compilation.hooks.optimizeChunkIds.tap(
  10. "FlagIncludedChunksPlugin",
  11. chunks => {
  12. // prepare two bit integers for each module
  13. // 2^31 is the max number represented as SMI in v8
  14. // we want the bits distributed this way:
  15. // the bit 2^31 is pretty rar and only one module should get it
  16. // so it has a probability of 1 / modulesCount
  17. // the first bit (2^0) is the easiest and every module could get it
  18. // if it doesn't get a better bit
  19. // from bit 2^n to 2^(n+1) there is a probability of p
  20. // so 1 / modulesCount == p^31
  21. // <=> p = sqrt31(1 / modulesCount)
  22. // so we use a modulo of 1 / sqrt31(1 / modulesCount)
  23. const moduleBits = new WeakMap();
  24. const modulesCount = compilation.modules.length;
  25. // precalculate the modulo values for each bit
  26. const modulo = 1 / Math.pow(1 / modulesCount, 1 / 31);
  27. const modulos = Array.from(
  28. { length: 31 },
  29. (x, i) => Math.pow(modulo, i) | 0
  30. );
  31. // iterate all modules to generate bit values
  32. let i = 0;
  33. for (const module of compilation.modules) {
  34. let bit = 30;
  35. while (i % modulos[bit] !== 0) {
  36. bit--;
  37. }
  38. moduleBits.set(module, 1 << bit);
  39. i++;
  40. }
  41. // interate all chunks to generate bitmaps
  42. const chunkModulesHash = new WeakMap();
  43. for (const chunk of chunks) {
  44. let hash = 0;
  45. for (const module of chunk.modulesIterable) {
  46. hash |= moduleBits.get(module);
  47. }
  48. chunkModulesHash.set(chunk, hash);
  49. }
  50. for (const chunkA of chunks) {
  51. const chunkAHash = chunkModulesHash.get(chunkA);
  52. const chunkAModulesCount = chunkA.getNumberOfModules();
  53. if (chunkAModulesCount === 0) continue;
  54. let bestModule = undefined;
  55. for (const module of chunkA.modulesIterable) {
  56. if (
  57. bestModule === undefined ||
  58. bestModule.getNumberOfChunks() > module.getNumberOfChunks()
  59. )
  60. bestModule = module;
  61. }
  62. loopB: for (const chunkB of bestModule.chunksIterable) {
  63. // as we iterate the same iterables twice
  64. // skip if we find ourselves
  65. if (chunkA === chunkB) continue;
  66. const chunkBModulesCount = chunkB.getNumberOfModules();
  67. // ids for empty chunks are not included
  68. if (chunkBModulesCount === 0) continue;
  69. // instead of swapping A and B just bail
  70. // as we loop twice the current A will be B and B then A
  71. if (chunkAModulesCount > chunkBModulesCount) continue;
  72. // is chunkA in chunkB?
  73. // we do a cheap check for the hash value
  74. const chunkBHash = chunkModulesHash.get(chunkB);
  75. if ((chunkBHash & chunkAHash) !== chunkAHash) continue;
  76. // compare all modules
  77. for (const m of chunkA.modulesIterable) {
  78. if (!chunkB.containsModule(m)) continue loopB;
  79. }
  80. chunkB.ids.push(chunkA.id);
  81. }
  82. }
  83. }
  84. );
  85. });
  86. }
  87. }
  88. module.exports = FlagIncludedChunksPlugin;