perf(compiler-cli): optimize computation of type-check scope information (#38539)
When type-checking a component, the declaring NgModule scope is used to create a directive matcher that contains flattened directive metadata, i.e. the metadata of a directive and its base classes. This computation is done for all components, whereas the type-check scope is constant per NgModule. Additionally, the flattening of metadata is constant per directive instance so doesn't necessarily have to be recomputed for each component. This commit introduces a `TypeCheckScopes` class that is responsible for flattening directives and computing the scope per NgModule. It caches the computed results as appropriate to avoid repeated computation. PR Close #38539
This commit is contained in:
parent
4faac78e32
commit
ba95b79a21
|
@ -16,7 +16,6 @@ import {DefaultImportRecorder, ModuleResolver, Reference, ReferenceEmitter} from
|
||||||
import {DependencyTracker} from '../../incremental/api';
|
import {DependencyTracker} from '../../incremental/api';
|
||||||
import {IndexingContext} from '../../indexer';
|
import {IndexingContext} from '../../indexer';
|
||||||
import {ClassPropertyMapping, DirectiveMeta, DirectiveTypeCheckMeta, extractDirectiveTypeCheckMeta, InjectableClassRegistry, MetadataReader, MetadataRegistry} from '../../metadata';
|
import {ClassPropertyMapping, DirectiveMeta, DirectiveTypeCheckMeta, extractDirectiveTypeCheckMeta, InjectableClassRegistry, MetadataReader, MetadataRegistry} from '../../metadata';
|
||||||
import {flattenInheritedDirectiveMetadata} from '../../metadata/src/inheritance';
|
|
||||||
import {EnumValue, PartialEvaluator} from '../../partial_evaluator';
|
import {EnumValue, PartialEvaluator} from '../../partial_evaluator';
|
||||||
import {ClassDeclaration, Decorator, ReflectionHost, reflectObjectLiteral} from '../../reflection';
|
import {ClassDeclaration, Decorator, ReflectionHost, reflectObjectLiteral} from '../../reflection';
|
||||||
import {ComponentScopeReader, LocalModuleScopeRegistry} from '../../scope';
|
import {ComponentScopeReader, LocalModuleScopeRegistry} from '../../scope';
|
||||||
|
@ -31,6 +30,7 @@ import {createValueHasWrongTypeError, getDirectiveDiagnostics, getProviderDiagno
|
||||||
import {extractDirectiveMetadata, parseFieldArrayValue} from './directive';
|
import {extractDirectiveMetadata, parseFieldArrayValue} from './directive';
|
||||||
import {compileNgFactoryDefField} from './factory';
|
import {compileNgFactoryDefField} from './factory';
|
||||||
import {generateSetClassMetadataCall} from './metadata';
|
import {generateSetClassMetadataCall} from './metadata';
|
||||||
|
import {TypeCheckScopes} from './typecheck_scopes';
|
||||||
import {findAngularDecorator, isAngularCoreReference, isExpressionForwardReference, readBaseClass, resolveProvidersRequiringFactory, unwrapExpression, wrapFunctionExpressionsInParens} from './util';
|
import {findAngularDecorator, isAngularCoreReference, isExpressionForwardReference, readBaseClass, resolveProvidersRequiringFactory, unwrapExpression, wrapFunctionExpressionsInParens} from './util';
|
||||||
|
|
||||||
const EMPTY_MAP = new Map<string, Expression>();
|
const EMPTY_MAP = new Map<string, Expression>();
|
||||||
|
@ -95,6 +95,7 @@ export class ComponentDecoratorHandler implements
|
||||||
|
|
||||||
private literalCache = new Map<Decorator, ts.ObjectLiteralExpression>();
|
private literalCache = new Map<Decorator, ts.ObjectLiteralExpression>();
|
||||||
private elementSchemaRegistry = new DomElementSchemaRegistry();
|
private elementSchemaRegistry = new DomElementSchemaRegistry();
|
||||||
|
private typeCheckScopes = new TypeCheckScopes(this.scopeReader, this.metaReader);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* During the asynchronous preanalyze phase, it's necessary to parse the template to extract
|
* During the asynchronous preanalyze phase, it's necessary to parse the template to extract
|
||||||
|
@ -423,36 +424,15 @@ export class ComponentDecoratorHandler implements
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const matcher = new SelectorMatcher<DirectiveMeta>();
|
const scope = this.typeCheckScopes.getTypeCheckScope(node);
|
||||||
const pipes = new Map<string, Reference<ClassDeclaration<ts.ClassDeclaration>>>();
|
|
||||||
let schemas: SchemaMetadata[] = [];
|
|
||||||
|
|
||||||
const scope = this.scopeReader.getScopeForComponent(node);
|
|
||||||
if (scope === 'error') {
|
if (scope === 'error') {
|
||||||
// Don't type-check components that had errors in their scopes.
|
// Don't type-check components that had errors in their scopes.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (scope !== null) {
|
const binder = new R3TargetBinder(scope.matcher);
|
||||||
for (const meta of scope.compilation.directives) {
|
|
||||||
if (meta.selector !== null) {
|
|
||||||
const extMeta = flattenInheritedDirectiveMetadata(this.metaReader, meta.ref);
|
|
||||||
matcher.addSelectables(CssSelector.parse(meta.selector), extMeta);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (const {name, ref} of scope.compilation.pipes) {
|
|
||||||
if (!ts.isClassDeclaration(ref.node)) {
|
|
||||||
throw new Error(`Unexpected non-class declaration ${
|
|
||||||
ts.SyntaxKind[ref.node.kind]} for pipe ${ref.debugName}`);
|
|
||||||
}
|
|
||||||
pipes.set(name, ref as Reference<ClassDeclaration<ts.ClassDeclaration>>);
|
|
||||||
}
|
|
||||||
schemas = scope.schemas;
|
|
||||||
}
|
|
||||||
|
|
||||||
const binder = new R3TargetBinder(matcher);
|
|
||||||
ctx.addTemplate(
|
ctx.addTemplate(
|
||||||
new Reference(node), binder, meta.template.diagNodes, pipes, schemas,
|
new Reference(node), binder, meta.template.diagNodes, scope.pipes, scope.schemas,
|
||||||
meta.template.sourceMapping, meta.template.file);
|
meta.template.sourceMapping, meta.template.file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,105 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google LLC All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {CssSelector, SchemaMetadata, SelectorMatcher} from '@angular/compiler';
|
||||||
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
|
import {Reference} from '../../imports';
|
||||||
|
import {DirectiveMeta, flattenInheritedDirectiveMetadata, MetadataReader} from '../../metadata';
|
||||||
|
import {ClassDeclaration} from '../../reflection';
|
||||||
|
import {ComponentScopeReader} from '../../scope';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The scope that is used for type-check code generation of a component template.
|
||||||
|
*/
|
||||||
|
export interface TypeCheckScope {
|
||||||
|
/**
|
||||||
|
* A `SelectorMatcher` instance that contains the flattened directive metadata of all directives
|
||||||
|
* that are in the compilation scope of the declaring NgModule.
|
||||||
|
*/
|
||||||
|
matcher: SelectorMatcher<DirectiveMeta>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The pipes that are available in the compilation scope.
|
||||||
|
*/
|
||||||
|
pipes: Map<string, Reference<ClassDeclaration<ts.ClassDeclaration>>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The schemas that are used in this scope.
|
||||||
|
*/
|
||||||
|
schemas: SchemaMetadata[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes scope information to be used in template type checking.
|
||||||
|
*/
|
||||||
|
export class TypeCheckScopes {
|
||||||
|
/**
|
||||||
|
* Cache of flattened directive metadata. Because flattened metadata is scope-invariant it's
|
||||||
|
* cached individually, such that all scopes refer to the same flattened metadata.
|
||||||
|
*/
|
||||||
|
private flattenedDirectiveMetaCache = new Map<ClassDeclaration, DirectiveMeta>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cache of the computed type check scope per NgModule declaration.
|
||||||
|
*/
|
||||||
|
private scopeCache = new Map<ClassDeclaration, TypeCheckScope>();
|
||||||
|
|
||||||
|
constructor(private scopeReader: ComponentScopeReader, private metaReader: MetadataReader) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes the type-check scope information for the component declaration. If the NgModule
|
||||||
|
* contains an error, then 'error' is returned. If the component is not declared in any NgModule,
|
||||||
|
* an empty type-check scope is returned.
|
||||||
|
*/
|
||||||
|
getTypeCheckScope(node: ClassDeclaration): TypeCheckScope|'error' {
|
||||||
|
const matcher = new SelectorMatcher<DirectiveMeta>();
|
||||||
|
const pipes = new Map<string, Reference<ClassDeclaration<ts.ClassDeclaration>>>();
|
||||||
|
|
||||||
|
const scope = this.scopeReader.getScopeForComponent(node);
|
||||||
|
if (scope === null) {
|
||||||
|
return {matcher, pipes, schemas: []};
|
||||||
|
} else if (scope === 'error') {
|
||||||
|
return scope;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.scopeCache.has(scope.ngModule)) {
|
||||||
|
return this.scopeCache.get(scope.ngModule)!;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const meta of scope.compilation.directives) {
|
||||||
|
if (meta.selector !== null) {
|
||||||
|
const extMeta = this.getInheritedDirectiveMetadata(meta.ref);
|
||||||
|
matcher.addSelectables(CssSelector.parse(meta.selector), extMeta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const {name, ref} of scope.compilation.pipes) {
|
||||||
|
if (!ts.isClassDeclaration(ref.node)) {
|
||||||
|
throw new Error(`Unexpected non-class declaration ${
|
||||||
|
ts.SyntaxKind[ref.node.kind]} for pipe ${ref.debugName}`);
|
||||||
|
}
|
||||||
|
pipes.set(name, ref as Reference<ClassDeclaration<ts.ClassDeclaration>>);
|
||||||
|
}
|
||||||
|
|
||||||
|
const typeCheckScope: TypeCheckScope = {matcher, pipes, schemas: scope.schemas};
|
||||||
|
this.scopeCache.set(scope.ngModule, typeCheckScope);
|
||||||
|
return typeCheckScope;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getInheritedDirectiveMetadata(ref: Reference<ClassDeclaration>): DirectiveMeta {
|
||||||
|
const clazz = ref.node;
|
||||||
|
if (this.flattenedDirectiveMetaCache.has(clazz)) {
|
||||||
|
return this.flattenedDirectiveMetaCache.get(clazz)!;
|
||||||
|
}
|
||||||
|
|
||||||
|
const meta = flattenInheritedDirectiveMetadata(this.metaReader, ref);
|
||||||
|
this.flattenedDirectiveMetaCache.set(clazz, meta);
|
||||||
|
return meta;
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
export * from './src/api';
|
export * from './src/api';
|
||||||
export {DtsMetadataReader} from './src/dts';
|
export {DtsMetadataReader} from './src/dts';
|
||||||
|
export {flattenInheritedDirectiveMetadata} from './src/inheritance';
|
||||||
export {CompoundMetadataRegistry, LocalMetadataRegistry, InjectableClassRegistry} from './src/registry';
|
export {CompoundMetadataRegistry, LocalMetadataRegistry, InjectableClassRegistry} from './src/registry';
|
||||||
export {extractDirectiveTypeCheckMeta, CompoundMetadataReader} from './src/util';
|
export {extractDirectiveTypeCheckMeta, CompoundMetadataReader} from './src/util';
|
||||||
export {BindingPropertyName, ClassPropertyMapping, ClassPropertyName, InputOrOutput} from './src/property_mapping';
|
export {BindingPropertyName, ClassPropertyMapping, ClassPropertyName, InputOrOutput} from './src/property_mapping';
|
||||||
|
|
|
@ -26,6 +26,9 @@ export function flattenInheritedDirectiveMetadata(
|
||||||
if (topMeta === null) {
|
if (topMeta === null) {
|
||||||
throw new Error(`Metadata not found for directive: ${dir.debugName}`);
|
throw new Error(`Metadata not found for directive: ${dir.debugName}`);
|
||||||
}
|
}
|
||||||
|
if (topMeta.baseClass === null) {
|
||||||
|
return topMeta;
|
||||||
|
}
|
||||||
|
|
||||||
const coercedInputFields = new Set<ClassPropertyName>();
|
const coercedInputFields = new Set<ClassPropertyName>();
|
||||||
const undeclaredInputFields = new Set<ClassPropertyName>();
|
const undeclaredInputFields = new Set<ClassPropertyName>();
|
||||||
|
|
|
@ -26,6 +26,7 @@ export interface LocalNgModuleData {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LocalModuleScope extends ExportScope {
|
export interface LocalModuleScope extends ExportScope {
|
||||||
|
ngModule: ClassDeclaration;
|
||||||
compilation: ScopeData;
|
compilation: ScopeData;
|
||||||
reexports: Reexport[]|null;
|
reexports: Reexport[]|null;
|
||||||
schemas: SchemaMetadata[];
|
schemas: SchemaMetadata[];
|
||||||
|
@ -433,7 +434,8 @@ export class LocalModuleScopeRegistry implements MetadataRegistry, ComponentScop
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finally, produce the `LocalModuleScope` with both the compilation and export scopes.
|
// Finally, produce the `LocalModuleScope` with both the compilation and export scopes.
|
||||||
const scope = {
|
const scope: LocalModuleScope = {
|
||||||
|
ngModule: ngModule.ref.node,
|
||||||
compilation: {
|
compilation: {
|
||||||
directives: Array.from(compilationDirectives.values()),
|
directives: Array.from(compilationDirectives.values()),
|
||||||
pipes: Array.from(compilationPipes.values()),
|
pipes: Array.from(compilationPipes.values()),
|
||||||
|
|
Loading…
Reference in New Issue