From 4149a4a56fe7b282da882f5a4a1ff7de60887cf7 Mon Sep 17 00:00:00 2001 From: gdh1995 Date: Tue, 31 Jan 2017 16:25:26 +0800 Subject: [PATCH 1/2] really write output file only if needed --- src/compiler/program.ts | 45 +++++++++++++++++++++++++++++++++------ src/compiler/sys.ts | 32 +++++++++++++++++++++++----- src/compiler/tsc.ts | 7 +++--- src/compiler/types.ts | 1 + src/services/transpile.ts | 1 + 5 files changed, 72 insertions(+), 14 deletions(-) diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 650711afc790a..5fc8e53967bfe 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -76,6 +76,7 @@ namespace ts { hash: string; byteOrderMark: boolean; mtime: Date; + updated: boolean; } export function createCompilerHost(options: CompilerOptions, setParentNodes?: boolean): CompilerHost { @@ -143,24 +144,55 @@ namespace ts { const fingerprint = outputFingerprints.get(fileName); // If output has not been changed, and the file has no external modification if (fingerprint && - fingerprint.byteOrderMark === writeByteOrderMark && + fingerprint.byteOrderMark === !!writeByteOrderMark && fingerprint.hash === hash && fingerprint.mtime.getTime() === mtimeBefore.getTime()) { return; } } - sys.writeFile(fileName, data, writeByteOrderMark); + writeFileIfDiff(fileName, data, writeByteOrderMark, hash); + } - const mtimeAfter = sys.getModifiedTime(fileName); + function writeFileIfDiff(fileName: string, data: string, writeByteOrderMark: boolean, hash?: string) { + let oldContent: string | undefined, info: { + bom?: string + }; + if (!outputFingerprints) { + outputFingerprints = createMap(); + } + hash = hash !== undefined ? hash : sys.createHash(data); + + info = {}; + try { + oldContent = sys.readFile(fileName, undefined, info); + } + catch (e) { + } + const sameBOM = info.bom === undefined || (info.bom === "\uFEFF") === !!writeByteOrderMark; + const isSame = oldContent !== undefined && oldContent === data && sameBOM; + + if (!isSame) { + sys.writeFile(fileName, data, writeByteOrderMark); + } + const mtime = sys.getModifiedTime(fileName); outputFingerprints.set(fileName, { hash, - byteOrderMark: writeByteOrderMark, - mtime: mtimeAfter + byteOrderMark: !!writeByteOrderMark, + mtime, + updated: !isSame }); } + function isOutputFileUpdated(fileName: string): boolean { + if (outputFingerprints) { + const fingerprint = outputFingerprints.get(fileName); + return !!fingerprint.updated; + } + return false; + } + function writeFile(fileName: string, data: string, writeByteOrderMark: boolean, onError?: (message: string) => void) { try { performance.mark("beforeIOWrite"); @@ -170,7 +202,7 @@ namespace ts { writeFileIfUpdated(fileName, data, writeByteOrderMark); } else { - sys.writeFile(fileName, data, writeByteOrderMark); + writeFileIfDiff(fileName, data, writeByteOrderMark); } performance.mark("afterIOWrite"); @@ -195,6 +227,7 @@ namespace ts { getDefaultLibLocation, getDefaultLibFileName: options => combinePaths(getDefaultLibLocation(), getDefaultLibFileName(options)), writeFile, + isOutputFileUpdated, getCurrentDirectory: memoize(() => sys.getCurrentDirectory()), useCaseSensitiveFileNames: () => sys.useCaseSensitiveFileNames, getCanonicalFileName, diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index 71ff4485343c6..b645e6df6e4d2 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -17,7 +17,7 @@ namespace ts { newLine: string; useCaseSensitiveFileNames: boolean; write(s: string): void; - readFile(path: string, encoding?: string): string; + readFile(path: string, encoding?: string, info?: {bom?: string}): string; getFileSize?(path: string): number; writeFile(path: string, data: string, writeByteOrderMark?: boolean): void; /** @@ -106,7 +106,13 @@ namespace ts { args[i] = WScript.Arguments.Item(i); } - function readFile(fileName: string, encoding?: string): string { + function readFile(fileName: string, encoding?: string, info?: {bom?: string}): string { + if (info) { + info.bom = undefined; + } + else { + info = {}; + } if (!fso.FileExists(fileName)) { return undefined; } @@ -124,7 +130,10 @@ namespace ts { // Position must be at 0 before encoding can be changed fileStream.Position = 0; // [0xFF,0xFE] and [0xFE,0xFF] mean utf-16 (little or big endian), otherwise default to utf-8 - fileStream.Charset = bom.length >= 2 && (bom.charCodeAt(0) === 0xFF && bom.charCodeAt(1) === 0xFE || bom.charCodeAt(0) === 0xFE && bom.charCodeAt(1) === 0xFF) ? "unicode" : "utf-8"; + const isUnicode = bom.length >= 2 && (bom.charCodeAt(0) === 0xFF && bom.charCodeAt(1) === 0xFE || bom.charCodeAt(0) === 0xFE && bom.charCodeAt(1) === 0xFF); + const isUtf8BOM = bom.length >= 3 && (bom.charCodeAt(0) === 0xEF && bom.charCodeAt(1) === 0xBB && bom.charCodeAt(2) === 0xBF); + fileStream.Charset = isUnicode ? "unicode" : "utf-8"; + info.bom = isUnicode ? bom.substring(0, 2) : isUtf8BOM ? "\uFEFF" : ""; } // ReadText method always strips byte order mark from resulting string return fileStream.ReadText(); @@ -332,7 +341,13 @@ namespace ts { const platform: string = _os.platform(); const useCaseSensitiveFileNames = isFileSystemCaseSensitive(); - function readFile(fileName: string, _encoding?: string): string { + function readFile(fileName: string, _encoding?: string, info?: {bom?: string}): string { + if (info) { + info.bom = undefined; + } + else { + info = {}; + } if (!fileExists(fileName)) { return undefined; } @@ -347,16 +362,20 @@ namespace ts { buffer[i] = buffer[i + 1]; buffer[i + 1] = temp; } + info.bom = "\uFFFE"; return buffer.toString("utf16le", 2); } if (len >= 2 && buffer[0] === 0xFF && buffer[1] === 0xFE) { // Little endian UTF-16 byte order mark detected + info.bom = "\uFEFF"; return buffer.toString("utf16le", 2); } if (len >= 3 && buffer[0] === 0xEF && buffer[1] === 0xBB && buffer[2] === 0xBF) { // UTF-8 byte order mark detected + info.bom = "\uFEFF"; return buffer.toString("utf8", 3); } + info.bom = ""; // Default is UTF-8 with no byte order mark return buffer.toString("utf8"); } @@ -587,8 +606,11 @@ namespace ts { args: ChakraHost.args, useCaseSensitiveFileNames: !!ChakraHost.useCaseSensitiveFileNames, write: ChakraHost.echo, - readFile(path: string, _encoding?: string) { + readFile(path: string, _encoding?: string, info?: {bom?: string}) { // encoding is automatically handled by the implementation in ChakraHost + if (info) { + info.bom = undefined; + } return ChakraHost.readFile(path); }, writeFile(path: string, data: string, writeByteOrderMark?: boolean) { diff --git a/src/compiler/tsc.ts b/src/compiler/tsc.ts index b1ffcff43a4f7..21c43032add9b 100644 --- a/src/compiler/tsc.ts +++ b/src/compiler/tsc.ts @@ -29,7 +29,7 @@ namespace ts { } } - function reportEmittedFiles(files: string[]): void { + function reportEmittedFiles(host: CompilerHost, files: string[]): void { if (!files || files.length == 0) { return; } @@ -38,8 +38,9 @@ namespace ts { for (const file of files) { const filepath = getNormalizedAbsolutePath(file, currentDir); + const updated = host.isOutputFileUpdated ? host.isOutputFileUpdated(filepath) : true; - sys.write(`TSFILE: ${filepath}${sys.newLine}`); + sys.write(`TSFILE${updated ? "" : " (no changes)"}: ${filepath}${sys.newLine}`); } } @@ -569,7 +570,7 @@ namespace ts { reportDiagnostics(sortAndDeduplicateDiagnostics(diagnostics), compilerHost); - reportEmittedFiles(emitOutput.emittedFiles); + reportEmittedFiles(compilerHost, emitOutput.emittedFiles); if (emitOutput.emitSkipped && diagnostics.length > 0) { // If the emitter didn't emit anything, then pass that value along. diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 8b49af402cf6c..580de8e98a937 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3616,6 +3616,7 @@ getDefaultLibFileName(options: CompilerOptions): string; getDefaultLibLocation?(): string; writeFile: WriteFileCallback; + isOutputFileUpdated?(fileName: string): boolean; getCurrentDirectory(): string; getDirectories(path: string): string[]; getCanonicalFileName(fileName: string): string; diff --git a/src/services/transpile.ts b/src/services/transpile.ts index 0b90e9d030b72..95abd7365917a 100644 --- a/src/services/transpile.ts +++ b/src/services/transpile.ts @@ -85,6 +85,7 @@ outputText = text; } }, + isOutputFileUpdated: (name) => fileExtensionIs(name, ".map") ? !!sourceMapText : !!outputText, getDefaultLibFileName: () => "lib.d.ts", useCaseSensitiveFileNames: () => false, getCanonicalFileName: fileName => fileName, From f1dcaf07514a9e134c53fd0b3358664f363ad66d Mon Sep 17 00:00:00 2001 From: gdh1995 Date: Tue, 31 Jan 2017 22:07:54 +0800 Subject: [PATCH 2/2] fix some problems in writeFileIfUpdated 1. reset fingerprint.updated to false if no more changes found 2. write directly without pre-reading if fingerprint is not matched 3. still check file content during the first time of compiling process --- src/compiler/program.ts | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 5fc8e53967bfe..6e693d67b4444 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -139,6 +139,7 @@ namespace ts { const hash = sys.createHash(data); const mtimeBefore = sys.getModifiedTime(fileName); + let forceUpdate = false; if (mtimeBefore) { const fingerprint = outputFingerprints.get(fileName); @@ -147,30 +148,35 @@ namespace ts { fingerprint.byteOrderMark === !!writeByteOrderMark && fingerprint.hash === hash && fingerprint.mtime.getTime() === mtimeBefore.getTime()) { + fingerprint.updated = false; return; } + if (fingerprint) { + forceUpdate = true; + } } - writeFileIfDiff(fileName, data, writeByteOrderMark, hash); + writeFileIfDiff(fileName, data, writeByteOrderMark, hash, forceUpdate); } - function writeFileIfDiff(fileName: string, data: string, writeByteOrderMark: boolean, hash?: string) { - let oldContent: string | undefined, info: { - bom?: string - }; + function writeFileIfDiff(fileName: string, data: string, writeByteOrderMark: boolean, hash?: string, forceUpdate?: boolean) { if (!outputFingerprints) { outputFingerprints = createMap(); } hash = hash !== undefined ? hash : sys.createHash(data); - info = {}; - try { - oldContent = sys.readFile(fileName, undefined, info); - } - catch (e) { + let isSame = false; + if (forceUpdate !== true) { + const info = {} as { bom?: string }; + let oldContent: string | undefined; + try { + oldContent = sys.readFile(fileName, undefined, info); + } + catch (e) { + } + const sameBOM = info.bom === undefined || (info.bom === "\uFEFF") === !!writeByteOrderMark; + isSame = oldContent !== undefined && oldContent === data && sameBOM; } - const sameBOM = info.bom === undefined || (info.bom === "\uFEFF") === !!writeByteOrderMark; - const isSame = oldContent !== undefined && oldContent === data && sameBOM; if (!isSame) { sys.writeFile(fileName, data, writeByteOrderMark);