@@ -695,12 +695,87 @@ namespace ts {
695
695
export interface ProgramBuildInfo {
696
696
fileInfos : MapLike < BuilderState . FileInfo > ;
697
697
options : CompilerOptions ;
698
- referencedMap ?: MapLike < string [ ] > ;
699
- exportedModulesMap ?: MapLike < string [ ] > ;
698
+ referencedMap ?: MapLike < string [ ] | number > ;
699
+ referencedMapLists ?: string [ ] [ ] ;
700
+ exportedModulesMap ?: MapLike < string [ ] | number > ;
701
+ exportedModulesMapLists ?: string [ ] [ ] ;
700
702
semanticDiagnosticsPerFile ?: ProgramBuildInfoDiagnostic [ ] ;
701
703
affectedFilesPendingEmit ?: ProgramBuilderInfoFilePendingEmit [ ] ;
702
704
}
703
705
706
+ /**
707
+ * Runs deduplication on a map of references lists to reduce storage cost
708
+ */
709
+ function deduplicateReferencedMap ( inputMap : ReadonlyESMap < Path , BuilderState . ReferencedSet > , relativeToBuildInfo : ( path : Path ) => string ) : { map : MapLike < string [ ] | number > , lists ?: string [ ] [ ] } {
710
+ const map : MapLike < string [ ] | number > = { } ;
711
+ const lists : string [ ] [ ] = [ ] ;
712
+ const sharedMap = new Map < BuilderState . ReferencedSet , number > ( ) ;
713
+ const notSharedMap = new Map < BuilderState . ReferencedSet , string > ( ) ;
714
+ const seenSetsBySize = new Map < number , BuilderState . ReferencedSet [ ] > ( ) ;
715
+ for ( const key of arrayFrom ( inputMap . keys ( ) ) . sort ( compareStringsCaseSensitive ) ) {
716
+ const set = inputMap . get ( key ) ! ;
717
+ let sharedSet ;
718
+ let notSharedSet ;
719
+ // already shared
720
+ if ( sharedMap . has ( set ) ) {
721
+ sharedSet = set ;
722
+ }
723
+ // already seen, but not shared yet
724
+ else if ( notSharedMap . has ( set ) ) {
725
+ notSharedSet = set ;
726
+ }
727
+ // compare with all seen sets to find duplicates without same identity
728
+ else {
729
+ const potentialDuplicates = seenSetsBySize . get ( set . size ) ;
730
+ if ( potentialDuplicates !== undefined ) {
731
+ outer: for ( const otherSet of potentialDuplicates ) {
732
+ const it = set . keys ( ) ;
733
+ let itResult ;
734
+ while ( ! ( itResult = it . next ( ) ) . done ) {
735
+ if ( ! otherSet . has ( itResult . value ) ) continue outer;
736
+ }
737
+ if ( sharedMap . has ( otherSet ) ) {
738
+ sharedSet = otherSet ;
739
+ }
740
+ else {
741
+ notSharedSet = otherSet ;
742
+ }
743
+ break ;
744
+ }
745
+ }
746
+ }
747
+
748
+ if ( sharedSet !== undefined ) {
749
+ // use the existing id
750
+ map [ relativeToBuildInfo ( key ) ] = sharedMap . get ( sharedSet ) ! ;
751
+ }
752
+ else if ( notSharedSet !== undefined ) {
753
+ // create a new shared list and assign both sets to it
754
+ // also update the existing key with the new shared index
755
+ const idx = lists . length ;
756
+ const existingKey = notSharedMap . get ( notSharedSet ) ! ;
757
+ sharedMap . set ( set , idx ) ;
758
+ sharedMap . set ( notSharedSet , idx ) ;
759
+ lists . push ( map [ existingKey ] as string [ ] ) ;
760
+ notSharedMap . delete ( notSharedSet ) ;
761
+ map [ existingKey ] = idx ;
762
+ map [ relativeToBuildInfo ( key ) ] = idx ;
763
+ }
764
+ else {
765
+ const relativeKey = relativeToBuildInfo ( key ) ;
766
+ notSharedMap . set ( set , relativeKey ) ;
767
+ map [ relativeKey ] = arrayFrom ( set . keys ( ) , relativeToBuildInfo ) . sort ( compareStringsCaseSensitive ) ;
768
+ if ( seenSetsBySize . has ( set . size ) ) {
769
+ seenSetsBySize . get ( set . size ) ! . push ( set ) ;
770
+ }
771
+ else {
772
+ seenSetsBySize . set ( set . size , [ set ] ) ;
773
+ }
774
+ }
775
+ }
776
+ return { map, lists : lists . length > 0 ? lists : undefined } ;
777
+ }
778
+
704
779
/**
705
780
* Gets the program information to be emitted in buildInfo so that we can use it to create new program
706
781
*/
@@ -719,23 +794,15 @@ namespace ts {
719
794
options : convertToReusableCompilerOptions ( state . compilerOptions , relativeToBuildInfoEnsuringAbsolutePath )
720
795
} ;
721
796
if ( state . referencedMap ) {
722
- const referencedMap : MapLike < string [ ] > = { } ;
723
- for ( const key of arrayFrom ( state . referencedMap . keys ( ) ) . sort ( compareStringsCaseSensitive ) ) {
724
- referencedMap [ relativeToBuildInfo ( key ) ] = arrayFrom ( state . referencedMap . get ( key ) ! . keys ( ) , relativeToBuildInfo ) . sort ( compareStringsCaseSensitive ) ;
725
- }
726
- result . referencedMap = referencedMap ;
797
+ const { map, lists } = deduplicateReferencedMap ( state . referencedMap , relativeToBuildInfo ) ;
798
+ result . referencedMap = map ;
799
+ result . referencedMapLists = lists ;
727
800
}
728
801
729
802
if ( state . exportedModulesMap ) {
730
- const exportedModulesMap : MapLike < string [ ] > = { } ;
731
- for ( const key of arrayFrom ( state . exportedModulesMap . keys ( ) ) . sort ( compareStringsCaseSensitive ) ) {
732
- const newValue = state . currentAffectedFilesExportedModulesMap && state . currentAffectedFilesExportedModulesMap . get ( key ) ;
733
- // Not in temporary cache, use existing value
734
- if ( newValue === undefined ) exportedModulesMap [ relativeToBuildInfo ( key ) ] = arrayFrom ( state . exportedModulesMap . get ( key ) ! . keys ( ) , relativeToBuildInfo ) . sort ( compareStringsCaseSensitive ) ;
735
- // Value in cache and has updated value map, use that
736
- else if ( newValue ) exportedModulesMap [ relativeToBuildInfo ( key ) ] = arrayFrom ( newValue . keys ( ) , relativeToBuildInfo ) . sort ( compareStringsCaseSensitive ) ;
737
- }
738
- result . exportedModulesMap = exportedModulesMap ;
803
+ const { map, lists } = deduplicateReferencedMap ( state . exportedModulesMap , relativeToBuildInfo ) ;
804
+ result . exportedModulesMap = map ;
805
+ result . exportedModulesMapLists = lists ;
739
806
}
740
807
741
808
if ( state . semanticDiagnosticsPerFile ) {
@@ -1167,14 +1234,29 @@ namespace ts {
1167
1234
}
1168
1235
}
1169
1236
1170
- function getMapOfReferencedSet ( mapLike : MapLike < readonly string [ ] > | undefined , toPath : ( path : string ) => Path ) : ReadonlyESMap < Path , BuilderState . ReferencedSet > | undefined {
1237
+ function getMapOfReferencedSet ( mapLike : MapLike < readonly string [ ] | number > | undefined , lists : ( readonly string [ ] ) [ ] | undefined , toPath : ( path : string ) => Path ) : ReadonlyESMap < Path , BuilderState . ReferencedSet > | undefined {
1171
1238
if ( ! mapLike ) return undefined ;
1172
1239
const map = new Map < Path , BuilderState . ReferencedSet > ( ) ;
1240
+ const cache = new Map < number , BuilderState . ReferencedSet > ( ) ;
1173
1241
// Copies keys/values from template. Note that for..in will not throw if
1174
1242
// template is undefined, and instead will just exit the loop.
1175
1243
for ( const key in mapLike ) {
1176
1244
if ( hasProperty ( mapLike , key ) ) {
1177
- map . set ( toPath ( key ) , new Set ( mapLike [ key ] . map ( toPath ) ) ) ;
1245
+ const value = mapLike [ key ] ;
1246
+ if ( typeof value === "number" ) {
1247
+ if ( cache . has ( value ) ) {
1248
+ map . set ( toPath ( key ) , cache . get ( value ) ! ) ;
1249
+ }
1250
+ else {
1251
+ if ( ! lists ) return undefined ;
1252
+ const list = new Set ( lists [ value ] . map ( toPath ) ) ;
1253
+ cache . set ( value , list ) ;
1254
+ map . set ( toPath ( key ) , list ) ;
1255
+ }
1256
+ }
1257
+ else {
1258
+ map . set ( toPath ( key ) , new Set ( value . map ( toPath ) ) ) ;
1259
+ }
1178
1260
}
1179
1261
}
1180
1262
return map ;
@@ -1194,8 +1276,8 @@ namespace ts {
1194
1276
const state : ReusableBuilderProgramState = {
1195
1277
fileInfos,
1196
1278
compilerOptions : convertToOptionsWithAbsolutePaths ( program . options , toAbsolutePath ) ,
1197
- referencedMap : getMapOfReferencedSet ( program . referencedMap , toPath ) ,
1198
- exportedModulesMap : getMapOfReferencedSet ( program . exportedModulesMap , toPath ) ,
1279
+ referencedMap : getMapOfReferencedSet ( program . referencedMap , program . referencedMapLists , toPath ) ,
1280
+ exportedModulesMap : getMapOfReferencedSet ( program . exportedModulesMap , program . exportedModulesMapLists , toPath ) ,
1199
1281
semanticDiagnosticsPerFile : program . semanticDiagnosticsPerFile && arrayToMap ( program . semanticDiagnosticsPerFile , value => toPath ( isString ( value ) ? value : value [ 0 ] ) , value => isString ( value ) ? emptyArray : value [ 1 ] ) ,
1200
1282
hasReusableDiagnostic : true ,
1201
1283
affectedFilesPendingEmit : map ( program . affectedFilesPendingEmit , value => toPath ( value [ 0 ] ) ) ,
0 commit comments