Skip to content

really write output file only if needed #13776

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 45 additions & 6 deletions src/compiler/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ namespace ts {
hash: string;
byteOrderMark: boolean;
mtime: Date;
updated: boolean;
}

export function createCompilerHost(options: CompilerOptions, setParentNodes?: boolean): CompilerHost {
Expand Down Expand Up @@ -138,29 +139,66 @@ namespace ts {

const hash = sys.createHash(data);
const mtimeBefore = sys.getModifiedTime(fileName);
let forceUpdate = false;

if (mtimeBefore) {
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()) {
fingerprint.updated = false;
return;
}
if (fingerprint) {
forceUpdate = true;
}
}

writeFileIfDiff(fileName, data, writeByteOrderMark, hash, forceUpdate);
}

function writeFileIfDiff(fileName: string, data: string, writeByteOrderMark: boolean, hash?: string, forceUpdate?: boolean) {
if (!outputFingerprints) {
outputFingerprints = createMap<OutputFingerprint>();
}
hash = hash !== undefined ? hash : sys.createHash(data);

sys.writeFile(fileName, data, writeByteOrderMark);
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 mtimeAfter = sys.getModifiedTime(fileName);
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");
Expand All @@ -170,7 +208,7 @@ namespace ts {
writeFileIfUpdated(fileName, data, writeByteOrderMark);
}
else {
sys.writeFile(fileName, data, writeByteOrderMark);
writeFileIfDiff(fileName, data, writeByteOrderMark);
}

performance.mark("afterIOWrite");
Expand All @@ -195,6 +233,7 @@ namespace ts {
getDefaultLibLocation,
getDefaultLibFileName: options => combinePaths(getDefaultLibLocation(), getDefaultLibFileName(options)),
writeFile,
isOutputFileUpdated,
getCurrentDirectory: memoize(() => sys.getCurrentDirectory()),
useCaseSensitiveFileNames: () => sys.useCaseSensitiveFileNames,
getCanonicalFileName,
Expand Down
32 changes: 27 additions & 5 deletions src/compiler/sys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
/**
Expand Down Expand Up @@ -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;
}
Expand All @@ -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();
Expand Down Expand Up @@ -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;
}
Expand All @@ -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");
}
Expand Down Expand Up @@ -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) {
Expand Down
7 changes: 4 additions & 3 deletions src/compiler/tsc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ namespace ts {
}
}

function reportEmittedFiles(files: string[]): void {
function reportEmittedFiles(host: CompilerHost, files: string[]): void {
if (!files || files.length == 0) {
return;
}
Expand All @@ -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}`);
}
}

Expand Down Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions src/services/transpile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
outputText = text;
}
},
isOutputFileUpdated: (name) => fileExtensionIs(name, ".map") ? !!sourceMapText : !!outputText,
getDefaultLibFileName: () => "lib.d.ts",
useCaseSensitiveFileNames: () => false,
getCanonicalFileName: fileName => fileName,
Expand Down