Skip to content

Commit 297c060

Browse files
JoostKAndrewKushnir
authored andcommittedSep 14, 2020
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
1 parent 077f516 commit 297c060

File tree

5 files changed

+117
-26
lines changed

5 files changed

+117
-26
lines changed
 

‎packages/compiler-cli/src/ngtsc/annotations/src/component.ts

+5-25
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import {DefaultImportRecorder, ModuleResolver, Reference, ReferenceEmitter} from
1616
import {DependencyTracker} from '../../incremental/api';
1717
import {IndexingContext} from '../../indexer';
1818
import {ClassPropertyMapping, DirectiveMeta, DirectiveTypeCheckMeta, extractDirectiveTypeCheckMeta, InjectableClassRegistry, MetadataReader, MetadataRegistry} from '../../metadata';
19-
import {flattenInheritedDirectiveMetadata} from '../../metadata/src/inheritance';
2019
import {EnumValue, PartialEvaluator} from '../../partial_evaluator';
2120
import {ClassDeclaration, Decorator, ReflectionHost, reflectObjectLiteral} from '../../reflection';
2221
import {ComponentScopeReader, LocalModuleScopeRegistry} from '../../scope';
@@ -31,6 +30,7 @@ import {createValueHasWrongTypeError, getDirectiveDiagnostics, getProviderDiagno
3130
import {extractDirectiveMetadata, parseFieldArrayValue} from './directive';
3231
import {compileNgFactoryDefField} from './factory';
3332
import {generateSetClassMetadataCall} from './metadata';
33+
import {TypeCheckScopes} from './typecheck_scopes';
3434
import {findAngularDecorator, isAngularCoreReference, isExpressionForwardReference, readBaseClass, resolveProvidersRequiringFactory, unwrapExpression, wrapFunctionExpressionsInParens} from './util';
3535

3636
const EMPTY_MAP = new Map<string, Expression>();
@@ -95,6 +95,7 @@ export class ComponentDecoratorHandler implements
9595

9696
private literalCache = new Map<Decorator, ts.ObjectLiteralExpression>();
9797
private elementSchemaRegistry = new DomElementSchemaRegistry();
98+
private typeCheckScopes = new TypeCheckScopes(this.scopeReader, this.metaReader);
9899

99100
/**
100101
* During the asynchronous preanalyze phase, it's necessary to parse the template to extract
@@ -423,36 +424,15 @@ export class ComponentDecoratorHandler implements
423424
return;
424425
}
425426

426-
const matcher = new SelectorMatcher<DirectiveMeta>();
427-
const pipes = new Map<string, Reference<ClassDeclaration<ts.ClassDeclaration>>>();
428-
let schemas: SchemaMetadata[] = [];
429-
430-
const scope = this.scopeReader.getScopeForComponent(node);
427+
const scope = this.typeCheckScopes.getTypeCheckScope(node);
431428
if (scope === 'error') {
432429
// Don't type-check components that had errors in their scopes.
433430
return;
434431
}
435432

436-
if (scope !== null) {
437-
for (const meta of scope.compilation.directives) {
438-
if (meta.selector !== null) {
439-
const extMeta = flattenInheritedDirectiveMetadata(this.metaReader, meta.ref);
440-
matcher.addSelectables(CssSelector.parse(meta.selector), extMeta);
441-
}
442-
}
443-
for (const {name, ref} of scope.compilation.pipes) {
444-
if (!ts.isClassDeclaration(ref.node)) {
445-
throw new Error(`Unexpected non-class declaration ${
446-
ts.SyntaxKind[ref.node.kind]} for pipe ${ref.debugName}`);
447-
}
448-
pipes.set(name, ref as Reference<ClassDeclaration<ts.ClassDeclaration>>);
449-
}
450-
schemas = scope.schemas;
451-
}
452-
453-
const binder = new R3TargetBinder(matcher);
433+
const binder = new R3TargetBinder(scope.matcher);
454434
ctx.addTemplate(
455-
new Reference(node), binder, meta.template.diagNodes, pipes, schemas,
435+
new Reference(node), binder, meta.template.diagNodes, scope.pipes, scope.schemas,
456436
meta.template.sourceMapping, meta.template.file);
457437
}
458438

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {CssSelector, SchemaMetadata, SelectorMatcher} from '@angular/compiler';
10+
import * as ts from 'typescript';
11+
12+
import {Reference} from '../../imports';
13+
import {DirectiveMeta, flattenInheritedDirectiveMetadata, MetadataReader} from '../../metadata';
14+
import {ClassDeclaration} from '../../reflection';
15+
import {ComponentScopeReader} from '../../scope';
16+
17+
/**
18+
* The scope that is used for type-check code generation of a component template.
19+
*/
20+
export interface TypeCheckScope {
21+
/**
22+
* A `SelectorMatcher` instance that contains the flattened directive metadata of all directives
23+
* that are in the compilation scope of the declaring NgModule.
24+
*/
25+
matcher: SelectorMatcher<DirectiveMeta>;
26+
27+
/**
28+
* The pipes that are available in the compilation scope.
29+
*/
30+
pipes: Map<string, Reference<ClassDeclaration<ts.ClassDeclaration>>>;
31+
32+
/**
33+
* The schemas that are used in this scope.
34+
*/
35+
schemas: SchemaMetadata[];
36+
}
37+
38+
/**
39+
* Computes scope information to be used in template type checking.
40+
*/
41+
export class TypeCheckScopes {
42+
/**
43+
* Cache of flattened directive metadata. Because flattened metadata is scope-invariant it's
44+
* cached individually, such that all scopes refer to the same flattened metadata.
45+
*/
46+
private flattenedDirectiveMetaCache = new Map<ClassDeclaration, DirectiveMeta>();
47+
48+
/**
49+
* Cache of the computed type check scope per NgModule declaration.
50+
*/
51+
private scopeCache = new Map<ClassDeclaration, TypeCheckScope>();
52+
53+
constructor(private scopeReader: ComponentScopeReader, private metaReader: MetadataReader) {}
54+
55+
/**
56+
* Computes the type-check scope information for the component declaration. If the NgModule
57+
* contains an error, then 'error' is returned. If the component is not declared in any NgModule,
58+
* an empty type-check scope is returned.
59+
*/
60+
getTypeCheckScope(node: ClassDeclaration): TypeCheckScope|'error' {
61+
const matcher = new SelectorMatcher<DirectiveMeta>();
62+
const pipes = new Map<string, Reference<ClassDeclaration<ts.ClassDeclaration>>>();
63+
64+
const scope = this.scopeReader.getScopeForComponent(node);
65+
if (scope === null) {
66+
return {matcher, pipes, schemas: []};
67+
} else if (scope === 'error') {
68+
return scope;
69+
}
70+
71+
if (this.scopeCache.has(scope.ngModule)) {
72+
return this.scopeCache.get(scope.ngModule)!;
73+
}
74+
75+
for (const meta of scope.compilation.directives) {
76+
if (meta.selector !== null) {
77+
const extMeta = this.getInheritedDirectiveMetadata(meta.ref);
78+
matcher.addSelectables(CssSelector.parse(meta.selector), extMeta);
79+
}
80+
}
81+
82+
for (const {name, ref} of scope.compilation.pipes) {
83+
if (!ts.isClassDeclaration(ref.node)) {
84+
throw new Error(`Unexpected non-class declaration ${
85+
ts.SyntaxKind[ref.node.kind]} for pipe ${ref.debugName}`);
86+
}
87+
pipes.set(name, ref as Reference<ClassDeclaration<ts.ClassDeclaration>>);
88+
}
89+
90+
const typeCheckScope: TypeCheckScope = {matcher, pipes, schemas: scope.schemas};
91+
this.scopeCache.set(scope.ngModule, typeCheckScope);
92+
return typeCheckScope;
93+
}
94+
95+
private getInheritedDirectiveMetadata(ref: Reference<ClassDeclaration>): DirectiveMeta {
96+
const clazz = ref.node;
97+
if (this.flattenedDirectiveMetaCache.has(clazz)) {
98+
return this.flattenedDirectiveMetaCache.get(clazz)!;
99+
}
100+
101+
const meta = flattenInheritedDirectiveMetadata(this.metaReader, ref);
102+
this.flattenedDirectiveMetaCache.set(clazz, meta);
103+
return meta;
104+
}
105+
}

‎packages/compiler-cli/src/ngtsc/metadata/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
export * from './src/api';
1010
export {DtsMetadataReader} from './src/dts';
11+
export {flattenInheritedDirectiveMetadata} from './src/inheritance';
1112
export {CompoundMetadataRegistry, LocalMetadataRegistry, InjectableClassRegistry} from './src/registry';
1213
export {extractDirectiveTypeCheckMeta, CompoundMetadataReader} from './src/util';
1314
export {BindingPropertyName, ClassPropertyMapping, ClassPropertyName, InputOrOutput} from './src/property_mapping';

‎packages/compiler-cli/src/ngtsc/metadata/src/inheritance.ts

+3
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ export function flattenInheritedDirectiveMetadata(
2626
if (topMeta === null) {
2727
throw new Error(`Metadata not found for directive: ${dir.debugName}`);
2828
}
29+
if (topMeta.baseClass === null) {
30+
return topMeta;
31+
}
2932

3033
const coercedInputFields = new Set<ClassPropertyName>();
3134
const undeclaredInputFields = new Set<ClassPropertyName>();

‎packages/compiler-cli/src/ngtsc/scope/src/local.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export interface LocalNgModuleData {
2626
}
2727

2828
export interface LocalModuleScope extends ExportScope {
29+
ngModule: ClassDeclaration;
2930
compilation: ScopeData;
3031
reexports: Reexport[]|null;
3132
schemas: SchemaMetadata[];
@@ -433,7 +434,8 @@ export class LocalModuleScopeRegistry implements MetadataRegistry, ComponentScop
433434
}
434435

435436
// Finally, produce the `LocalModuleScope` with both the compilation and export scopes.
436-
const scope = {
437+
const scope: LocalModuleScope = {
438+
ngModule: ngModule.ref.node,
437439
compilation: {
438440
directives: Array.from(compilationDirectives.values()),
439441
pipes: Array.from(compilationPipes.values()),

0 commit comments

Comments
 (0)