Skip to content

Commit c8ae5b5

Browse files
authored
Merge pull request #28644 from Microsoft/cacheHostResults
[release-3.2] Cache results for readFile, fileExists, directory exists, sourceFiles for .d.ts files across the build (only first time)
2 parents 6197b7b + eb817de commit c8ae5b5

File tree

4 files changed

+143
-7
lines changed

4 files changed

+143
-7
lines changed

src/compiler/program.ts

+119-6
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,6 @@ namespace ts {
7373
// TODO(shkamat): update this after reworking ts build API
7474
export function createCompilerHostWorker(options: CompilerOptions, setParentNodes?: boolean, system = sys): CompilerHost {
7575
const existingDirectories = createMap<boolean>();
76-
7776
function getCanonicalFileName(fileName: string): string {
7877
// if underlying system can distinguish between two files whose names differs only in cases then file name already in canonical form.
7978
// otherwise use toLowerCase as a canonical form.
@@ -84,7 +83,7 @@ namespace ts {
8483
let text: string | undefined;
8584
try {
8685
performance.mark("beforeIORead");
87-
text = system.readFile(fileName, options.charset);
86+
text = compilerHost.readFile(fileName);
8887
performance.mark("afterIORead");
8988
performance.measure("I/O Read", "beforeIORead", "afterIORead");
9089
}
@@ -113,7 +112,12 @@ namespace ts {
113112
if (directoryPath.length > getRootLength(directoryPath) && !directoryExists(directoryPath)) {
114113
const parentDirectory = getDirectoryPath(directoryPath);
115114
ensureDirectoriesExist(parentDirectory);
116-
system.createDirectory(directoryPath);
115+
if (compilerHost.createDirectory) {
116+
compilerHost.createDirectory(directoryPath);
117+
}
118+
else {
119+
system.createDirectory(directoryPath);
120+
}
117121
}
118122
}
119123

@@ -177,8 +181,7 @@ namespace ts {
177181

178182
const newLine = getNewLineCharacter(options, () => system.newLine);
179183
const realpath = system.realpath && ((path: string) => system.realpath!(path));
180-
181-
return {
184+
const compilerHost: CompilerHost = {
182185
getSourceFile,
183186
getDefaultLibLocation,
184187
getDefaultLibFileName: options => combinePaths(getDefaultLibLocation(), getDefaultLibFileName(options)),
@@ -194,7 +197,117 @@ namespace ts {
194197
getEnvironmentVariable: name => system.getEnvironmentVariable ? system.getEnvironmentVariable(name) : "",
195198
getDirectories: (path: string) => system.getDirectories(path),
196199
realpath,
197-
readDirectory: (path, extensions, include, exclude, depth) => system.readDirectory(path, extensions, include, exclude, depth)
200+
readDirectory: (path, extensions, include, exclude, depth) => system.readDirectory(path, extensions, include, exclude, depth),
201+
createDirectory: d => system.createDirectory(d)
202+
};
203+
return compilerHost;
204+
}
205+
206+
/*@internal*/
207+
export function changeCompilerHostToUseCache(
208+
host: CompilerHost,
209+
toPath: (fileName: string) => Path,
210+
useCacheForSourceFile: boolean
211+
) {
212+
const originalReadFile = host.readFile;
213+
const originalFileExists = host.fileExists;
214+
const originalDirectoryExists = host.directoryExists;
215+
const originalCreateDirectory = host.createDirectory;
216+
const originalWriteFile = host.writeFile;
217+
const originalGetSourceFile = host.getSourceFile;
218+
const readFileCache = createMap<string | false>();
219+
const fileExistsCache = createMap<boolean>();
220+
const directoryExistsCache = createMap<boolean>();
221+
const sourceFileCache = createMap<SourceFile>();
222+
223+
const readFileWithCache = (fileName: string): string | undefined => {
224+
const key = toPath(fileName);
225+
const value = readFileCache.get(key);
226+
if (value !== undefined) return value || undefined;
227+
return setReadFileCache(key, fileName);
228+
};
229+
const setReadFileCache = (key: Path, fileName: string) => {
230+
const newValue = originalReadFile.call(host, fileName);
231+
readFileCache.set(key, newValue || false);
232+
return newValue;
233+
};
234+
host.readFile = fileName => {
235+
const key = toPath(fileName);
236+
const value = readFileCache.get(key);
237+
if (value !== undefined) return value; // could be .d.ts from output
238+
if (!fileExtensionIs(fileName, Extension.Json)) {
239+
return originalReadFile.call(host, fileName);
240+
}
241+
242+
return setReadFileCache(key, fileName);
243+
};
244+
245+
if (useCacheForSourceFile) {
246+
host.getSourceFile = (fileName, languageVersion, onError, shouldCreateNewSourceFile) => {
247+
const key = toPath(fileName);
248+
const value = sourceFileCache.get(key);
249+
if (value) return value;
250+
251+
const sourceFile = originalGetSourceFile.call(host, fileName, languageVersion, onError, shouldCreateNewSourceFile);
252+
if (sourceFile && (isDeclarationFileName(fileName) || fileExtensionIs(fileName, Extension.Json))) {
253+
sourceFileCache.set(key, sourceFile);
254+
}
255+
return sourceFile;
256+
};
257+
}
258+
259+
// fileExists for any kind of extension
260+
host.fileExists = fileName => {
261+
const key = toPath(fileName);
262+
const value = fileExistsCache.get(key);
263+
if (value !== undefined) return value;
264+
const newValue = originalFileExists.call(host, fileName);
265+
fileExistsCache.set(key, !!newValue);
266+
return newValue;
267+
};
268+
host.writeFile = (fileName, data, writeByteOrderMark, onError, sourceFiles) => {
269+
const key = toPath(fileName);
270+
fileExistsCache.delete(key);
271+
272+
const value = readFileCache.get(key);
273+
if (value && value !== data) {
274+
readFileCache.delete(key);
275+
sourceFileCache.delete(key);
276+
}
277+
else if (useCacheForSourceFile) {
278+
const sourceFile = sourceFileCache.get(key);
279+
if (sourceFile && sourceFile.text !== data) {
280+
sourceFileCache.delete(key);
281+
}
282+
}
283+
originalWriteFile.call(host, fileName, data, writeByteOrderMark, onError, sourceFiles);
284+
};
285+
286+
// directoryExists
287+
if (originalDirectoryExists && originalCreateDirectory) {
288+
host.directoryExists = directory => {
289+
const key = toPath(directory);
290+
const value = directoryExistsCache.get(key);
291+
if (value !== undefined) return value;
292+
const newValue = originalDirectoryExists.call(host, directory);
293+
directoryExistsCache.set(key, !!newValue);
294+
return newValue;
295+
};
296+
host.createDirectory = directory => {
297+
const key = toPath(directory);
298+
directoryExistsCache.delete(key);
299+
originalCreateDirectory.call(host, directory);
300+
};
301+
}
302+
303+
return {
304+
originalReadFile,
305+
originalFileExists,
306+
originalDirectoryExists,
307+
originalCreateDirectory,
308+
originalWriteFile,
309+
originalGetSourceFile,
310+
readFileWithCache
198311
};
199312
}
200313

src/compiler/tsbuild.ts

+18-1
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,7 @@ namespace ts {
433433
const missingRoots = createMap<true>();
434434
let globalDependencyGraph: DependencyGraph | undefined;
435435
const writeFileName = (s: string) => host.trace && host.trace(s);
436+
let readFileWithCache = (f: string) => host.readFile(f);
436437

437438
// Watch state
438439
const diagnostics = createFileMap<ReadonlyArray<Diagnostic>>(toPath);
@@ -1073,7 +1074,7 @@ namespace ts {
10731074
let priorChangeTime: Date | undefined;
10741075
if (!anyDtsChanged && isDeclarationFile(name)) {
10751076
// Check for unchanged .d.ts files
1076-
if (host.fileExists(name) && host.readFile(name) === text) {
1077+
if (host.fileExists(name) && readFileWithCache(name) === text) {
10771078
priorChangeTime = host.getModifiedTime(name);
10781079
}
10791080
else {
@@ -1184,6 +1185,15 @@ namespace ts {
11841185

11851186
function buildAllProjects(): ExitStatus {
11861187
if (options.watch) { reportWatchStatus(Diagnostics.Starting_compilation_in_watch_mode); }
1188+
// TODO:: In watch mode as well to use caches for incremental build once we can invalidate caches correctly and have right api
1189+
// Override readFile for json files and output .d.ts to cache the text
1190+
const { originalReadFile, originalFileExists, originalDirectoryExists,
1191+
originalCreateDirectory, originalWriteFile, originalGetSourceFile,
1192+
readFileWithCache: newReadFileWithCache
1193+
} = changeCompilerHostToUseCache(host, toPath, /*useCacheForSourceFile*/ true);
1194+
const savedReadFileWithCache = readFileWithCache;
1195+
readFileWithCache = newReadFileWithCache;
1196+
11871197
const graph = getGlobalDependencyGraph();
11881198
reportBuildQueue(graph);
11891199
let anyFailed = false;
@@ -1234,6 +1244,13 @@ namespace ts {
12341244
anyFailed = anyFailed || !!(buildResult & BuildResultFlags.AnyErrors);
12351245
}
12361246
reportErrorSummary();
1247+
host.readFile = originalReadFile;
1248+
host.fileExists = originalFileExists;
1249+
host.directoryExists = originalDirectoryExists;
1250+
host.createDirectory = originalCreateDirectory;
1251+
host.writeFile = originalWriteFile;
1252+
readFileWithCache = savedReadFileWithCache;
1253+
host.getSourceFile = originalGetSourceFile;
12371254
return anyFailed ? ExitStatus.DiagnosticsPresent_OutputsSkipped : ExitStatus.Success;
12381255
}
12391256

src/compiler/types.ts

+3
Original file line numberDiff line numberDiff line change
@@ -5014,6 +5014,9 @@ namespace ts {
50145014
/* @internal */ hasInvalidatedResolution?: HasInvalidatedResolution;
50155015
/* @internal */ hasChangedAutomaticTypeDirectiveNames?: boolean;
50165016
createHash?(data: string): string;
5017+
5018+
// TODO: later handle this in better way in builder host instead once the api for tsbuild finalizes and doesnt use compilerHost as base
5019+
/*@internal*/createDirectory?(directory: string): void;
50175020
}
50185021

50195022
/* @internal */

src/tsc/tsc.ts

+3
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,9 @@ namespace ts {
227227

228228
function performCompilation(rootNames: string[], projectReferences: ReadonlyArray<ProjectReference> | undefined, options: CompilerOptions, configFileParsingDiagnostics?: ReadonlyArray<Diagnostic>) {
229229
const host = createCompilerHost(options);
230+
const currentDirectory = host.getCurrentDirectory();
231+
const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames());
232+
changeCompilerHostToUseCache(host, fileName => toPath(fileName, currentDirectory, getCanonicalFileName), /*useCacheForSourceFile*/ false);
230233
enableStatistics(options);
231234

232235
const programOptions: CreateProgramOptions = {

0 commit comments

Comments
 (0)