Skip to content

Commit 6e4456b

Browse files
sheetalkamatsokra
andauthored
Optimize the size for tsbuildinfo (#43155)
* Baseline readable buildinfo * Use file names as index in file name list This is extension of the idea given by @sokra to optimize size of tsbuildinfo * Deduplicate reference map lists and use file name index to sort them Different implementation of #43079 based on idea suggested by @sokra * Minimal json.stringify for the tsbuildinfo Again implementaion of suggestion by @sokra * Update src/testRunner/unittests/tsbuild/helpers.ts Co-authored-by: Tobias Koppers <[email protected]> * Readable version of buildinfo all the time * Some renames for readability as per feedback Co-authored-by: Tobias Koppers <[email protected]>
1 parent 626e78c commit 6e4456b

File tree

237 files changed

+14628
-15138
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

237 files changed

+14628
-15138
lines changed

src/compiler/builder.ts

+79-56
Original file line numberDiff line numberDiff line change
@@ -690,13 +690,16 @@ namespace ts {
690690
return filterSemanticDiagnotics(diagnostics, state.compilerOptions);
691691
}
692692

693-
export type ProgramBuildInfoDiagnostic = string | [string, readonly ReusableDiagnostic[]];
694-
export type ProgramBuilderInfoFilePendingEmit = [string, BuilderFileEmit];
693+
export type ProgramBuildInfoDiagnostic = number | [fileId: number, diagnostics: readonly ReusableDiagnostic[]];
694+
export type ProgramBuilderInfoFilePendingEmit = [fileId: number, emitKind: BuilderFileEmit];
695+
export type ProgramBuildInfoReferencedMap = [fileId: number, fileIdListId: number][];
695696
export interface ProgramBuildInfo {
696-
fileInfos: MapLike<BuilderState.FileInfo>;
697+
fileNames: readonly string[];
698+
fileInfos: readonly BuilderState.FileInfo[];
697699
options: CompilerOptions;
698-
referencedMap?: MapLike<string[]>;
699-
exportedModulesMap?: MapLike<string[]>;
700+
fileIdsList?: readonly (readonly number[])[];
701+
referencedMap?: ProgramBuildInfoReferencedMap;
702+
exportedModulesMap?: ProgramBuildInfoReferencedMap;
700703
semanticDiagnosticsPerFile?: ProgramBuildInfoDiagnostic[];
701704
affectedFilesPendingEmit?: ProgramBuilderInfoFilePendingEmit[];
702705
}
@@ -708,66 +711,74 @@ namespace ts {
708711
if (outFile(state.compilerOptions)) return undefined;
709712
const currentDirectory = Debug.checkDefined(state.program).getCurrentDirectory();
710713
const buildInfoDirectory = getDirectoryPath(getNormalizedAbsolutePath(getTsBuildInfoEmitOutputFilePath(state.compilerOptions)!, currentDirectory));
711-
const fileInfos: MapLike<BuilderState.FileInfo> = {};
712-
state.fileInfos.forEach((value, key) => {
714+
const fileNames: string[] = [];
715+
const fileNameToFileId = new Map<string, number>();
716+
let fileIdsList: (readonly number[])[] | undefined;
717+
let fileNamesToFileIdListId: ESMap<string, number> | undefined;
718+
const fileInfos = arrayFrom(state.fileInfos.entries(), ([key, value]) => {
719+
// Ensure fileId
720+
const fileId = toFileId(key);
721+
Debug.assert(fileNames[fileId] === relativeToBuildInfo(key));
713722
const signature = state.currentAffectedFilesSignatures && state.currentAffectedFilesSignatures.get(key);
714-
fileInfos[relativeToBuildInfo(key)] = signature === undefined ? value : { version: value.version, signature, affectsGlobalScope: value.affectsGlobalScope };
723+
return signature === undefined ? value : { version: value.version, signature, affectsGlobalScope: value.affectsGlobalScope };
715724
});
716725

717-
const result: ProgramBuildInfo = {
718-
fileInfos,
719-
options: convertToReusableCompilerOptions(state.compilerOptions, relativeToBuildInfoEnsuringAbsolutePath)
720-
};
726+
let referencedMap: ProgramBuildInfoReferencedMap | undefined;
721727
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;
728+
referencedMap = arrayFrom(state.referencedMap.keys()).sort(compareStringsCaseSensitive).map(key => [
729+
toFileId(key),
730+
toFileIdListId(state.referencedMap!.get(key)!)
731+
]);
727732
}
728733

734+
let exportedModulesMap: ProgramBuildInfoReferencedMap | undefined;
729735
if (state.exportedModulesMap) {
730-
const exportedModulesMap: MapLike<string[]> = {};
731-
for (const key of arrayFrom(state.exportedModulesMap.keys()).sort(compareStringsCaseSensitive)) {
736+
exportedModulesMap = mapDefined(arrayFrom(state.exportedModulesMap.keys()).sort(compareStringsCaseSensitive), key => {
732737
const newValue = state.currentAffectedFilesExportedModulesMap && state.currentAffectedFilesExportedModulesMap.get(key);
733738
// Not in temporary cache, use existing value
734-
if (newValue === undefined) exportedModulesMap[relativeToBuildInfo(key)] = arrayFrom(state.exportedModulesMap.get(key)!.keys(), relativeToBuildInfo).sort(compareStringsCaseSensitive);
739+
if (newValue === undefined) return [toFileId(key), toFileIdListId(state.exportedModulesMap!.get(key)!)];
735740
// 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;
741+
else if (newValue) return [toFileId(key), toFileIdListId(newValue)];
742+
});
739743
}
740744

745+
let semanticDiagnosticsPerFile: ProgramBuildInfoDiagnostic[] | undefined;
741746
if (state.semanticDiagnosticsPerFile) {
742-
const semanticDiagnosticsPerFile: ProgramBuildInfoDiagnostic[] = [];
743747
for (const key of arrayFrom(state.semanticDiagnosticsPerFile.keys()).sort(compareStringsCaseSensitive)) {
744748
const value = state.semanticDiagnosticsPerFile.get(key)!;
745-
semanticDiagnosticsPerFile.push(
749+
(semanticDiagnosticsPerFile ||= []).push(
746750
value.length ?
747751
[
748-
relativeToBuildInfo(key),
752+
toFileId(key),
749753
state.hasReusableDiagnostic ?
750754
value as readonly ReusableDiagnostic[] :
751755
convertToReusableDiagnostics(value as readonly Diagnostic[], relativeToBuildInfo)
752756
] :
753-
relativeToBuildInfo(key)
757+
toFileId(key)
754758
);
755759
}
756-
result.semanticDiagnosticsPerFile = semanticDiagnosticsPerFile;
757760
}
758761

762+
let affectedFilesPendingEmit: ProgramBuilderInfoFilePendingEmit[] | undefined;
759763
if (state.affectedFilesPendingEmit) {
760-
const affectedFilesPendingEmit: ProgramBuilderInfoFilePendingEmit[] = [];
761764
const seenFiles = new Set<Path>();
762765
for (const path of state.affectedFilesPendingEmit.slice(state.affectedFilesPendingEmitIndex).sort(compareStringsCaseSensitive)) {
763766
if (tryAddToSet(seenFiles, path)) {
764-
affectedFilesPendingEmit.push([relativeToBuildInfo(path), state.affectedFilesPendingEmitKind!.get(path)!]);
767+
(affectedFilesPendingEmit ||= []).push([toFileId(path), state.affectedFilesPendingEmitKind!.get(path)!]);
765768
}
766769
}
767-
result.affectedFilesPendingEmit = affectedFilesPendingEmit;
768770
}
769771

770-
return result;
772+
return {
773+
fileNames,
774+
fileInfos,
775+
options: convertToReusableCompilerOptions(state.compilerOptions, relativeToBuildInfoEnsuringAbsolutePath),
776+
fileIdsList,
777+
referencedMap,
778+
exportedModulesMap,
779+
semanticDiagnosticsPerFile,
780+
affectedFilesPendingEmit,
781+
};
771782

772783
function relativeToBuildInfoEnsuringAbsolutePath(path: string) {
773784
return relativeToBuildInfo(getNormalizedAbsolutePath(path, currentDirectory));
@@ -776,6 +787,22 @@ namespace ts {
776787
function relativeToBuildInfo(path: string) {
777788
return ensurePathIsNonModuleName(getRelativePathFromDirectory(buildInfoDirectory, path, getCanonicalFileName));
778789
}
790+
791+
function toFileId(path: Path): number {
792+
const existing = fileNameToFileId.get(path);
793+
if (existing !== undefined) return existing;
794+
fileNameToFileId.set(path, fileNames.length);
795+
return fileNames.push(relativeToBuildInfo(path)) - 1;
796+
}
797+
798+
function toFileIdListId(set: ReadonlySet<Path>): number {
799+
const fileIds = arrayFrom(set.keys(), toFileId).sort(compareValues);
800+
const key = fileIds.join();
801+
const existing = fileNamesToFileIdListId?.get(key);
802+
if (existing !== undefined) return existing;
803+
(fileNamesToFileIdListId ||= new Map()).set(key, fileIdsList?.length || 0);
804+
return (fileIdsList ||= []).push(fileIds) - 1;
805+
}
779806
}
780807

781808
function convertToReusableCompilerOptions(options: CompilerOptions, relativeToBuildInfo: (path: string) => string) {
@@ -1167,39 +1194,23 @@ namespace ts {
11671194
}
11681195
}
11691196

1170-
function getMapOfReferencedSet(mapLike: MapLike<readonly string[]> | undefined, toPath: (path: string) => Path): ReadonlyESMap<Path, BuilderState.ReferencedSet> | undefined {
1171-
if (!mapLike) return undefined;
1172-
const map = new Map<Path, BuilderState.ReferencedSet>();
1173-
// Copies keys/values from template. Note that for..in will not throw if
1174-
// template is undefined, and instead will just exit the loop.
1175-
for (const key in mapLike) {
1176-
if (hasProperty(mapLike, key)) {
1177-
map.set(toPath(key), new Set(mapLike[key].map(toPath)));
1178-
}
1179-
}
1180-
return map;
1181-
}
1182-
11831197
export function createBuildProgramUsingProgramBuildInfo(program: ProgramBuildInfo, buildInfoPath: string, host: ReadBuildProgramHost): EmitAndSemanticDiagnosticsBuilderProgram {
11841198
const buildInfoDirectory = getDirectoryPath(getNormalizedAbsolutePath(buildInfoPath, host.getCurrentDirectory()));
11851199
const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames());
11861200

1201+
const filePaths = program.fileNames.map(toPath);
1202+
const filePathsSetList = program.fileIdsList?.map(fileIds => new Set(fileIds.map(toFilePath)));
11871203
const fileInfos = new Map<Path, BuilderState.FileInfo>();
1188-
for (const key in program.fileInfos) {
1189-
if (hasProperty(program.fileInfos, key)) {
1190-
fileInfos.set(toPath(key), program.fileInfos[key]);
1191-
}
1192-
}
1193-
1204+
program.fileInfos.forEach((fileInfo, fileId) => fileInfos.set(toFilePath(fileId), fileInfo));
11941205
const state: ReusableBuilderProgramState = {
11951206
fileInfos,
11961207
compilerOptions: convertToOptionsWithAbsolutePaths(program.options, toAbsolutePath),
1197-
referencedMap: getMapOfReferencedSet(program.referencedMap, toPath),
1198-
exportedModulesMap: getMapOfReferencedSet(program.exportedModulesMap, toPath),
1199-
semanticDiagnosticsPerFile: program.semanticDiagnosticsPerFile && arrayToMap(program.semanticDiagnosticsPerFile, value => toPath(isString(value) ? value : value[0]), value => isString(value) ? emptyArray : value[1]),
1208+
referencedMap: toMapOfReferencedSet(program.referencedMap),
1209+
exportedModulesMap: toMapOfReferencedSet(program.exportedModulesMap),
1210+
semanticDiagnosticsPerFile: program.semanticDiagnosticsPerFile && arrayToMap(program.semanticDiagnosticsPerFile, value => toFilePath(isNumber(value) ? value : value[0]), value => isNumber(value) ? emptyArray : value[1]),
12001211
hasReusableDiagnostic: true,
1201-
affectedFilesPendingEmit: map(program.affectedFilesPendingEmit, value => toPath(value[0])),
1202-
affectedFilesPendingEmitKind: program.affectedFilesPendingEmit && arrayToMap(program.affectedFilesPendingEmit, value => toPath(value[0]), value => value[1]),
1212+
affectedFilesPendingEmit: map(program.affectedFilesPendingEmit, value => toFilePath(value[0])),
1213+
affectedFilesPendingEmitKind: program.affectedFilesPendingEmit && arrayToMap(program.affectedFilesPendingEmit, value => toFilePath(value[0]), value => value[1]),
12031214
affectedFilesPendingEmitIndex: program.affectedFilesPendingEmit && 0,
12041215
};
12051216
return {
@@ -1234,6 +1245,18 @@ namespace ts {
12341245
function toAbsolutePath(path: string) {
12351246
return getNormalizedAbsolutePath(path, buildInfoDirectory);
12361247
}
1248+
1249+
function toFilePath(fileId: number) {
1250+
return filePaths[fileId];
1251+
}
1252+
1253+
function toFilePathsSet(fileIdsListId: number) {
1254+
return filePathsSetList![fileIdsListId];
1255+
}
1256+
1257+
function toMapOfReferencedSet(referenceMap: ProgramBuildInfoReferencedMap | undefined): ReadonlyESMap<Path, BuilderState.ReferencedSet> | undefined {
1258+
return referenceMap && arrayToMap(referenceMap, value => toFilePath(value[0]), value => toFilePathsSet(value[1]));
1259+
}
12371260
}
12381261

12391262
export function createRedirectedBuilderProgram(state: { program: Program | undefined; compilerOptions: CompilerOptions; }, configFileParsingDiagnostics: readonly Diagnostic[]): BuilderProgram {

src/compiler/emitter.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -663,7 +663,7 @@ namespace ts {
663663

664664
/*@internal*/
665665
export function getBuildInfoText(buildInfo: BuildInfo) {
666-
return JSON.stringify(buildInfo, undefined, 2);
666+
return JSON.stringify(buildInfo);
667667
}
668668

669669
/*@internal*/

src/testRunner/unittests/tsbuild/helpers.ts

+66-7
Original file line numberDiff line numberDiff line change
@@ -236,18 +236,78 @@ interface Symbol {
236236
}
237237
}
238238

239+
function generateBuildInfoProgramBaseline(sys: System, originalWriteFile: System["writeFile"], buildInfoPath: string, buildInfo: BuildInfo) {
240+
type ProgramBuildInfoDiagnostic = string | [string, readonly ReusableDiagnostic[]];
241+
type ProgramBuilderInfoFilePendingEmit = [string, BuilderFileEmit];
242+
interface ProgramBuildInfo {
243+
fileInfos: MapLike<BuilderState.FileInfo>;
244+
options: CompilerOptions;
245+
referencedMap?: MapLike<string[]>;
246+
exportedModulesMap?: MapLike<string[]>;
247+
semanticDiagnosticsPerFile?: ProgramBuildInfoDiagnostic[];
248+
affectedFilesPendingEmit?: ProgramBuilderInfoFilePendingEmit[];
249+
}
250+
const fileInfos: ProgramBuildInfo["fileInfos"] = {};
251+
buildInfo.program?.fileInfos.forEach((fileInfo, fileId) => {
252+
fileInfos[toFileName(fileId)] = fileInfo;
253+
});
254+
const fileNamesList = buildInfo.program?.fileIdsList?.map(fileIdsListId => fileIdsListId.map(toFileName));
255+
const program: ProgramBuildInfo | undefined = buildInfo.program && {
256+
fileInfos,
257+
options: buildInfo.program.options,
258+
referencedMap: toMapOfReferencedSet(buildInfo.program.referencedMap),
259+
exportedModulesMap: toMapOfReferencedSet(buildInfo.program.exportedModulesMap),
260+
semanticDiagnosticsPerFile: buildInfo.program.semanticDiagnosticsPerFile?.map(d =>
261+
isNumber(d) ?
262+
toFileName(d) :
263+
[toFileName(d[0]), d[1]]
264+
),
265+
affectedFilesPendingEmit: buildInfo.program.affectedFilesPendingEmit?.map(([fileId, emitKind]) => [
266+
toFileName(fileId),
267+
emitKind
268+
]),
269+
};
270+
const result: Omit<BuildInfo, "program"> & { program: ProgramBuildInfo | undefined; } = {
271+
bundle: buildInfo.bundle,
272+
program,
273+
version: buildInfo.version === version ? fakes.version : buildInfo.version,
274+
};
275+
// For now its just JSON.stringify
276+
originalWriteFile.call(sys, `${buildInfoPath}.readable.baseline.txt`, JSON.stringify(result, /*replacer*/ undefined, 2));
277+
278+
function toFileName(fileId: number) {
279+
return buildInfo.program!.fileNames[fileId];
280+
}
281+
282+
function toFileNames(fileIdsListId: number) {
283+
return fileNamesList![fileIdsListId];
284+
}
285+
286+
function toMapOfReferencedSet(referenceMap: ProgramBuildInfoReferencedMap | undefined): MapLike<string[]> | undefined {
287+
if (!referenceMap) return undefined;
288+
const result: MapLike<string[]> = {};
289+
for (const [fileNamesKey, fileNamesListKey] of referenceMap) {
290+
result[toFileName(fileNamesKey)] = toFileNames(fileNamesListKey);
291+
}
292+
return result;
293+
}
294+
}
295+
239296
export function baselineBuildInfo(
240297
options: CompilerOptions,
241298
sys: System & { writtenFiles: ReadonlyCollection<string>; },
242-
originalReadCall?: System["readFile"]
299+
originalReadCall?: System["readFile"],
300+
originalWriteFile?: System["writeFile"],
243301
) {
244-
const out = outFile(options);
245-
if (!out) return;
246-
const { buildInfoPath, jsFilePath, declarationFilePath } = getOutputPathsForBundle(options, /*forceDts*/ false);
302+
const buildInfoPath = getTsBuildInfoEmitOutputFilePath(options);
247303
if (!buildInfoPath || !sys.writtenFiles.has(buildInfoPath)) return;
248304
if (!sys.fileExists(buildInfoPath)) return;
249305

250306
const buildInfo = getBuildInfo((originalReadCall || sys.readFile).call(sys, buildInfoPath, "utf8")!);
307+
generateBuildInfoProgramBaseline(sys, originalWriteFile || sys.writeFile, buildInfoPath, buildInfo);
308+
309+
if (!outFile(options)) return;
310+
const { jsFilePath, declarationFilePath } = getOutputPathsForBundle(options, /*forceDts*/ false);
251311
const bundle = buildInfo.bundle;
252312
if (!bundle || (!length(bundle.js && bundle.js.sections) && !length(bundle.dts && bundle.dts.sections))) return;
253313

@@ -256,9 +316,8 @@ interface Symbol {
256316
generateBundleFileSectionInfo(sys, originalReadCall || sys.readFile, baselineRecorder, bundle.js, jsFilePath);
257317
generateBundleFileSectionInfo(sys, originalReadCall || sys.readFile, baselineRecorder, bundle.dts, declarationFilePath);
258318
baselineRecorder.Close();
259-
260319
const text = baselineRecorder.lines.join("\r\n");
261-
sys.writeFile(`${buildInfoPath}.baseline.txt`, text);
320+
(originalWriteFile || sys.writeFile).call(sys, `${buildInfoPath}.baseline.txt`, text);
262321
}
263322

264323
interface VerifyIncrementalCorrectness {
@@ -295,7 +354,7 @@ interface Symbol {
295354
const cleanBuildText = sys.readFile(outputFile);
296355
const incrementalBuildText = newSys.readFile(outputFile);
297356
const descrepancyInClean = discrepancies?.get(outputFile);
298-
if (!isBuildInfoFile(outputFile)) {
357+
if (!isBuildInfoFile(outputFile) && !fileExtensionIs(outputFile, ".tsbuildinfo.readable.baseline.txt")) {
299358
verifyTextEqual(incrementalBuildText, cleanBuildText, descrepancyInClean, `File: ${outputFile}`);
300359
}
301360
else if (incrementalBuildText !== cleanBuildText) {

src/testRunner/unittests/tsbuild/outputPaths.ts

+4-5
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,10 @@ namespace ts {
6060
noChangeRun,
6161
{
6262
...noChangeProject,
63-
cleanBuildDiscrepancies: () => {
64-
const map = new Map<string, CleanBuildDescrepancy>();
65-
map.set("/src/dist/tsconfig.tsbuildinfo", CleanBuildDescrepancy.CleanFileTextDifferent); // tsbuildinfo will have -p setting when built using -p vs no build happens incrementally because of no change.
66-
return map;
67-
}
63+
cleanBuildDiscrepancies: () => new Map([
64+
["/src/dist/tsconfig.tsbuildinfo", CleanBuildDescrepancy.CleanFileTextDifferent], // tsbuildinfo will have -p setting when built using -p vs no build happens incrementally because of no change.
65+
["/src/dist/tsconfig.tsbuildinfo.readable.baseline.txt", CleanBuildDescrepancy.CleanFileTextDifferent] // tsbuildinfo will have -p setting when built using -p vs no build happens incrementally because of no change.
66+
]),
6867
}
6968
],
7069
}, ["/src/dist/src/index.js", "/src/dist/src/index.d.ts"]);

0 commit comments

Comments
 (0)