perf(ivy): basic incremental compilation for ngtsc (#29380)

This commit introduces a mechanism for incremental compilation to the ngtsc
compiler.

Previously, incremental information was used in the construction of the
ts.Program for subsequent compilations, but was not used in ngtsc itself.

This commit adds an IncrementalState class, which tracks state between ngtsc
compilations. Currently, this supports skipping the TypeScript emit step
when the compiler can prove the contents of emit have not changed.

This is implemented for @Injectables as well as for files which don't
contain any Angular decorated types. These are the only files which can be
proven to be safe today.

See ngtsc/incremental/README.md for more details.

PR Close #29380
This commit is contained in:
Alex Rickabaugh
2019-03-18 12:25:26 -07:00
committed by Jason Aden
parent 7316212c1e
commit 7041e61562
11 changed files with 241 additions and 4 deletions

View File

@ -11,6 +11,7 @@ ts_library(
"//packages/compiler",
"//packages/compiler-cli/src/ngtsc/diagnostics",
"//packages/compiler-cli/src/ngtsc/imports",
"//packages/compiler-cli/src/ngtsc/incremental",
"//packages/compiler-cli/src/ngtsc/perf",
"//packages/compiler-cli/src/ngtsc/reflection",
"//packages/compiler-cli/src/ngtsc/translator",

View File

@ -110,6 +110,7 @@ export interface AnalysisOutput<A> {
diagnostics?: ts.Diagnostic[];
factorySymbolName?: string;
typeCheck?: boolean;
allowSkipAnalysisAndEmit?: boolean;
}
/**

View File

@ -11,6 +11,7 @@ import * as ts from 'typescript';
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
import {ImportRewriter} from '../../imports';
import {IncrementalState} from '../../incremental';
import {PerfRecorder} from '../../perf';
import {ClassDeclaration, ReflectionHost, isNamedClassDeclaration, reflectNameOfDeclaration} from '../../reflection';
import {TypeCheckContext} from '../../typecheck';
@ -76,7 +77,8 @@ export class IvyCompilation {
constructor(
private handlers: DecoratorHandler<any, any>[], private checker: ts.TypeChecker,
private reflector: ReflectionHost, private importRewriter: ImportRewriter,
private perf: PerfRecorder, private sourceToFactorySymbols: Map<string, Set<string>>|null) {}
private incrementalState: IncrementalState, private perf: PerfRecorder,
private sourceToFactorySymbols: Map<string, Set<string>>|null) {}
get exportStatements(): Map<string, Map<string, [string, string]>> { return this.reexportMap; }
@ -170,6 +172,10 @@ export class IvyCompilation {
private analyze(sf: ts.SourceFile, preanalyze: boolean): Promise<void>|undefined {
const promises: Promise<void>[] = [];
// This flag begins as true for the file. If even one handler is matched and does not explicitly
// state that analysis/emit can be skipped, then the flag will be set to false.
let allowSkipAnalysisAndEmit = true;
const analyzeClass = (node: ClassDeclaration): void => {
const ivyClass = this.detectHandlersForClass(node);
@ -197,6 +203,11 @@ export class IvyCompilation {
this.sourceToFactorySymbols.get(sf.fileName) !.add(match.analyzed.factorySymbolName);
}
// Update the allowSkipAnalysisAndEmit flag - it will only remain true if match.analyzed
// also explicitly specifies a value of true for the flag.
allowSkipAnalysisAndEmit =
allowSkipAnalysisAndEmit && (!!match.analyzed.allowSkipAnalysisAndEmit);
} catch (err) {
if (err instanceof FatalDiagnosticError) {
this._diagnostics.push(err.toDiagnostic());
@ -239,9 +250,19 @@ export class IvyCompilation {
visit(sf);
const updateIncrementalState = () => {
if (allowSkipAnalysisAndEmit) {
this.incrementalState.markFileAsSafeToSkipEmitIfUnchanged(sf);
}
};
if (preanalyze && promises.length > 0) {
return Promise.all(promises).then(() => undefined);
return Promise.all(promises).then(() => {
updateIncrementalState();
return undefined;
});
} else {
updateIncrementalState();
return undefined;
}
}