Skip to content

Commit 6006593

Browse files
mheveryalxhub
authored andcommitted
refactor(ivy): align compiler with runtime (angular#22921)
Remove `containerRefreshStart` and `containerRefreshEnd` instruction from the output. Generate directives as a list in `componentDef` rather than inline into instructions. This is consistent in making selector resolution runtime so that translation of templates can follow locality. PR Close angular#22921
1 parent 5266ffe commit 6006593

32 files changed

+402
-348
lines changed

modules/benchmarks/src/tree/render3/tree.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ export class TreeComponent {
8181
},
8282
factory: () => new TreeComponent,
8383
inputs: {data: 'data'},
84-
directiveDefs: () => [TreeComponent.ngComponentDef]
84+
directives: () => [TreeComponent]
8585
});
8686
}
8787

packages/compiler/src/render3/r3_identifiers.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,6 @@ export class Identifiers {
3333

3434
static containerEnd: o.ExternalReference = {name: 'ɵc', moduleName: CORE};
3535

36-
static containerRefreshStart: o.ExternalReference = {name: 'ɵcR', moduleName: CORE};
37-
38-
static containerRefreshEnd: o.ExternalReference = {name: 'ɵcr', moduleName: CORE};
39-
4036
static directiveCreate: o.ExternalReference = {name: 'ɵD', moduleName: CORE};
4137

4238
static text: o.ExternalReference = {name: 'ɵT', moduleName: CORE};

packages/compiler/src/render3/r3_view_compiler.ts

Lines changed: 60 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,23 @@ export function compileComponent(
120120
addDependencyToComponent(outputCtx, summary, pipeSet, pipeExps);
121121
}
122122

123+
const directiveExps: o.Expression[] = [];
124+
const directiveMap = new Set<string>();
125+
/**
126+
* This function gets called every time a directive dependency needs to be added to the template.
127+
* Its job is to remove duplicates from the list. (Only have single dependency no matter how many
128+
* times the dependency is used.)
129+
*/
130+
function addDirectiveDependency(ast: DirectiveAst) {
131+
const importExpr = outputCtx.importExpr(ast.directive.type.reference) as o.ExternalExpr;
132+
const uniqueKey = importExpr.value.moduleName + ':' + importExpr.value.name;
133+
134+
if (!directiveMap.has(uniqueKey)) {
135+
directiveMap.add(uniqueKey);
136+
directiveExps.push(importExpr);
137+
}
138+
}
139+
123140
const field = (key: string, value: o.Expression | null) => {
124141
if (value) {
125142
definitionMapValues.push({key, value, quoted: false});
@@ -162,10 +179,13 @@ export function compileComponent(
162179
new TemplateDefinitionBuilder(
163180
outputCtx, outputCtx.constantPool, reflector, CONTEXT_NAME, ROOT_SCOPE.nestedScope(), 0,
164181
component.template !.ngContentSelectors, templateTypeName, templateName, pipeMap,
165-
component.viewQueries, addPipeDependency)
182+
component.viewQueries, addDirectiveDependency, addPipeDependency)
166183
.buildTemplateFunction(template, []);
167184

168185
field('template', templateFunctionExpression);
186+
if (directiveExps.length) {
187+
field('directives', o.literalArr(directiveExps));
188+
}
169189

170190
// e.g. `pipes: [MyPipe]`
171191
if (pipeExps.length) {
@@ -373,6 +393,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
373393
private bindingScope: BindingScope, private level = 0, private ngContentSelectors: string[],
374394
private contextName: string|null, private templateName: string|null,
375395
private pipes: Map<string, CompilePipeSummary>, private viewQueries: CompileQueryMetadata[],
396+
private addDirectiveDependency: (ast: DirectiveAst) => void,
376397
private addPipeDependency: (summary: CompilePipeSummary) => void) {
377398
this._valueConverter = new ValueConverter(
378399
outputCtx, () => this.allocateDataSlot(), (name, localName, slot, value) => {
@@ -504,23 +525,9 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
504525
this.instruction(this._creationMode, ast.sourceSpan, R3.projection, ...parameters);
505526
}
506527

507-
private _computeDirectivesArray(directives: DirectiveAst[]) {
508-
const directiveExpressions: o.Expression[] =
509-
directives.filter(directive => !directive.directive.isComponent).map(directive => {
510-
this.allocateDataSlot(); // Allocate space for the directive
511-
return this.typeReference(directive.directive.type.reference);
512-
});
513-
return directiveExpressions.length ?
514-
this.constantPool.getConstLiteral(
515-
o.literalArr(directiveExpressions), /* forceShared */ true) :
516-
o.literal(null, o.INFERRED_TYPE);
517-
;
518-
}
519-
520528
// TemplateAstVisitor
521529
visitElement(element: ElementAst) {
522530
const elementIndex = this.allocateDataSlot();
523-
let componentIndex: number|undefined = undefined;
524531
const referenceDataSlots = new Map<string, number>();
525532
const wasInI18nSection = this._inI18nSection;
526533

@@ -563,13 +570,11 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
563570
const nullNode = o.literal(null, o.INFERRED_TYPE);
564571
const parameters: o.Expression[] = [o.literal(elementIndex)];
565572

566-
// Add component type or element tag
567573
if (component) {
568-
parameters.push(this.typeReference(component.directive.type.reference));
569-
componentIndex = this.allocateDataSlot();
570-
} else {
571-
parameters.push(o.literal(element.name));
574+
this.addDirectiveDependency(component);
572575
}
576+
element.directives.forEach(this.addDirectiveDependency);
577+
parameters.push(o.literal(element.name));
573578

574579
// Add the attributes
575580
const i18nMessages: o.Statement[] = [];
@@ -598,10 +603,6 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
598603

599604
parameters.push(attrArg);
600605

601-
// Add directives array
602-
const directivesArray = this._computeDirectivesArray(element.directives);
603-
parameters.push(directivesArray);
604-
605606
if (element.references && element.references.length > 0) {
606607
const references =
607608
flatten(element.references.map(reference => {
@@ -621,16 +622,12 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
621622
parameters.push(nullNode);
622623
}
623624

624-
// Remove trailing null nodes as they are implied.
625-
while (o.isNull(parameters[parameters.length - 1])) {
626-
parameters.pop();
627-
}
628-
629625
// Generate the instruction create element instruction
630626
if (i18nMessages.length > 0) {
631627
this._creationMode.push(...i18nMessages);
632628
}
633-
this.instruction(this._creationMode, element.sourceSpan, R3.createElement, ...parameters);
629+
this.instruction(
630+
this._creationMode, element.sourceSpan, R3.createElement, ...trimTrailingNulls(parameters));
634631

635632
const implicit = o.variable(this.contextParameter);
636633

@@ -725,28 +722,41 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
725722
contextName ? `${contextName}_Template_${templateIndex}` : `Template_${templateIndex}`;
726723
const templateContext = `ctx${this.level}`;
727724

728-
const directivesArray = this._computeDirectivesArray(ast.directives);
725+
const parameters: o.Expression[] = [o.variable(templateName), o.literal(null, o.INFERRED_TYPE)];
726+
const attributeNames: o.Expression[] = [];
727+
ast.directives.forEach((directiveAst: DirectiveAst) => {
728+
this.addDirectiveDependency(directiveAst);
729+
CssSelector.parse(directiveAst.directive.selector !).forEach(selector => {
730+
selector.attrs.forEach((value) => {
731+
// Convert '' (falsy) strings into `null`. This is needed because we want
732+
// to communicate to runtime that these attributes are present for
733+
// selector matching, but should not actually be added to the DOM.
734+
// attributeNames.push(o.literal(value ? value : null));
735+
736+
// TODO(misko): make the above comment true, for now just write to DOM because
737+
// the runtime selectors have not been updated.
738+
attributeNames.push(o.literal(value));
739+
});
740+
});
741+
});
742+
if (attributeNames.length) {
743+
parameters.push(
744+
this.constantPool.getConstLiteral(o.literalArr(attributeNames), /* forcedShared */ true));
745+
}
729746

730747
// e.g. C(1, C1Template)
731748
this.instruction(
732749
this._creationMode, ast.sourceSpan, R3.containerCreate, o.literal(templateIndex),
733-
directivesArray, o.variable(templateName));
734-
735-
// e.g. Cr(1)
736-
this.instruction(
737-
this._refreshMode, ast.sourceSpan, R3.containerRefreshStart, o.literal(templateIndex));
750+
...trimTrailingNulls(parameters));
738751

739752
// Generate directives
740753
this._visitDirectives(ast.directives, o.variable(this.contextParameter), templateIndex);
741754

742-
// e.g. cr();
743-
this.instruction(this._refreshMode, ast.sourceSpan, R3.containerRefreshEnd);
744-
745755
// Create the template function
746756
const templateVisitor = new TemplateDefinitionBuilder(
747757
this.outputCtx, this.constantPool, this.reflector, templateContext,
748758
this.bindingScope.nestedScope(), this.level + 1, this.ngContentSelectors, contextName,
749-
templateName, this.pipes, [], this.addPipeDependency);
759+
templateName, this.pipes, [], this.addDirectiveDependency, this.addPipeDependency);
750760
const templateFunctionExpr = templateVisitor.buildTemplateFunction(ast.children, ast.variables);
751761
this._postfix.push(templateFunctionExpr.toDeclStmt(templateName, null));
752762
}
@@ -811,8 +821,6 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
811821
statements.push(o.importExpr(reference, null, span).callFn(params, span).toStmt());
812822
}
813823

814-
private typeReference(type: any): o.Expression { return this.outputCtx.importExpr(type); }
815-
816824
private definitionOf(type: any, kind: DefinitionKind): o.Expression {
817825
return this.constantPool.getDefinition(type, kind, this.outputCtx);
818826
}
@@ -923,6 +931,16 @@ export function createFactory(
923931
type.reference.name ? `${type.reference.name}_Factory` : null);
924932
}
925933

934+
/**
935+
* Remove trailing null nodes as they are implied.
936+
*/
937+
function trimTrailingNulls(parameters: o.Expression[]): o.Expression[] {
938+
while (o.isNull(parameters[parameters.length - 1])) {
939+
parameters.pop();
940+
}
941+
return parameters;
942+
}
943+
926944
type HostBindings = {
927945
[key: string]: string
928946
};

packages/compiler/src/selector.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,17 @@ const _SELECTOR_REGEXP = new RegExp(
2828
export class CssSelector {
2929
element: string|null = null;
3030
classNames: string[] = [];
31+
/**
32+
* The selectors are encoded in pairs where:
33+
* - even locations are attribute names
34+
* - odd locations are attribute values.
35+
*
36+
* Example:
37+
* Selector: `[key1=value1][key2]` would parse to:
38+
* ```
39+
* ['key1', 'value1', 'key2', '']
40+
* ```
41+
*/
3142
attrs: string[] = [];
3243
notSelectors: CssSelector[] = [];
3344

0 commit comments

Comments
 (0)