From c7e80e19f0bf61d9097e89e4dc1864e51b128821 Mon Sep 17 00:00:00 2001 From: vilicvane Date: Sat, 6 Feb 2016 16:51:25 +0800 Subject: [PATCH 1/5] Avoid writing files that are not changed while compiling incrementally. --- src/compiler/sys.ts | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index 9c3972b27566a..69277f65a92fc 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -39,6 +39,15 @@ namespace ts { referenceCount: number; } + interface OutputFingerprint { + hash: string; + mtime: Date; + } + + interface OutputFingerprintMap { + [fileName: string]: OutputFingerprint; + } + declare var require: any; declare var module: any; declare var process: any; @@ -226,6 +235,7 @@ namespace ts { const _fs = require("fs"); const _path = require("path"); const _os = require("os"); + const _crypto = require("crypto"); // average async stat takes about 30 microseconds // set chunk size to do 30 files in < 1 millisecond @@ -439,12 +449,26 @@ namespace ts { return buffer.toString("utf8"); } + const outputFingerprintMap: OutputFingerprintMap = {}; + function writeFile(fileName: string, data: string, writeByteOrderMark?: boolean): void { // If a BOM is required, emit one if (writeByteOrderMark) { data = "\uFEFF" + data; } + const md5 = getMd5(data); + const mtimeBefore = _fs.existsSync(fileName) && _fs.statSync(fileName).mtime; + + if (mtimeBefore && outputFingerprintMap.hasOwnProperty(fileName)) { + const fingerprint = outputFingerprintMap[fileName]; + + // If output has not been changed, and the file has no external modification + if (fingerprint.hash === md5 && fingerprint.mtime.getTime() === mtimeBefore.getTime()) { + return; + } + } + let fd: number; try { @@ -456,6 +480,19 @@ namespace ts { _fs.closeSync(fd); } } + + const mtimeAfter = _fs.statSync(fileName).mtime; + + outputFingerprintMap[fileName] = { + hash: md5, + mtime: mtimeAfter + }; + } + + function getMd5(data: string): string { + const hash = _crypto.createHash("md5"); + hash.update(data); + return hash.digest("hex"); } function getCanonicalPath(path: string): string { From 63c690813fe82138be9f7d53c814f05992eece74 Mon Sep 17 00:00:00 2001 From: vilicvane Date: Tue, 9 Feb 2016 22:23:43 +0800 Subject: [PATCH 2/5] Create createHash and getModifiedTime under sys, and refactor implementation into compiler host --- src/compiler/program.ts | 39 +++++++++++++++++++++++++++++++++- src/compiler/sys.ts | 46 +++++++++-------------------------------- 2 files changed, 48 insertions(+), 37 deletions(-) diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 9852b2354e2e5..d9d23a9218696 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -559,6 +559,12 @@ namespace ts { sourceMap: false, }; + interface OutputFingerprint { + hash: string; + byteOrderMark: boolean; + mtime: Date; + } + export function createCompilerHost(options: CompilerOptions, setParentNodes?: boolean): CompilerHost { const existingDirectories: Map = {}; @@ -609,11 +615,42 @@ namespace ts { } } + const outputFingerprints: Map = + options.watch && sys.createHash && sys.getModifiedTime ? {} : undefined; + + const fileWriter: typeof sys.writeFile = outputFingerprints ? + (fileName, data, writeByteOrderMark) => { + const hash = sys.createHash(data); + const mtimeBefore = sys.getModifiedTime(fileName); + + if (mtimeBefore && outputFingerprints.hasOwnProperty(fileName)) { + const fingerprint = outputFingerprints[fileName]; + + // If output has not been changed, and the file has no external modification + if (fingerprint.byteOrderMark === writeByteOrderMark && + fingerprint.hash === hash && + fingerprint.mtime.getTime() === mtimeBefore.getTime()) { + return; + } + } + + sys.writeFile(fileName, data, writeByteOrderMark); + + const mtimeAfter = sys.getModifiedTime(fileName); + + outputFingerprints[fileName] = { + hash, + byteOrderMark: writeByteOrderMark, + mtime: mtimeAfter + }; + } : + (fileName, data, writeByteOrderMark) => sys.writeFile(fileName, data, writeByteOrderMark); + function writeFile(fileName: string, data: string, writeByteOrderMark: boolean, onError?: (message: string) => void) { try { const start = new Date().getTime(); ensureDirectoriesExist(getDirectoryPath(normalizePath(fileName))); - sys.writeFile(fileName, data, writeByteOrderMark); + fileWriter(fileName, data, writeByteOrderMark); ioWriteTime += new Date().getTime() - start; } catch (e) { diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index 69277f65a92fc..3f33df61a0f86 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -20,6 +20,8 @@ namespace ts { getExecutingFilePath(): string; getCurrentDirectory(): string; readDirectory(path: string, extension?: string, exclude?: string[]): string[]; + getModifiedTime?(path: string): Date; + createHash?(data: string): string; getMemoryUsage?(): number; exit(exitCode?: number): void; } @@ -39,15 +41,6 @@ namespace ts { referenceCount: number; } - interface OutputFingerprint { - hash: string; - mtime: Date; - } - - interface OutputFingerprintMap { - [fileName: string]: OutputFingerprint; - } - declare var require: any; declare var module: any; declare var process: any; @@ -449,26 +442,12 @@ namespace ts { return buffer.toString("utf8"); } - const outputFingerprintMap: OutputFingerprintMap = {}; - function writeFile(fileName: string, data: string, writeByteOrderMark?: boolean): void { // If a BOM is required, emit one if (writeByteOrderMark) { data = "\uFEFF" + data; } - const md5 = getMd5(data); - const mtimeBefore = _fs.existsSync(fileName) && _fs.statSync(fileName).mtime; - - if (mtimeBefore && outputFingerprintMap.hasOwnProperty(fileName)) { - const fingerprint = outputFingerprintMap[fileName]; - - // If output has not been changed, and the file has no external modification - if (fingerprint.hash === md5 && fingerprint.mtime.getTime() === mtimeBefore.getTime()) { - return; - } - } - let fd: number; try { @@ -480,19 +459,6 @@ namespace ts { _fs.closeSync(fd); } } - - const mtimeAfter = _fs.statSync(fileName).mtime; - - outputFingerprintMap[fileName] = { - hash: md5, - mtime: mtimeAfter - }; - } - - function getMd5(data: string): string { - const hash = _crypto.createHash("md5"); - hash.update(data); - return hash.digest("hex"); } function getCanonicalPath(path: string): string { @@ -593,6 +559,14 @@ namespace ts { return process.cwd(); }, readDirectory, + getModifiedTime(path) { + return _fs.existsSync(path) && _fs.statSync(path).mtime; + }, + createHash(data) { + const hash = _crypto.createHash("md5"); + hash.update(data); + return hash.digest("hex"); + }, getMemoryUsage() { if (global.gc) { global.gc(); From acf965a20e0f238aac6cf48649ab78fec867213b Mon Sep 17 00:00:00 2001 From: vilicvane Date: Wed, 10 Feb 2016 08:47:52 +0800 Subject: [PATCH 3/5] Refine implementation --- src/compiler/program.ts | 2 +- src/compiler/sys.ts | 13 +++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/compiler/program.ts b/src/compiler/program.ts index d9d23a9218696..33c2da50c1a4a 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -623,7 +623,7 @@ namespace ts { const hash = sys.createHash(data); const mtimeBefore = sys.getModifiedTime(fileName); - if (mtimeBefore && outputFingerprints.hasOwnProperty(fileName)) { + if (mtimeBefore && hasProperty(outputFingerprints, fileName)) { const fingerprint = outputFingerprints[fileName]; // If output has not been changed, and the file has no external modification diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index 3f33df61a0f86..9f7903d420129 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -229,6 +229,7 @@ namespace ts { const _path = require("path"); const _os = require("os"); const _crypto = require("crypto"); + let hash: any; // average async stat takes about 30 microseconds // set chunk size to do 30 files in < 1 millisecond @@ -560,10 +561,18 @@ namespace ts { }, readDirectory, getModifiedTime(path) { - return _fs.existsSync(path) && _fs.statSync(path).mtime; + try { + return _fs.statSync(path).mtime; + } + catch (e) { + return undefined; + } }, createHash(data) { - const hash = _crypto.createHash("md5"); + if (!hash) { + hash = _crypto.createHash("md5"); + } + hash.update(data); return hash.digest("hex"); }, From 0282c0463d975c48d9cdda429690a36544941e97 Mon Sep 17 00:00:00 2001 From: vilicvane Date: Wed, 10 Feb 2016 08:50:22 +0800 Subject: [PATCH 4/5] Revert hash object caching --- src/compiler/sys.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index 9f7903d420129..99008d4cc7e8d 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -229,7 +229,6 @@ namespace ts { const _path = require("path"); const _os = require("os"); const _crypto = require("crypto"); - let hash: any; // average async stat takes about 30 microseconds // set chunk size to do 30 files in < 1 millisecond @@ -569,10 +568,7 @@ namespace ts { } }, createHash(data) { - if (!hash) { - hash = _crypto.createHash("md5"); - } - + const hash = _crypto.createHash("md5"); hash.update(data); return hash.digest("hex"); }, From a4813052922da694a34474d884f0f633482fd588 Mon Sep 17 00:00:00 2001 From: vilicvane Date: Thu, 11 Feb 2016 16:38:21 +0800 Subject: [PATCH 5/5] Reorganize related functions --- src/compiler/program.ts | 62 +++++++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 27 deletions(-) diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 33c2da50c1a4a..0f98b46d933c2 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -615,42 +615,50 @@ namespace ts { } } - const outputFingerprints: Map = - options.watch && sys.createHash && sys.getModifiedTime ? {} : undefined; - - const fileWriter: typeof sys.writeFile = outputFingerprints ? - (fileName, data, writeByteOrderMark) => { - const hash = sys.createHash(data); - const mtimeBefore = sys.getModifiedTime(fileName); - - if (mtimeBefore && hasProperty(outputFingerprints, fileName)) { - const fingerprint = outputFingerprints[fileName]; - - // If output has not been changed, and the file has no external modification - if (fingerprint.byteOrderMark === writeByteOrderMark && - fingerprint.hash === hash && - fingerprint.mtime.getTime() === mtimeBefore.getTime()) { - return; - } + let outputFingerprints: Map; + + function writeFileIfUpdated(fileName: string, data: string, writeByteOrderMark: boolean): void { + if (!outputFingerprints) { + outputFingerprints = {}; + } + + const hash = sys.createHash(data); + const mtimeBefore = sys.getModifiedTime(fileName); + + if (mtimeBefore && hasProperty(outputFingerprints, fileName)) { + const fingerprint = outputFingerprints[fileName]; + + // If output has not been changed, and the file has no external modification + if (fingerprint.byteOrderMark === writeByteOrderMark && + fingerprint.hash === hash && + fingerprint.mtime.getTime() === mtimeBefore.getTime()) { + return; } + } - sys.writeFile(fileName, data, writeByteOrderMark); + sys.writeFile(fileName, data, writeByteOrderMark); - const mtimeAfter = sys.getModifiedTime(fileName); + const mtimeAfter = sys.getModifiedTime(fileName); - outputFingerprints[fileName] = { - hash, - byteOrderMark: writeByteOrderMark, - mtime: mtimeAfter - }; - } : - (fileName, data, writeByteOrderMark) => sys.writeFile(fileName, data, writeByteOrderMark); + outputFingerprints[fileName] = { + hash, + byteOrderMark: writeByteOrderMark, + mtime: mtimeAfter + }; + } function writeFile(fileName: string, data: string, writeByteOrderMark: boolean, onError?: (message: string) => void) { try { const start = new Date().getTime(); ensureDirectoriesExist(getDirectoryPath(normalizePath(fileName))); - fileWriter(fileName, data, writeByteOrderMark); + + if (options.watch && sys.createHash && sys.getModifiedTime) { + writeFileIfUpdated(fileName, data, writeByteOrderMark); + } + else { + sys.writeFile(fileName, data, writeByteOrderMark); + } + ioWriteTime += new Date().getTime() - start; } catch (e) {