feat(compiler): reuse the TypeScript typecheck for template typechecking. (#19152)

This speeds up the compilation process significantly.

Also introduces a new option `fullTemplateTypeCheck` to do more checks in templates:
- check expressions inside of templatized content (e.g. inside of `<div *ngIf>`).
- check the arguments of calls to the `transform` function of pipes
- check references to directives that were exposed as variables via `exportAs`
PR Close #19152
This commit is contained in:
Tobias Bosch
2017-09-11 15:18:19 -07:00
committed by Matias Niemelä
parent 554fe65690
commit 996c7c2dde
22 changed files with 712 additions and 401 deletions

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {CompileDirectiveMetadata, CompileDirectiveSummary, CompileIdentifierMetadata, CompileNgModuleMetadata, CompileNgModuleSummary, CompilePipeMetadata, CompileProviderMetadata, CompileStylesheetMetadata, CompileSummaryKind, CompileTypeMetadata, CompileTypeSummary, componentFactoryName, createHostComponentMeta, flatten, identifierName, sourceUrl, templateSourceUrl} from '../compile_metadata';
import {CompileDirectiveMetadata, CompileDirectiveSummary, CompileIdentifierMetadata, CompileNgModuleMetadata, CompileNgModuleSummary, CompilePipeMetadata, CompilePipeSummary, CompileProviderMetadata, CompileStylesheetMetadata, CompileSummaryKind, CompileTypeMetadata, CompileTypeSummary, componentFactoryName, createHostComponentMeta, flatten, identifierName, sourceUrl, templateSourceUrl} from '../compile_metadata';
import {CompilerConfig} from '../config';
import {MessageBundle} from '../i18n/message_bundle';
import {Identifiers, createTokenForExternalReference} from '../identifiers';
@ -19,8 +19,10 @@ import * as o from '../output/output_ast';
import {ParseError} from '../parse_util';
import {CompiledStylesheet, StyleCompiler} from '../style_compiler';
import {SummaryResolver} from '../summary_resolver';
import {TemplateAst} from '../template_parser/template_ast';
import {TemplateParser} from '../template_parser/template_parser';
import {OutputContext, syntaxError} from '../util';
import {TypeCheckCompiler} from '../view_compiler/type_check_compiler';
import {ViewCompileResult, ViewCompiler} from '../view_compiler/view_compiler';
import {AotCompilerHost} from './compiler_host';
@ -32,11 +34,15 @@ import {createForJitStub, serializeSummaries} from './summary_serializer';
import {ngfactoryFilePath, splitTypescriptSuffix, summaryFileName, summaryForJitFileName, summaryForJitName} from './util';
export class AotCompiler {
private _templateAstCache =
new Map<StaticSymbol, {template: TemplateAst[], pipes: CompilePipeSummary[]}>();
constructor(
private _config: CompilerConfig, private _host: AotCompilerHost,
private _reflector: StaticReflector, private _metadataResolver: CompileMetadataResolver,
private _templateParser: TemplateParser, private _styleCompiler: StyleCompiler,
private _viewCompiler: ViewCompiler, private _ngModuleCompiler: NgModuleCompiler,
private _htmlParser: HtmlParser, private _templateParser: TemplateParser,
private _styleCompiler: StyleCompiler, private _viewCompiler: ViewCompiler,
private _typeCheckCompiler: TypeCheckCompiler, private _ngModuleCompiler: NgModuleCompiler,
private _outputEmitter: OutputEmitter,
private _summaryResolver: SummaryResolver<StaticSymbol>, private _localeId: string|null,
private _translationFormat: string|null, private _enableSummariesForJit: boolean|null,
@ -66,9 +72,10 @@ export class AotCompiler {
}
emitAllStubs(analyzeResult: NgAnalyzedModules): GeneratedFile[] {
const {files} = analyzeResult;
const {files, ngModuleByPipeOrDirective} = analyzeResult;
const sourceModules = files.map(
file => this._compileStubFile(file.srcUrl, file.directives, file.pipes, file.ngModules));
file => this._compileStubFile(
file.srcUrl, ngModuleByPipeOrDirective, file.directives, file.pipes, file.ngModules));
return flatten(sourceModules);
}
@ -112,7 +119,8 @@ export class AotCompiler {
}
private _compileStubFile(
srcFileUrl: string, directives: StaticSymbol[], pipes: StaticSymbol[],
srcFileUrl: string, ngModuleByPipeOrDirective: Map<StaticSymbol, CompileNgModuleMetadata>,
directives: StaticSymbol[], pipes: StaticSymbol[],
ngModules: StaticSymbol[]): GeneratedFile[] {
const fileSuffix = splitTypescriptSuffix(srcFileUrl, true)[1];
const generatedFiles: GeneratedFile[] = [];
@ -130,10 +138,17 @@ export class AotCompiler {
// the generated code)
directives.forEach((dirType) => {
const compMeta = this._metadataResolver.getDirectiveMetadata(<any>dirType);
if (!compMeta.isComponent) {
return;
}
const ngModule = ngModuleByPipeOrDirective.get(dirType);
if (!ngModule) {
throw new Error(
`Internal Error: cannot determine the module for component ${identifierName(compMeta.type)}!`);
}
this._compileComponentTypeCheckBlock(
ngFactoryOutputCtx, compMeta, ngModule, ngModule.transitiveModule.directives);
// Note: compMeta is a component and therefore template is non null.
compMeta.template !.externalStylesheets.forEach((stylesheetMeta) => {
const styleContext = this._createOutputContext(_stylesModuleUrl(
@ -285,7 +300,8 @@ export class AotCompiler {
ngModule: CompileNgModuleMetadata, fileSuffix: string): void {
const hostType = this._metadataResolver.getHostComponentType(compMeta.type.reference);
const hostMeta = createHostComponentMeta(
hostType, compMeta, this._metadataResolver.getHostComponentViewClass(hostType));
hostType, compMeta, this._metadataResolver.getHostComponentViewClass(hostType),
this._htmlParser);
const hostViewFactoryVar =
this._compileComponent(outputCtx, hostMeta, ngModule, [compMeta.type], null, fileSuffix)
.viewClassVar;
@ -320,19 +336,32 @@ export class AotCompiler {
[o.StmtModifier.Final, o.StmtModifier.Exported]));
}
private _compileComponent(
outputCtx: OutputContext, compMeta: CompileDirectiveMetadata,
ngModule: CompileNgModuleMetadata, directiveIdentifiers: CompileIdentifierMetadata[],
componentStyles: CompiledStylesheet|null, fileSuffix: string): ViewCompileResult {
private _parseTemplate(
compMeta: CompileDirectiveMetadata, ngModule: CompileNgModuleMetadata,
directiveIdentifiers: CompileIdentifierMetadata[]):
{template: TemplateAst[], pipes: CompilePipeSummary[]} {
let result = this._templateAstCache.get(compMeta.type.reference);
if (result) {
return result;
}
const preserveWhitespaces = compMeta !.template !.preserveWhitespaces;
const directives =
directiveIdentifiers.map(dir => this._metadataResolver.getDirectiveSummary(dir.reference));
const pipes = ngModule.transitiveModule.pipes.map(
pipe => this._metadataResolver.getPipeSummary(pipe.reference));
const preserveWhitespaces = compMeta !.template !.preserveWhitespaces;
const {template: parsedTemplate, pipes: usedPipes} = this._templateParser.parse(
compMeta, compMeta.template !.template !, directives, pipes, ngModule.schemas,
result = this._templateParser.parse(
compMeta, compMeta.template !.htmlAst !, directives, pipes, ngModule.schemas,
templateSourceUrl(ngModule.type, compMeta, compMeta.template !), preserveWhitespaces);
this._templateAstCache.set(compMeta.type.reference, result);
return result;
}
private _compileComponent(
outputCtx: OutputContext, compMeta: CompileDirectiveMetadata,
ngModule: CompileNgModuleMetadata, directiveIdentifiers: CompileIdentifierMetadata[],
componentStyles: CompiledStylesheet|null, fileSuffix: string): ViewCompileResult {
const {template: parsedTemplate, pipes: usedPipes} =
this._parseTemplate(compMeta, ngModule, directiveIdentifiers);
const stylesExpr = componentStyles ? o.variable(componentStyles.stylesVar) : o.literalArr([]);
const viewResult = this._viewCompiler.compileComponent(
outputCtx, compMeta, parsedTemplate, stylesExpr, usedPipes);
@ -344,6 +373,14 @@ export class AotCompiler {
return viewResult;
}
private _compileComponentTypeCheckBlock(
outputCtx: OutputContext, compMeta: CompileDirectiveMetadata,
ngModule: CompileNgModuleMetadata, directiveIdentifiers: CompileIdentifierMetadata[]) {
const {template: parsedTemplate, pipes: usedPipes} =
this._parseTemplate(compMeta, ngModule, directiveIdentifiers);
this._typeCheckCompiler.compileComponent(outputCtx, compMeta, parsedTemplate, usedPipes);
}
private _createOutputContext(genFilePath: string): OutputContext {
const importExpr = (symbol: StaticSymbol, typeParams: o.Type[] | null = null) => {
if (!(symbol instanceof StaticSymbol)) {

View File

@ -24,6 +24,7 @@ import {StyleCompiler} from '../style_compiler';
import {TemplateParser} from '../template_parser/template_parser';
import {UrlResolver} from '../url_resolver';
import {syntaxError} from '../util';
import {TypeCheckCompiler} from '../view_compiler/type_check_compiler';
import {ViewCompiler} from '../view_compiler/view_compiler';
import {AotCompiler} from './compiler';
@ -81,9 +82,11 @@ export function createAotCompiler(compilerHost: AotCompilerHost, options: AotCom
console, symbolCache, staticReflector);
// TODO(vicb): do not pass options.i18nFormat here
const viewCompiler = new ViewCompiler(config, staticReflector, elementSchemaRegistry);
const typeCheckCompiler = new TypeCheckCompiler(options, staticReflector);
const compiler = new AotCompiler(
config, compilerHost, staticReflector, resolver, tmplParser, new StyleCompiler(urlResolver),
viewCompiler, new NgModuleCompiler(staticReflector), new TypeScriptEmitter(), summaryResolver,
config, compilerHost, staticReflector, resolver, htmlParser, tmplParser,
new StyleCompiler(urlResolver), viewCompiler, typeCheckCompiler,
new NgModuleCompiler(staticReflector), new TypeScriptEmitter(), summaryResolver,
options.locale || null, options.i18nFormat || null, options.enableSummariesForJit || null,
symbolResolver);
return {compiler, reflector: staticReflector};

View File

@ -16,4 +16,5 @@ export interface AotCompilerOptions {
enableLegacyTemplate?: boolean;
enableSummariesForJit?: boolean;
preserveWhitespaces?: boolean;
fullTemplateTypeCheck?: boolean;
}