Permalink
Browse files

perf(compiler): only emit changed files for incremental compilation

For now, we always create all generated files, but diff them
before we pass them to TypeScript.

For the user files, we compare the programs and only emit changed
TypeScript files.

This also adds more diagnostic messages if the `—diagnostics` flag
is passed to the command line.
  • Loading branch information...
tbosch authored and alxhub committed Sep 29, 2017
1 parent b086891 commit 745b59f49cacb55c54d11e948c011fbbf8b724f7
@@ -18,7 +18,7 @@ import * as api from './transformers/api';
import * as ngc from './transformers/entry_points';
import {GENERATED_FILES} from './transformers/util';
import {exitCodeFromResult, performCompilation, readConfiguration, formatDiagnostics, Diagnostics, ParsedConfiguration, PerformCompilationResult} from './perform_compile';
import {exitCodeFromResult, performCompilation, readConfiguration, formatDiagnostics, Diagnostics, ParsedConfiguration, PerformCompilationResult, filterErrorsAndWarnings} from './perform_compile';
import {performWatchCompilationcreatePerformWatchHost} from './perform_watch';
import {isSyntaxError} from '@angular/compiler';
@@ -130,8 +130,9 @@ export function readCommandLineAndConfiguration(
function reportErrorsAndExit(
options: api.CompilerOptions, allDiagnostics: Diagnostics,
consoleError: (s: string) => void = console.error): number {
if (allDiagnostics.length) {
consoleError(formatDiagnostics(options, allDiagnostics));
const errorsAndWarnings = filterErrorsAndWarnings(allDiagnostics);
if (errorsAndWarnings.length) {
consoleError(formatDiagnostics(options, errorsAndWarnings));
}
return exitCodeFromResult(allDiagnostics);
}
@@ -19,9 +19,11 @@ import {ParseSourceSpan} from '@angular/compiler';
import * as ts from 'typescript';
import {formatDiagnostics as formatDiagnosticsOrig} from './perform_compile';
import {Program as ProgramOrig} from './transformers/api';
import {createCompilerHost as createCompilerOrig} from './transformers/compiler_host';
import {createProgram as createProgramOrig} from './transformers/program';
// Interfaces from ./transformers/api;
export interface Diagnostic {
messageText: string;
@@ -92,12 +94,6 @@ export interface TsEmitArguments {
export interface TsEmitCallback { (args: TsEmitArguments): ts.EmitResult; }
export interface LibrarySummary {
fileName: string;
text: string;
sourceFile?: ts.SourceFile;
}
export interface Program {
getTsProgram(): ts.Program;
getTsOptionDiagnostics(cancellationToken?: ts.CancellationToken): ts.Diagnostic[];
@@ -116,15 +112,14 @@ export interface Program {
customTransformers?: CustomTransformers,
emitCallback?: TsEmitCallback
}): ts.EmitResult;
getLibrarySummaries(): LibrarySummary[];
}
// Wrapper for createProgram.
export function createProgram(
{rootNames, options, host, oldProgram}:
{rootNames: string[], options: CompilerOptions, host: CompilerHost, oldProgram?: Program}):
Program {
return createProgramOrig({rootNames, options, host, oldProgram});
return createProgramOrig({rootNames, options, host, oldProgram: oldProgram as ProgramOrig});
}
// Wrapper for createCompilerHost.
@@ -13,11 +13,16 @@ import * as ts from 'typescript';
import * as api from './transformers/api';
import * as ng from './transformers/entry_points';
import {createMessageDiagnostic} from './transformers/util';
const TS_EXT = /\.ts$/;
export type Diagnostics = Array<ts.Diagnostic|api.Diagnostic>;
export function filterErrorsAndWarnings(diagnostics: Diagnostics): Diagnostics {
return diagnostics.filter(d => d.category !== ts.DiagnosticCategory.Message);
}
export function formatDiagnostics(options: api.CompilerOptions, diags: Diagnostics): string {
if (diags && diags.length) {
const tsFormatHost: ts.FormatDiagnosticsHost = {
@@ -123,7 +128,7 @@ export interface PerformCompilationResult {
}
export function exitCodeFromResult(diags: Diagnostics | undefined): number {
if (!diags || diags.length === 0) {
if (!diags || filterErrorsAndWarnings(diags).length === 0) {
// If we have a result and didn't get any errors, we succeeded.
return 0;
}
@@ -154,7 +159,13 @@ export function performCompilation({rootNames, options, host, oldProgram, emitCa
program = ng.createProgram({rootNames, host, options, oldProgram});
const beforeDiags = Date.now();
allDiagnostics.push(...gatherDiagnostics(program !));
if (options.diagnostics) {
const afterDiags = Date.now();
allDiagnostics.push(
createMessageDiagnostic(`Time for diagnostics: ${afterDiags - beforeDiags}ms.`));
}
if (!hasErrors(allDiagnostics)) {
emitResult = program !.emit({emitCallback, customTransformers, emitFlags});
@@ -13,27 +13,7 @@ import * as ts from 'typescript';
import {Diagnostics, ParsedConfiguration, PerformCompilationResult, exitCodeFromResult, performCompilation, readConfiguration} from './perform_compile';
import * as api from './transformers/api';
import {createCompilerHost} from './transformers/entry_points';
const ChangeDiagnostics = {
Compilation_complete_Watching_for_file_changes: {
category: ts.DiagnosticCategory.Message,
messageText: 'Compilation complete. Watching for file changes.',
code: api.DEFAULT_ERROR_CODE,
source: api.SOURCE
},
Compilation_failed_Watching_for_file_changes: {
category: ts.DiagnosticCategory.Message,
messageText: 'Compilation failed. Watching for file changes.',
code: api.DEFAULT_ERROR_CODE,
source: api.SOURCE
},
File_change_detected_Starting_incremental_compilation: {
category: ts.DiagnosticCategory.Message,
messageText: 'File change detected. Starting incremental compilation.',
code: api.DEFAULT_ERROR_CODE,
source: api.SOURCE
},
};
import {createMessageDiagnostic} from './transformers/util';
function totalCompilationTimeDiagnostic(timeInMillis: number): api.Diagnostic {
let duration: string;
@@ -231,9 +211,11 @@ export function performWatchCompilation(host: PerformWatchHost):
const exitCode = exitCodeFromResult(compileResult.diagnostics);
if (exitCode == 0) {
cachedProgram = compileResult.program;
host.reportDiagnostics([ChangeDiagnostics.Compilation_complete_Watching_for_file_changes]);
host.reportDiagnostics(
[createMessageDiagnostic('Compilation complete. Watching for file changes.')]);
} else {
host.reportDiagnostics([ChangeDiagnostics.Compilation_failed_Watching_for_file_changes]);
host.reportDiagnostics(
[createMessageDiagnostic('Compilation failed. Watching for file changes.')]);
}
return compileResult.diagnostics;
@@ -285,7 +267,7 @@ export function performWatchCompilation(host: PerformWatchHost):
function recompile() {
timerHandleForRecompilation = undefined;
host.reportDiagnostics(
[ChangeDiagnostics.File_change_detected_Starting_incremental_compilation]);
[createMessageDiagnostic('File change detected. Starting incremental compilation.')]);
doCompilation();
}
}
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {ParseSourceSpan} from '@angular/compiler';
import {GeneratedFile, ParseSourceSpan} from '@angular/compiler';
import * as ts from 'typescript';
export const DEFAULT_ERROR_CODE = 100;
@@ -220,6 +220,9 @@ export interface TsEmitArguments {
export interface TsEmitCallback { (args: TsEmitArguments): ts.EmitResult; }
/**
* @internal
*/
export interface LibrarySummary {
fileName: string;
text: string;
@@ -306,6 +309,13 @@ export interface Program {
* Returns the .d.ts / .ngsummary.json / .ngfactory.d.ts files of libraries that have been emitted
* in this program or previous programs with paths that emulate the fact that these libraries
* have been compiled before with no outDir.
*
* @internal
*/
getLibrarySummaries(): Map<string, LibrarySummary>;
/**
* @internal
*/
getLibrarySummaries(): LibrarySummary[];
getEmittedGeneratedFiles(): Map<string, GeneratedFile>;
}
@@ -58,7 +58,6 @@ export class TsCompilerAotCompilerTypeCheckHostAdapter extends
private generatedSourceFiles = new Map<string, GenSourceFile>();
private generatedCodeFor = new Map<string, string[]>();
private emitter = new TypeScriptEmitter();
private librarySummaries = new Map<string, LibrarySummary>();
getCancellationToken: () => ts.CancellationToken;
getDefaultLibLocation: () => string;
trace: (s: string) => void;
@@ -68,9 +67,8 @@ export class TsCompilerAotCompilerTypeCheckHostAdapter extends
constructor(
private rootFiles: string[], options: CompilerOptions, context: CompilerHost,
private metadataProvider: MetadataProvider, private codeGenerator: CodeGenerator,
librarySummaries: LibrarySummary[]) {
private librarySummaries = new Map<string, LibrarySummary>()) {
super(options, context);
librarySummaries.forEach(summary => this.librarySummaries.set(summary.fileName, summary));
this.moduleResolutionCache = ts.createModuleResolutionCache(
this.context.getCurrentDirectory !(), this.context.getCanonicalFileName.bind(this.context));
const basePath = this.options.basePath !;
@@ -10,6 +10,7 @@ import {GeneratedFile} from '@angular/compiler';
import * as ts from 'typescript';
import {TypeScriptNodeEmitter} from './node_emitter';
import {GENERATED_FILES} from './util';
const PREAMBLE = `/**
* @fileoverview This file is generated by the Angular template compiler.
@@ -18,17 +19,17 @@ const PREAMBLE = `/**
* tslint:disable
*/`;
export function getAngularEmitterTransformFactory(generatedFiles: GeneratedFile[]): () =>
export function getAngularEmitterTransformFactory(generatedFiles: Map<string, GeneratedFile>): () =>
(sourceFile: ts.SourceFile) => ts.SourceFile {
return function() {
const map = new Map(generatedFiles.filter(g => g.stmts && g.stmts.length)
.map<[string, GeneratedFile]>(g => [g.genFileUrl, g]));
const emitter = new TypeScriptNodeEmitter();
return function(sourceFile: ts.SourceFile): ts.SourceFile {
const g = map.get(sourceFile.fileName);
const g = generatedFiles.get(sourceFile.fileName);
if (g && g.stmts) {
const [newSourceFile] = emitter.updateSourceFile(sourceFile, g.stmts, PREAMBLE);
return newSourceFile;
} else if (GENERATED_FILES.test(sourceFile.fileName)) {
return ts.updateSourceFileNode(sourceFile, []);
}
return sourceFile;
};
Oops, something went wrong.

0 comments on commit 745b59f

Please sign in to comment.