diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 118850ab737..1789b161e39 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -203,6 +203,114 @@ namespace ts { return compilerHost; } + /*@internal*/ + export function changeCompilerHostToUseCache( + host: CompilerHost, + toPath: (fileName: string) => Path, + useCacheForSourceFile: boolean + ) { + const originalReadFile = host.readFile; + const originalFileExists = host.fileExists; + const originalDirectoryExists = host.directoryExists; + const originalCreateDirectory = host.createDirectory; + const originalWriteFile = host.writeFile; + const originalGetSourceFile = host.getSourceFile; + const readFileCache = createMap(); + const fileExistsCache = createMap(); + const directoryExistsCache = createMap(); + const sourceFileCache = createMap(); + + const readFileWithCache = (fileName: string): string | undefined => { + const key = toPath(fileName); + const value = readFileCache.get(key); + if (value !== undefined) return value || undefined; + return setReadFileCache(key, fileName); + }; + const setReadFileCache = (key: Path, fileName: string) => { + const newValue = originalReadFile.call(host, fileName); + readFileCache.set(key, newValue || false); + return newValue; + }; + host.readFile = fileName => { + const key = toPath(fileName); + const value = readFileCache.get(key); + if (value !== undefined) return value; // could be .d.ts from output + if (!fileExtensionIs(fileName, Extension.Json)) { + return originalReadFile.call(host, fileName); + } + + return setReadFileCache(key, fileName); + }; + + if (useCacheForSourceFile) { + host.getSourceFile = (fileName, languageVersion, onError, shouldCreateNewSourceFile) => { + const key = toPath(fileName); + const value = sourceFileCache.get(key); + if (value) return value; + + const sourceFile = originalGetSourceFile.call(host, fileName, languageVersion, onError, shouldCreateNewSourceFile); + if (sourceFile && (isDeclarationFileName(fileName) || fileExtensionIs(fileName, Extension.Json))) { + sourceFileCache.set(key, sourceFile); + } + return sourceFile; + }; + } + + // fileExists for any kind of extension + host.fileExists = fileName => { + const key = toPath(fileName); + const value = fileExistsCache.get(key); + if (value !== undefined) return value; + const newValue = originalFileExists.call(host, fileName); + fileExistsCache.set(key, !!newValue); + return newValue; + }; + host.writeFile = (fileName, data, writeByteOrderMark, onError, sourceFiles) => { + const key = toPath(fileName); + fileExistsCache.delete(key); + + const value = readFileCache.get(key); + if (value && value !== data) { + readFileCache.delete(key); + sourceFileCache.delete(key); + } + else if (useCacheForSourceFile) { + const sourceFile = sourceFileCache.get(key); + if (sourceFile && sourceFile.text !== data) { + sourceFileCache.delete(key); + } + } + originalWriteFile.call(host, fileName, data, writeByteOrderMark, onError, sourceFiles); + }; + + // directoryExists + if (originalDirectoryExists && originalCreateDirectory) { + host.directoryExists = directory => { + const key = toPath(directory); + const value = directoryExistsCache.get(key); + if (value !== undefined) return value; + const newValue = originalDirectoryExists.call(host, directory); + directoryExistsCache.set(key, !!newValue); + return newValue; + }; + host.createDirectory = directory => { + const key = toPath(directory); + directoryExistsCache.delete(key); + originalCreateDirectory.call(host, directory); + }; + } + + return { + originalReadFile, + originalFileExists, + originalDirectoryExists, + originalCreateDirectory, + originalWriteFile, + originalGetSourceFile, + readFileWithCache + }; + } + export function getPreEmitDiagnostics(program: Program, sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray { const diagnostics = [ ...program.getConfigFileParsingDiagnostics(), diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index b0f7dd7ac3c..2b8cac3ea57 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -1183,96 +1183,14 @@ namespace ts { function buildAllProjects(): ExitStatus { if (options.watch) { reportWatchStatus(Diagnostics.Starting_compilation_in_watch_mode); } - const originalReadFile = host.readFile; - const originalFileExists = host.fileExists; - const originalDirectoryExists = host.directoryExists; - const originalCreateDirectory = host.createDirectory; - const originalWriteFile = host.writeFile; - const originalGetSourceFile = host.getSourceFile; - const readFileCache = createMap(); - const fileExistsCache = createMap(); - const directoryExistsCache = createMap(); - const sourceFileCache = createMap(); - const savedReadFileWithCache = readFileWithCache; - // TODO:: In watch mode as well to use caches for incremental build once we can invalidate caches correctly and have right api // Override readFile for json files and output .d.ts to cache the text - readFileWithCache = fileName => { - const key = toPath(fileName); - const value = readFileCache.get(key); - if (value !== undefined) return value || undefined; - return setReadFileCache(key, fileName); - }; - const setReadFileCache = (key: Path, fileName: string) => { - const newValue = originalReadFile.call(host, fileName); - readFileCache.set(key, newValue || false); - return newValue; - }; - host.readFile = fileName => { - const key = toPath(fileName); - const value = readFileCache.get(key); - if (value !== undefined) return value; // could be .d.ts from output - if (!fileExtensionIs(fileName, Extension.Json)) { - return originalReadFile.call(host, fileName); - } - - return setReadFileCache(key, fileName); - }; - host.getSourceFile = (fileName, languageVersion, onError, shouldCreateNewSourceFile) => { - const key = toPath(fileName); - const value = sourceFileCache.get(key); - if (value) return value; - - const sourceFile = originalGetSourceFile.call(host, fileName, languageVersion, onError, shouldCreateNewSourceFile); - if (sourceFile && (isDeclarationFileName(fileName) || fileExtensionIs(fileName, Extension.Json))) { - sourceFileCache.set(key, sourceFile); - } - return sourceFile; - }; - - // fileExists for any kind of extension - host.fileExists = fileName => { - const key = toPath(fileName); - const value = fileExistsCache.get(key); - if (value !== undefined) return value; - const newValue = originalFileExists.call(host, fileName); - fileExistsCache.set(key, !!newValue); - return newValue; - }; - host.writeFile = (fileName, data, writeByteOrderMark, onError, sourceFiles) => { - const key = toPath(fileName); - fileExistsCache.delete(key); - - const value = readFileCache.get(key); - if (value && value !== data) { - readFileCache.delete(key); - sourceFileCache.delete(key); - } - else { - const sourceFile = sourceFileCache.get(key); - if (sourceFile && sourceFile.text !== data) { - sourceFileCache.delete(key); - } - } - originalWriteFile.call(host, fileName, data, writeByteOrderMark, onError, sourceFiles); - }; - - // directoryExists - if (originalDirectoryExists && originalCreateDirectory) { - host.directoryExists = directory => { - const key = toPath(directory); - const value = directoryExistsCache.get(key); - if (value !== undefined) return value; - const newValue = originalDirectoryExists.call(host, directory); - directoryExistsCache.set(key, !!newValue); - return newValue; - }; - host.createDirectory = directory => { - const key = toPath(directory); - directoryExistsCache.delete(key); - originalCreateDirectory.call(host, directory); - }; - } + const { originalReadFile, originalFileExists, originalDirectoryExists, + originalCreateDirectory, originalWriteFile, originalGetSourceFile, + readFileWithCache: newReadFileWithCache + } = changeCompilerHostToUseCache(host, toPath, /*useCacheForSourceFile*/ true); + const savedReadFileWithCache = readFileWithCache; + readFileWithCache = newReadFileWithCache; const graph = getGlobalDependencyGraph(); reportBuildQueue(graph); diff --git a/src/tsc/tsc.ts b/src/tsc/tsc.ts index 8d9b2f1afa7..ec3da718ef4 100644 --- a/src/tsc/tsc.ts +++ b/src/tsc/tsc.ts @@ -227,6 +227,9 @@ namespace ts { function performCompilation(rootNames: string[], projectReferences: ReadonlyArray | undefined, options: CompilerOptions, configFileParsingDiagnostics?: ReadonlyArray) { const host = createCompilerHost(options); + const currentDirectory = host.getCurrentDirectory(); + const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames()); + changeCompilerHostToUseCache(host, fileName => toPath(fileName, currentDirectory, getCanonicalFileName), /*useCacheForSourceFile*/ false); enableStatistics(options); const programOptions: CreateProgramOptions = {