Skip to content

Commit 1527e8f

Browse files
committed
refactor(core): change component emit to 'dependencies' (angular#45672)
Previously, the compiler would represent template dependencies of a component in its component definition through separate fields (`directives`, `pipes`). This commit refactors the compiler/runtime interface to use a single field (`dependencies`). The runtime component definition object still has separate `directiveDefs` and `pipeDefs`, which are calculated from the `dependencies` when the definition is evaluated. This change is also reflected in partially compiled declarations. To ensure compatibility with partially compiled code already on NPM, the linker will still honor the old form of declaration (with separate fields). PR Close angular#45672
1 parent 046dad1 commit 1527e8f

File tree

91 files changed

+659
-444
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

91 files changed

+659
-444
lines changed

goldens/size-tracking/aio-payloads.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@
1212
"aio-local": {
1313
"uncompressed": {
1414
"runtime": 4343,
15-
"main": 452837,
15+
"main": 453341,
1616
"polyfills": 33980,
1717
"styles": 71714,
1818
"light-theme": 78213,
1919
"dark-theme": 78318
2020
}
2121
}
22-
}
22+
}

integration/ngcc/run_test.sh

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -214,8 +214,8 @@ ngc -p tsconfig-app.json
214214
assertSucceeded "Expected the app to successfully compile with the ngcc-processed libraries."
215215

216216
# Did it compile the main.ts correctly (including the ngIf and MatButton directives)?
217-
grep "directives: \[.*\.NgIf.*\]" dist/src/main.js
218-
assertSucceeded "Expected the compiled app's 'main.ts' to list 'NgIf' in 'directives'."
217+
grep "dependencies: \[.*\.NgIf.*\]" dist/src/main.js
218+
assertSucceeded "Expected the compiled app's 'main.ts' to list 'NgIf' in 'dependencies'."
219219

220-
grep "directives: \[.*\.MatButton.*\]" dist/src/main.js
221-
assertSucceeded "Expected the compiled app's 'main.ts' to list 'MatButton' in 'directives'."
220+
grep "dependencies: \[.*\.MatButton.*\]" dist/src/main.js
221+
assertSucceeded "Expected the compiled app's 'main.ts' to list 'MatButton' in 'dependencies'."

packages/compiler-cli/linker/src/file_linker/partial_linkers/partial_component_linker_1.ts

Lines changed: 84 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* Use of this source code is governed by an MIT-style license that can be
66
* found in the LICENSE file at https://angular.io/license
77
*/
8-
import {ChangeDetectionStrategy, compileComponentFromMetadata, ConstantPool, DeclarationListEmitMode, DEFAULT_INTERPOLATION_CONFIG, ForwardRefHandling, InterpolationConfig, makeBindingParser, outputAst as o, parseTemplate, R3ComponentMetadata, R3DeclareComponentMetadata, R3DeclareUsedDirectiveMetadata, R3PartialDeclaration, R3UsedDirectiveMetadata, ViewEncapsulation} from '@angular/compiler';
8+
import {ChangeDetectionStrategy, compileComponentFromMetadata, ConstantPool, DeclarationListEmitMode, DEFAULT_INTERPOLATION_CONFIG, ForwardRefHandling, InterpolationConfig, makeBindingParser, outputAst as o, parseTemplate, R3ComponentMetadata, R3DeclareComponentMetadata, R3DeclareDirectiveDependencyMetadata, R3DeclarePipeDependencyMetadata, R3DirectiveDependencyMetadata, R3PartialDeclaration, R3TemplateDependencyKind, R3TemplateDependencyMetadata, ViewEncapsulation} from '@angular/compiler';
99

1010
import {AbsoluteFsPath} from '../../../../src/ngtsc/file_system';
1111
import {Range} from '../../ast/ast_host';
@@ -17,6 +17,28 @@ import {toR3DirectiveMeta} from './partial_directive_linker_1';
1717
import {LinkedDefinition, PartialLinker} from './partial_linker';
1818
import {extractForwardRef} from './util';
1919

20+
function makeDirectiveMetadata<TExpression>(
21+
directiveExpr: AstObject<R3DeclareDirectiveDependencyMetadata, TExpression>,
22+
typeExpr: o.WrappedNodeExpr<TExpression>,
23+
isComponentByDefault: true|null = null): R3DirectiveDependencyMetadata {
24+
return {
25+
kind: R3TemplateDependencyKind.Directive,
26+
isComponent: isComponentByDefault ||
27+
(directiveExpr.has('kind') && directiveExpr.getString('kind') === 'component'),
28+
type: typeExpr,
29+
selector: directiveExpr.getString('selector'),
30+
inputs: directiveExpr.has('inputs') ?
31+
directiveExpr.getArray('inputs').map(input => input.getString()) :
32+
[],
33+
outputs: directiveExpr.has('outputs') ?
34+
directiveExpr.getArray('outputs').map(input => input.getString()) :
35+
[],
36+
exportAs: directiveExpr.has('exportAs') ?
37+
directiveExpr.getArray('exportAs').map(exportAs => exportAs.getString()) :
38+
null,
39+
};
40+
}
41+
2042
/**
2143
* A `PartialLinker` that is designed to process `ɵɵngDeclareComponent()` call expressions.
2244
*/
@@ -37,7 +59,7 @@ export class PartialComponentLinkerVersion1<TStatement, TExpression> implements
3759
* This function derives the `R3ComponentMetadata` from the provided AST object.
3860
*/
3961
private toR3ComponentMeta(metaObj: AstObject<R3DeclareComponentMetadata, TExpression>):
40-
R3ComponentMetadata {
62+
R3ComponentMetadata<R3TemplateDependencyMetadata> {
4163
const interpolation = parseInterpolationConfig(metaObj);
4264
const templateSource = metaObj.getValue('template');
4365
const isInline = metaObj.has('isInline') ? metaObj.getBoolean('isInline') : false;
@@ -61,51 +83,74 @@ export class PartialComponentLinkerVersion1<TStatement, TExpression> implements
6183

6284
let declarationListEmitMode = DeclarationListEmitMode.Direct;
6385

64-
const collectUsedDirectives =
65-
(directives: AstValue<R3DeclareUsedDirectiveMetadata, TExpression>[]) => {
66-
return directives.map(directive => {
67-
const directiveExpr = directive.getObject();
68-
const type = directiveExpr.getValue('type');
69-
const selector = directiveExpr.getString('selector');
86+
const extractDeclarationTypeExpr =
87+
(type: AstValue<o.Expression|(() => o.Expression), TExpression>) => {
88+
const {expression, forwardRef} = extractForwardRef(type);
89+
if (forwardRef === ForwardRefHandling.Unwrapped) {
90+
declarationListEmitMode = DeclarationListEmitMode.Closure;
91+
}
92+
return expression;
93+
};
7094

71-
const {expression: typeExpr, forwardRef} = extractForwardRef(type);
72-
if (forwardRef === ForwardRefHandling.Unwrapped) {
73-
declarationListEmitMode = DeclarationListEmitMode.Closure;
74-
}
95+
let declarations: R3TemplateDependencyMetadata[] = [];
7596

76-
return {
77-
type: typeExpr,
78-
selector: selector,
79-
inputs: directiveExpr.has('inputs') ?
80-
directiveExpr.getArray('inputs').map(input => input.getString()) :
81-
[],
82-
outputs: directiveExpr.has('outputs') ?
83-
directiveExpr.getArray('outputs').map(input => input.getString()) :
84-
[],
85-
exportAs: directiveExpr.has('exportAs') ?
86-
directiveExpr.getArray('exportAs').map(exportAs => exportAs.getString()) :
87-
null,
88-
};
89-
});
90-
};
97+
// There are two ways that declarations (directives/pipes) can be represented in declare
98+
// metadata. The "old style" uses separate fields for each (arrays for components/directives and
99+
// an object literal for pipes). The "new style" uses a unified `dependencies` array. For
100+
// backwards compatibility, both are processed and unified here:
91101

92-
let directives: R3UsedDirectiveMetadata[] = [];
102+
// Process the old style fields:
93103
if (metaObj.has('components')) {
94-
directives.push(...collectUsedDirectives(metaObj.getArray('components')));
104+
declarations.push(...metaObj.getArray('components').map(dir => {
105+
const dirExpr = dir.getObject();
106+
const typeExpr = extractDeclarationTypeExpr(dirExpr.getValue('type'));
107+
return makeDirectiveMetadata(dirExpr, typeExpr, /* isComponentByDefault */ true);
108+
}));
95109
}
96110
if (metaObj.has('directives')) {
97-
directives.push(...collectUsedDirectives(metaObj.getArray('directives')));
111+
declarations.push(...metaObj.getArray('directives').map(dir => {
112+
const dirExpr = dir.getObject();
113+
const typeExpr = extractDeclarationTypeExpr(dirExpr.getValue('type'));
114+
return makeDirectiveMetadata(dirExpr, typeExpr);
115+
}));
98116
}
99-
100-
let pipes = new Map<string, o.Expression>();
101117
if (metaObj.has('pipes')) {
102-
pipes = metaObj.getObject('pipes').toMap(pipe => {
103-
const {expression: pipeType, forwardRef} = extractForwardRef(pipe);
104-
if (forwardRef === ForwardRefHandling.Unwrapped) {
105-
declarationListEmitMode = DeclarationListEmitMode.Closure;
118+
const pipes = metaObj.getObject('pipes').toMap(pipe => pipe);
119+
for (const [name, type] of pipes) {
120+
const typeExpr = extractDeclarationTypeExpr(type);
121+
declarations.push({
122+
kind: R3TemplateDependencyKind.Pipe,
123+
name,
124+
type: typeExpr,
125+
});
126+
}
127+
}
128+
129+
// Process the new style field:
130+
if (metaObj.has('dependencies')) {
131+
for (const dep of metaObj.getArray('dependencies')) {
132+
const depObj = dep.getObject();
133+
const typeExpr = extractDeclarationTypeExpr(depObj.getValue('type'));
134+
135+
switch (depObj.getString('kind')) {
136+
case 'directive':
137+
case 'component':
138+
declarations.push(makeDirectiveMetadata(depObj, typeExpr));
139+
break;
140+
case 'pipe':
141+
const pipeObj =
142+
depObj as AstObject<R3DeclarePipeDependencyMetadata&{kind: 'pipe'}, TExpression>;
143+
declarations.push({
144+
kind: R3TemplateDependencyKind.Pipe,
145+
name: pipeObj.getString('name'),
146+
type: typeExpr,
147+
});
148+
break;
149+
default:
150+
// Skip unknown types of dependencies.
151+
continue;
106152
}
107-
return pipeType;
108-
});
153+
}
109154
}
110155

111156
return {
@@ -128,8 +173,7 @@ export class PartialComponentLinkerVersion1<TStatement, TExpression> implements
128173
animations: metaObj.has('animations') ? metaObj.getOpaque('animations') : null,
129174
relativeContextFilePath: this.sourceUrl,
130175
i18nUseExternalIds: false,
131-
pipes,
132-
directives,
176+
declarations,
133177
};
134178
}
135179

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

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {AnimationTriggerNames, compileClassMetadata, compileComponentFromMetadata, compileDeclareClassMetadata, compileDeclareComponentFromMetadata, ConstantPool, CssSelector, DeclarationListEmitMode, DeclareComponentTemplateInfo, DEFAULT_INTERPOLATION_CONFIG, DomElementSchemaRegistry, Expression, FactoryTarget, makeBindingParser, R3ComponentMetadata, R3TargetBinder, R3UsedDirectiveMetadata, SelectorMatcher, ViewEncapsulation, WrappedNodeExpr} from '@angular/compiler';
9+
import {AnimationTriggerNames, compileClassMetadata, compileComponentFromMetadata, compileDeclareClassMetadata, compileDeclareComponentFromMetadata, ConstantPool, CssSelector, DeclarationListEmitMode, DeclareComponentTemplateInfo, DEFAULT_INTERPOLATION_CONFIG, DomElementSchemaRegistry, Expression, FactoryTarget, makeBindingParser, R3ComponentMetadata, R3DirectiveDependencyMetadata, R3PipeDependencyMetadata, R3TargetBinder, R3TemplateDependency, R3TemplateDependencyKind, R3TemplateDependencyMetadata, SelectorMatcher, ViewEncapsulation, WrappedNodeExpr} from '@angular/compiler';
1010
import ts from 'typescript';
1111

1212
import {Cycle, CycleAnalyzer, CycleHandlingStrategy} from '../../../cycles';
@@ -541,11 +541,11 @@ export class ComponentDecoratorHandler implements
541541
}
542542

543543
const context = getSourceFile(node);
544-
const metadata = analysis.meta as Readonly<R3ComponentMetadata>;
544+
const metadata = analysis.meta as Readonly<R3ComponentMetadata<R3TemplateDependencyMetadata>>;
545+
545546

546547
const data: ComponentResolutionData = {
547-
directives: EMPTY_ARRAY,
548-
pipes: EMPTY_MAP,
548+
declarations: EMPTY_ARRAY,
549549
declarationListEmitMode: DeclarationListEmitMode.Direct,
550550
};
551551
const diagnostics: ts.Diagnostic[] = [];
@@ -600,13 +600,14 @@ export class ComponentDecoratorHandler implements
600600
const bound = binder.bind({template: metadata.template.nodes});
601601

602602
// The BoundTarget knows which directives and pipes matched the template.
603-
type UsedDirective =
604-
R3UsedDirectiveMetadata&{ref: Reference<ClassDeclaration>, importedFile: ImportedFile};
603+
type UsedDirective = R3DirectiveDependencyMetadata&
604+
{ref: Reference<ClassDeclaration>, importedFile: ImportedFile};
605605
const usedDirectives: UsedDirective[] = bound.getUsedDirectives().map(directive => {
606606
const type = this.refEmitter.emit(directive.ref, context);
607607
assertSuccessfulReferenceEmit(
608608
type, node.name, directive.isComponent ? 'component' : 'directive');
609609
return {
610+
kind: R3TemplateDependencyKind.Directive,
610611
ref: directive.ref,
611612
type: type.expression,
612613
importedFile: type.importedFile,
@@ -618,10 +619,8 @@ export class ComponentDecoratorHandler implements
618619
};
619620
});
620621

621-
type UsedPipe = {
622+
type UsedPipe = R3PipeDependencyMetadata&{
622623
ref: Reference<ClassDeclaration>,
623-
pipeName: string,
624-
expression: Expression,
625624
importedFile: ImportedFile,
626625
};
627626
const usedPipes: UsedPipe[] = [];
@@ -633,18 +632,18 @@ export class ComponentDecoratorHandler implements
633632
const type = this.refEmitter.emit(pipe, context);
634633
assertSuccessfulReferenceEmit(type, node.name, 'pipe');
635634
usedPipes.push({
635+
kind: R3TemplateDependencyKind.Pipe,
636+
type: type.expression,
637+
name: pipeName,
636638
ref: pipe,
637-
pipeName,
638-
expression: type.expression,
639639
importedFile: type.importedFile,
640640
});
641641
}
642642
if (this.semanticDepGraphUpdater !== null) {
643643
symbol.usedDirectives = usedDirectives.map(
644644
dir => this.semanticDepGraphUpdater!.getSemanticReference(dir.ref.node, dir.type));
645645
symbol.usedPipes = usedPipes.map(
646-
pipe =>
647-
this.semanticDepGraphUpdater!.getSemanticReference(pipe.ref.node, pipe.expression));
646+
pipe => this.semanticDepGraphUpdater!.getSemanticReference(pipe.ref.node, pipe.type));
648647
}
649648

650649
// Scan through the directives/pipes actually used in the template and check whether any
@@ -659,8 +658,7 @@ export class ComponentDecoratorHandler implements
659658
}
660659
const cyclesFromPipes = new Map<UsedPipe, Cycle>();
661660
for (const usedPipe of usedPipes) {
662-
const cycle =
663-
this._checkForCyclicImport(usedPipe.importedFile, usedPipe.expression, context);
661+
const cycle = this._checkForCyclicImport(usedPipe.importedFile, usedPipe.type, context);
664662
if (cycle !== null) {
665663
cyclesFromPipes.set(usedPipe, cycle);
666664
}
@@ -673,8 +671,8 @@ export class ComponentDecoratorHandler implements
673671
for (const {type, importedFile} of usedDirectives) {
674672
this._recordSyntheticImport(importedFile, type, context);
675673
}
676-
for (const {expression, importedFile} of usedPipes) {
677-
this._recordSyntheticImport(importedFile, expression, context);
674+
for (const {type, importedFile} of usedPipes) {
675+
this._recordSyntheticImport(importedFile, type, context);
678676
}
679677

680678
// Check whether the directive/pipe arrays in ɵcmp need to be wrapped in closures.
@@ -683,11 +681,12 @@ export class ComponentDecoratorHandler implements
683681
const wrapDirectivesAndPipesInClosure =
684682
usedDirectives.some(
685683
dir => isExpressionForwardReference(dir.type, node.name, context)) ||
686-
usedPipes.some(
687-
pipe => isExpressionForwardReference(pipe.expression, node.name, context));
684+
usedPipes.some(pipe => isExpressionForwardReference(pipe.type, node.name, context));
688685

689-
data.directives = usedDirectives;
690-
data.pipes = new Map(usedPipes.map(pipe => [pipe.pipeName, pipe.expression]));
686+
data.declarations = [
687+
...usedDirectives,
688+
...usedPipes,
689+
];
691690
data.declarationListEmitMode = wrapDirectivesAndPipesInClosure ?
692691
DeclarationListEmitMode.Closure :
693692
DeclarationListEmitMode.Direct;
@@ -813,7 +812,7 @@ export class ComponentDecoratorHandler implements
813812
if (analysis.template.errors !== null && analysis.template.errors.length > 0) {
814813
return [];
815814
}
816-
const meta: R3ComponentMetadata = {...analysis.meta, ...resolution};
815+
const meta: R3ComponentMetadata<R3TemplateDependency> = {...analysis.meta, ...resolution};
817816
const fac = compileNgFactoryDefField(toFactoryMetadata(meta, FactoryTarget.Component));
818817
const def = compileComponentFromMetadata(meta, pool, makeBindingParser());
819818
const classMetadata = analysis.classMetadata !== null ?
@@ -836,7 +835,8 @@ export class ComponentDecoratorHandler implements
836835
new WrappedNodeExpr(analysis.template.sourceMapping.node) :
837836
null,
838837
};
839-
const meta: R3ComponentMetadata = {...analysis.meta, ...resolution};
838+
const meta:
839+
R3ComponentMetadata<R3TemplateDependencyMetadata> = {...analysis.meta, ...resolution};
840840
const fac = compileDeclareFactory(toFactoryMetadata(meta, FactoryTarget.Component));
841841
const def = compileDeclareComponentFromMetadata(meta, analysis.template, templateInfo);
842842
const classMetadata = analysis.classMetadata !== null ?

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

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {AnimationTriggerNames, R3ClassMetadata, R3ComponentMetadata} from '@angular/compiler';
9+
import {AnimationTriggerNames, R3ClassMetadata, R3ComponentMetadata, R3TemplateDependency, R3TemplateDependencyMetadata} from '@angular/compiler';
1010
import ts from 'typescript';
1111

1212
import {Reference} from '../../../imports';
@@ -22,15 +22,15 @@ import {ParsedTemplateWithSource, StyleUrlMeta} from './resources';
2222
* The `keyof R3ComponentMetadata &` condition ensures that only fields of `R3ComponentMetadata` can
2323
* be included here.
2424
*/
25-
export type ComponentMetadataResolvedFields =
26-
SubsetOfKeys<R3ComponentMetadata, 'directives'|'pipes'|'declarationListEmitMode'>;
25+
export type ComponentMetadataResolvedFields = SubsetOfKeys<
26+
R3ComponentMetadata<R3TemplateDependencyMetadata>, 'declarations'|'declarationListEmitMode'>;
2727

2828
export interface ComponentAnalysisData {
2929
/**
3030
* `meta` includes those fields of `R3ComponentMetadata` which are calculated at `analyze` time
3131
* (not during resolve).
3232
*/
33-
meta: Omit<R3ComponentMetadata, ComponentMetadataResolvedFields>;
33+
meta: Omit<R3ComponentMetadata<R3TemplateDependencyMetadata>, ComponentMetadataResolvedFields>;
3434
baseClass: Reference<ClassDeclaration>|'dynamic'|null;
3535
typeCheckMeta: DirectiveTypeCheckMeta;
3636
template: ParsedTemplateWithSource;
@@ -72,4 +72,5 @@ export interface ComponentAnalysisData {
7272
}|null;
7373
}
7474

75-
export type ComponentResolutionData = Pick<R3ComponentMetadata, ComponentMetadataResolvedFields>;
75+
export type ComponentResolutionData =
76+
Pick<R3ComponentMetadata<R3TemplateDependencyMetadata>, ComponentMetadataResolvedFields>;

0 commit comments

Comments
 (0)