@@ -10,24 +10,85 @@ class FlagIncludedChunksPlugin {
1010 compilation . hooks . optimizeChunkIds . tap (
1111 "FlagIncludedChunksPlugin" ,
1212 chunks => {
13+ // prepare two bit integers for each module
14+ // 2^31 is the max number represented as SMI in v8
15+ // we want the bits distributed this way:
16+ // the bit 2^31 is pretty rar and only one module should get it
17+ // so it has a probability of 1 / modulesCount
18+ // the first bit (2^0) is the easiest and every module could get it
19+ // if it doesn't get a better bit
20+ // from bit 2^n to 2^(n+1) there is a probability of p
21+ // so 1 / modulesCount == p^31
22+ // <=> p = sqrt31(1 / modulesCount)
23+ // so we use a modulo of 1 / sqrt31(1 / modulesCount)
24+ const moduleBits = new WeakMap ( ) ;
25+ const modulesCount = compilation . modules . length ;
26+
27+ // precalculate the modulo values for each bit
28+ const modulo = 1 / Math . pow ( 1 / modulesCount , 1 / 31 ) ;
29+ const modulos = Array . from (
30+ { length : 31 } ,
31+ ( x , i ) => Math . pow ( modulo , i ) | 0
32+ ) ;
33+
34+ // iterate all modules to generate bit values
35+ let i = 0 ;
36+ for ( const module of compilation . modules ) {
37+ let bit = 30 ;
38+ while ( i % modulos [ bit ] !== 0 ) {
39+ bit -- ;
40+ }
41+ moduleBits . set ( module , 1 << bit ) ;
42+ i ++ ;
43+ }
44+
45+ // interate all chunks to generate bitmaps
46+ const chunkModulesHash = new WeakMap ( ) ;
47+ for ( const chunk of chunks ) {
48+ let hash = 0 ;
49+ for ( const module of chunk . modulesIterable ) {
50+ hash |= moduleBits . get ( module ) ;
51+ }
52+ chunkModulesHash . set ( chunk , hash ) ;
53+ }
54+
1355 for ( const chunkA of chunks ) {
14- loopB: for ( const chunkB of chunks ) {
56+ const chunkAHash = chunkModulesHash . get ( chunkA ) ;
57+ const chunkAModulesCount = chunkA . getNumberOfModules ( ) ;
58+ if ( chunkAModulesCount === 0 ) continue ;
59+ let bestModule = undefined ;
60+ for ( const module of chunkA . modulesIterable ) {
61+ if (
62+ bestModule === undefined ||
63+ bestModule . getNumberOfChunks ( ) > module . getNumberOfChunks ( )
64+ )
65+ bestModule = module ;
66+ }
67+ loopB: for ( const chunkB of bestModule . chunksIterable ) {
1568 // as we iterate the same iterables twice
1669 // skip if we find ourselves
17- if ( chunkA === chunkB ) continue loopB;
70+ if ( chunkA === chunkB ) continue ;
71+
72+ const chunkBModulesCount = chunkB . getNumberOfModules ( ) ;
73+
74+ // ids for empty chunks are not included
75+ if ( chunkBModulesCount === 0 ) continue ;
1876
1977 // instead of swapping A and B just bail
2078 // as we loop twice the current A will be B and B then A
21- if ( chunkA . getNumberOfModules ( ) < chunkB . getNumberOfModules ( ) )
22- continue loopB;
79+ if ( chunkAModulesCount > chunkBModulesCount ) continue ;
80+
81+ // is chunkA in chunkB?
2382
24- if ( chunkB . getNumberOfModules ( ) === 0 ) continue loopB;
83+ // we do a cheap check for the hash value
84+ const chunkBHash = chunkModulesHash . get ( chunkB ) ;
85+ if ( ( chunkBHash & chunkAHash ) !== chunkAHash ) continue ;
2586
26- // is chunkB in chunkA?
27- for ( const m of chunkB . modulesIterable ) {
28- if ( ! chunkA . containsModule ( m ) ) continue loopB;
87+ // compare all modules
88+ for ( const m of chunkA . modulesIterable ) {
89+ if ( ! chunkB . containsModule ( m ) ) continue loopB;
2990 }
30- chunkA . ids . push ( chunkB . id ) ;
91+ chunkB . ids . push ( chunkA . id ) ;
3192 }
3293 }
3394 }
0 commit comments