refactor(ivy): break apart stylingMap into styleMap and classMap instructions (#30293)

This patch breaks up the existing `elementStylingMap` into
`elementClassMap` and `elementStyleMap` instructions. It also breaks
apart `hostStlyingMap` into `hostClassMap` and `hostStyleMap`
instructions. This change allows for better tree-shaking and reduces
the complexity of the styling algorithm code for `[style]` and `[class]`
bindings.

PR Close #30293
This commit is contained in:
Matias Niemelä
2019-05-06 17:44:03 -06:00
committed by Kara Erickson
parent 98a38ec98b
commit be8fbac942
18 changed files with 422 additions and 328 deletions

View File

@ -51,7 +51,9 @@ export class Identifiers {
static elementStyling: o.ExternalReference = {name: 'ɵɵelementStyling', moduleName: CORE};
static elementStylingMap: o.ExternalReference = {name: 'ɵɵelementStylingMap', moduleName: CORE};
static elementStyleMap: o.ExternalReference = {name: 'ɵɵelementStyleMap', moduleName: CORE};
static elementClassMap: o.ExternalReference = {name: 'ɵɵelementClassMap', moduleName: CORE};
static elementStyleProp: o.ExternalReference = {name: 'ɵɵelementStyleProp', moduleName: CORE};
@ -62,8 +64,11 @@ export class Identifiers {
static elementHostStyling: o.ExternalReference = {name: 'ɵɵelementHostStyling', moduleName: CORE};
static elementHostStylingMap:
o.ExternalReference = {name: 'ɵɵelementHostStylingMap', moduleName: CORE};
static elementHostStyleMap:
o.ExternalReference = {name: 'ɵɵelementHostStyleMap', moduleName: CORE};
static elementHostClassMap:
o.ExternalReference = {name: 'ɵɵelementHostClassMap', moduleName: CORE};
static elementHostStyleProp:
o.ExternalReference = {name: 'ɵɵelementHostStyleProp', moduleName: CORE};

View File

@ -61,7 +61,8 @@ interface BoundStylingEntry {
* elementStyling(...)
* }
* if (updateMode) {
* elementStylingMap(...)
* elementStyleMap(...)
* elementClassMap(...)
* elementStyleProp(...)
* elementClassProp(...)
* elementStylingApp(...)
@ -339,73 +340,67 @@ export class StylingBuilder {
}
/**
* Builds an instruction with all the expressions and parameters for `elementStylingMap`.
* Builds an instruction with all the expressions and parameters for `elementClassMap`.
*
* The instruction data will contain all expressions for `elementStylingMap` to function
* which include the `[style]` and `[class]` expression params (if they exist) as well as
* the sanitizer and directive reference expression.
* The instruction data will contain all expressions for `elementClassMap` to function
* which includes the `[class]` expression params.
*/
buildElementStylingMapInstruction(valueConverter: ValueConverter): Instruction|null {
if (this._classMapInput || this._styleMapInput) {
const stylingInput = this._classMapInput ! || this._styleMapInput !;
let totalBindingSlotsRequired = 0;
// these values must be outside of the update block so that they can
// be evaluted (the AST visit call) during creation time so that any
// pipes can be picked up in time before the template is built
const mapBasedClassValue =
this._classMapInput ? this._classMapInput.value.visit(valueConverter) : null;
if (mapBasedClassValue instanceof Interpolation) {
totalBindingSlotsRequired += mapBasedClassValue.expressions.length;
}
const mapBasedStyleValue =
this._styleMapInput ? this._styleMapInput.value.visit(valueConverter) : null;
if (mapBasedStyleValue instanceof Interpolation) {
totalBindingSlotsRequired += mapBasedStyleValue.expressions.length;
}
const isHostBinding = this._directiveExpr;
const reference = isHostBinding ? R3.elementHostStylingMap : R3.elementStylingMap;
return {
sourceSpan: stylingInput.sourceSpan,
reference,
allocateBindingSlots: totalBindingSlotsRequired,
buildParams: (convertFn: (value: any) => o.Expression) => {
// HOST:
// min params => elementHostStylingMap(classMap)
// max params => elementHostStylingMap(classMap, styleMap)
// Template:
// min params => elementStylingMap(elmIndex, classMap)
// max params => elementStylingMap(elmIndex, classMap, styleMap)
const params: o.Expression[] = [];
if (!isHostBinding) {
params.push(this._elementIndexExpr);
}
let expectedNumberOfArgs = 0;
if (mapBasedStyleValue) {
expectedNumberOfArgs = 2;
} else if (mapBasedClassValue) {
// index and class = 2
expectedNumberOfArgs = 1;
}
addParam(
params, mapBasedClassValue, mapBasedClassValue ? convertFn(mapBasedClassValue) : null,
1, expectedNumberOfArgs);
addParam(
params, mapBasedStyleValue, mapBasedStyleValue ? convertFn(mapBasedStyleValue) : null,
2, expectedNumberOfArgs);
return params;
}
};
buildElementClassMapInstruction(valueConverter: ValueConverter): Instruction|null {
if (this._classMapInput) {
return this._buildMapBasedInstruction(valueConverter, true, this._classMapInput);
}
return null;
}
/**
* Builds an instruction with all the expressions and parameters for `elementStyleMap`.
*
* The instruction data will contain all expressions for `elementStyleMap` to function
* which includes the `[style]` expression params.
*/
buildElementStyleMapInstruction(valueConverter: ValueConverter): Instruction|null {
if (this._styleMapInput) {
return this._buildMapBasedInstruction(valueConverter, false, this._styleMapInput);
}
return null;
}
private _buildMapBasedInstruction(
valueConverter: ValueConverter, isClassBased: boolean, stylingInput: BoundStylingEntry) {
let totalBindingSlotsRequired = 0;
// these values must be outside of the update block so that they can
// be evaluated (the AST visit call) during creation time so that any
// pipes can be picked up in time before the template is built
const mapValue = stylingInput.value.visit(valueConverter);
if (mapValue instanceof Interpolation) {
totalBindingSlotsRequired += mapValue.expressions.length;
}
const isHostBinding = this._directiveExpr;
let reference: o.ExternalReference;
if (isClassBased) {
reference = isHostBinding ? R3.elementHostClassMap : R3.elementClassMap;
} else {
reference = isHostBinding ? R3.elementHostStyleMap : R3.elementStyleMap;
}
return {
sourceSpan: stylingInput.sourceSpan,
reference,
allocateBindingSlots: totalBindingSlotsRequired,
buildParams: (convertFn: (value: any) => o.Expression) => {
const params: o.Expression[] = [];
if (!isHostBinding) {
params.push(this._elementIndexExpr);
}
params.push(convertFn(mapValue));
return params;
}
};
}
private _buildSingleInputs(
reference: o.ExternalReference, isHostBinding: boolean, inputs: BoundStylingEntry[],
mapIndex: Map<string, number>, allowUnits: boolean,
@ -498,9 +493,13 @@ export class StylingBuilder {
buildUpdateLevelInstructions(valueConverter: ValueConverter) {
const instructions: Instruction[] = [];
if (this.hasBindings) {
const mapInstruction = this.buildElementStylingMapInstruction(valueConverter);
if (mapInstruction) {
instructions.push(mapInstruction);
const styleMapInstruction = this.buildElementStyleMapInstruction(valueConverter);
if (styleMapInstruction) {
instructions.push(styleMapInstruction);
}
const classMapInstruction = this.buildElementClassMapInstruction(valueConverter);
if (classMapInstruction) {
instructions.push(classMapInstruction);
}
instructions.push(...this._buildStyleInputs(valueConverter));
instructions.push(...this._buildClassInputs(valueConverter));

View File

@ -686,8 +686,8 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
// the code here will collect all update-level styling instructions and add them to the
// update block of the template function AOT code. Instructions like `elementStyleProp`,
// `elementStylingMap`, `elementClassProp` and `elementStylingApply` are all generated
// and assign in the code below.
// `elementStyleMap`, `elementClassMap`, `elementClassProp` and `elementStylingApply`
// are all generated and assigned in the code below.
stylingBuilder.buildUpdateLevelInstructions(this._valueConverter).forEach(instruction => {
this._bindingSlots += instruction.allocateBindingSlots;
this.processStylingInstruction(implicit, instruction, false);