refactor(ivy): align compiler with runtime (#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 #22921
This commit is contained in:
Miško Hevery
2018-03-29 12:58:41 -07:00
committed by Alex Rickabaugh
parent 5266ffe04a
commit 60065935be
32 changed files with 402 additions and 348 deletions

View File

@ -33,10 +33,6 @@ export class Identifiers {
static containerEnd: o.ExternalReference = {name: 'ɵc', moduleName: CORE};
static containerRefreshStart: o.ExternalReference = {name: 'ɵcR', moduleName: CORE};
static containerRefreshEnd: o.ExternalReference = {name: 'ɵcr', moduleName: CORE};
static directiveCreate: o.ExternalReference = {name: 'ɵD', moduleName: CORE};
static text: o.ExternalReference = {name: 'ɵT', moduleName: CORE};

View File

@ -120,6 +120,23 @@ export function compileComponent(
addDependencyToComponent(outputCtx, summary, pipeSet, pipeExps);
}
const directiveExps: o.Expression[] = [];
const directiveMap = new Set<string>();
/**
* This function gets called every time a directive dependency needs to be added to the template.
* Its job is to remove duplicates from the list. (Only have single dependency no matter how many
* times the dependency is used.)
*/
function addDirectiveDependency(ast: DirectiveAst) {
const importExpr = outputCtx.importExpr(ast.directive.type.reference) as o.ExternalExpr;
const uniqueKey = importExpr.value.moduleName + ':' + importExpr.value.name;
if (!directiveMap.has(uniqueKey)) {
directiveMap.add(uniqueKey);
directiveExps.push(importExpr);
}
}
const field = (key: string, value: o.Expression | null) => {
if (value) {
definitionMapValues.push({key, value, quoted: false});
@ -162,10 +179,13 @@ export function compileComponent(
new TemplateDefinitionBuilder(
outputCtx, outputCtx.constantPool, reflector, CONTEXT_NAME, ROOT_SCOPE.nestedScope(), 0,
component.template !.ngContentSelectors, templateTypeName, templateName, pipeMap,
component.viewQueries, addPipeDependency)
component.viewQueries, addDirectiveDependency, addPipeDependency)
.buildTemplateFunction(template, []);
field('template', templateFunctionExpression);
if (directiveExps.length) {
field('directives', o.literalArr(directiveExps));
}
// e.g. `pipes: [MyPipe]`
if (pipeExps.length) {
@ -373,6 +393,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
private bindingScope: BindingScope, private level = 0, private ngContentSelectors: string[],
private contextName: string|null, private templateName: string|null,
private pipes: Map<string, CompilePipeSummary>, private viewQueries: CompileQueryMetadata[],
private addDirectiveDependency: (ast: DirectiveAst) => void,
private addPipeDependency: (summary: CompilePipeSummary) => void) {
this._valueConverter = new ValueConverter(
outputCtx, () => this.allocateDataSlot(), (name, localName, slot, value) => {
@ -504,23 +525,9 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
this.instruction(this._creationMode, ast.sourceSpan, R3.projection, ...parameters);
}
private _computeDirectivesArray(directives: DirectiveAst[]) {
const directiveExpressions: o.Expression[] =
directives.filter(directive => !directive.directive.isComponent).map(directive => {
this.allocateDataSlot(); // Allocate space for the directive
return this.typeReference(directive.directive.type.reference);
});
return directiveExpressions.length ?
this.constantPool.getConstLiteral(
o.literalArr(directiveExpressions), /* forceShared */ true) :
o.literal(null, o.INFERRED_TYPE);
;
}
// TemplateAstVisitor
visitElement(element: ElementAst) {
const elementIndex = this.allocateDataSlot();
let componentIndex: number|undefined = undefined;
const referenceDataSlots = new Map<string, number>();
const wasInI18nSection = this._inI18nSection;
@ -563,13 +570,11 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
const nullNode = o.literal(null, o.INFERRED_TYPE);
const parameters: o.Expression[] = [o.literal(elementIndex)];
// Add component type or element tag
if (component) {
parameters.push(this.typeReference(component.directive.type.reference));
componentIndex = this.allocateDataSlot();
} else {
parameters.push(o.literal(element.name));
this.addDirectiveDependency(component);
}
element.directives.forEach(this.addDirectiveDependency);
parameters.push(o.literal(element.name));
// Add the attributes
const i18nMessages: o.Statement[] = [];
@ -598,10 +603,6 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
parameters.push(attrArg);
// Add directives array
const directivesArray = this._computeDirectivesArray(element.directives);
parameters.push(directivesArray);
if (element.references && element.references.length > 0) {
const references =
flatten(element.references.map(reference => {
@ -621,16 +622,12 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
parameters.push(nullNode);
}
// Remove trailing null nodes as they are implied.
while (o.isNull(parameters[parameters.length - 1])) {
parameters.pop();
}
// Generate the instruction create element instruction
if (i18nMessages.length > 0) {
this._creationMode.push(...i18nMessages);
}
this.instruction(this._creationMode, element.sourceSpan, R3.createElement, ...parameters);
this.instruction(
this._creationMode, element.sourceSpan, R3.createElement, ...trimTrailingNulls(parameters));
const implicit = o.variable(this.contextParameter);
@ -725,28 +722,41 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
contextName ? `${contextName}_Template_${templateIndex}` : `Template_${templateIndex}`;
const templateContext = `ctx${this.level}`;
const directivesArray = this._computeDirectivesArray(ast.directives);
const parameters: o.Expression[] = [o.variable(templateName), o.literal(null, o.INFERRED_TYPE)];
const attributeNames: o.Expression[] = [];
ast.directives.forEach((directiveAst: DirectiveAst) => {
this.addDirectiveDependency(directiveAst);
CssSelector.parse(directiveAst.directive.selector !).forEach(selector => {
selector.attrs.forEach((value) => {
// Convert '' (falsy) strings into `null`. This is needed because we want
// to communicate to runtime that these attributes are present for
// selector matching, but should not actually be added to the DOM.
// attributeNames.push(o.literal(value ? value : null));
// TODO(misko): make the above comment true, for now just write to DOM because
// the runtime selectors have not been updated.
attributeNames.push(o.literal(value));
});
});
});
if (attributeNames.length) {
parameters.push(
this.constantPool.getConstLiteral(o.literalArr(attributeNames), /* forcedShared */ true));
}
// e.g. C(1, C1Template)
this.instruction(
this._creationMode, ast.sourceSpan, R3.containerCreate, o.literal(templateIndex),
directivesArray, o.variable(templateName));
// e.g. Cr(1)
this.instruction(
this._refreshMode, ast.sourceSpan, R3.containerRefreshStart, o.literal(templateIndex));
...trimTrailingNulls(parameters));
// Generate directives
this._visitDirectives(ast.directives, o.variable(this.contextParameter), templateIndex);
// e.g. cr();
this.instruction(this._refreshMode, ast.sourceSpan, R3.containerRefreshEnd);
// Create the template function
const templateVisitor = new TemplateDefinitionBuilder(
this.outputCtx, this.constantPool, this.reflector, templateContext,
this.bindingScope.nestedScope(), this.level + 1, this.ngContentSelectors, contextName,
templateName, this.pipes, [], this.addPipeDependency);
templateName, this.pipes, [], this.addDirectiveDependency, this.addPipeDependency);
const templateFunctionExpr = templateVisitor.buildTemplateFunction(ast.children, ast.variables);
this._postfix.push(templateFunctionExpr.toDeclStmt(templateName, null));
}
@ -811,8 +821,6 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
statements.push(o.importExpr(reference, null, span).callFn(params, span).toStmt());
}
private typeReference(type: any): o.Expression { return this.outputCtx.importExpr(type); }
private definitionOf(type: any, kind: DefinitionKind): o.Expression {
return this.constantPool.getDefinition(type, kind, this.outputCtx);
}
@ -923,6 +931,16 @@ export function createFactory(
type.reference.name ? `${type.reference.name}_Factory` : null);
}
/**
* Remove trailing null nodes as they are implied.
*/
function trimTrailingNulls(parameters: o.Expression[]): o.Expression[] {
while (o.isNull(parameters[parameters.length - 1])) {
parameters.pop();
}
return parameters;
}
type HostBindings = {
[key: string]: string
};

View File

@ -28,6 +28,17 @@ const _SELECTOR_REGEXP = new RegExp(
export class CssSelector {
element: string|null = null;
classNames: string[] = [];
/**
* The selectors are encoded in pairs where:
* - even locations are attribute names
* - odd locations are attribute values.
*
* Example:
* Selector: `[key1=value1][key2]` would parse to:
* ```
* ['key1', 'value1', 'key2', '']
* ```
*/
attrs: string[] = [];
notSelectors: CssSelector[] = [];