perf(ivy): chain styling instructions (#33837)
Adds support for chaining of `styleProp`, `classProp` and `stylePropInterpolateX` instructions whenever possible which should help generate less code. Note that one complication here is for `stylePropInterpolateX` instructions where we have to break into multiple chains if there are other styling instructions inbetween the interpolations which helps maintain the execution order. PR Close #33837
This commit is contained in:
parent
f69c6e204a
commit
8a052dc858
|
@ -513,8 +513,7 @@ describe('compiler compliance: styling', () => {
|
|||
if (rf & 2) {
|
||||
$r3$.ɵɵstyleSanitizer($r3$.ɵɵdefaultStyleSanitizer);
|
||||
$r3$.ɵɵstyleMap($ctx$.myStyleExp);
|
||||
$r3$.ɵɵstyleProp("width", $ctx$.myWidth);
|
||||
$r3$.ɵɵstyleProp("height", $ctx$.myHeight);
|
||||
$r3$.ɵɵstyleProp("width", $ctx$.myWidth)("height", $ctx$.myHeight);
|
||||
$r3$.ɵɵattribute("style", "border-width: 10px", $r3$.ɵɵsanitizeStyle);
|
||||
}
|
||||
},
|
||||
|
@ -706,8 +705,7 @@ describe('compiler compliance: styling', () => {
|
|||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵɵclassMap($ctx$.myClassExp);
|
||||
$r3$.ɵɵclassProp("apple", $ctx$.yesToApple);
|
||||
$r3$.ɵɵclassProp("orange", $ctx$.yesToOrange);
|
||||
$r3$.ɵɵclassProp("apple", $ctx$.yesToApple)("orange", $ctx$.yesToOrange);
|
||||
$r3$.ɵɵattribute("class", "banana");
|
||||
}
|
||||
},
|
||||
|
@ -916,8 +914,7 @@ describe('compiler compliance: styling', () => {
|
|||
$r3$.ɵɵstyleSanitizer($r3$.ɵɵdefaultStyleSanitizer);
|
||||
$r3$.ɵɵstyleMap($r3$.ɵɵpipeBind2(1, 8, $ctx$.myStyleExp, 1000));
|
||||
$r3$.ɵɵclassMap($r3$.ɵɵpureFunction0(20, _c0));
|
||||
$r3$.ɵɵstyleProp("bar", $r3$.ɵɵpipeBind2(2, 11, $ctx$.barExp, 3000));
|
||||
$r3$.ɵɵstyleProp("baz", $r3$.ɵɵpipeBind2(3, 14, $ctx$.bazExp, 4000));
|
||||
$r3$.ɵɵstyleProp("bar", $r3$.ɵɵpipeBind2(2, 11, $ctx$.barExp, 3000))("baz", $r3$.ɵɵpipeBind2(3, 14, $ctx$.bazExp, 4000));
|
||||
$r3$.ɵɵclassProp("foo", $r3$.ɵɵpipeBind2(4, 17, $ctx$.fooExp, 2000));
|
||||
$r3$.ɵɵadvance(5);
|
||||
$r3$.ɵɵtextInterpolate1(" ", $ctx$.item, "");
|
||||
|
@ -1081,10 +1078,8 @@ describe('compiler compliance: styling', () => {
|
|||
$r3$.ɵɵstyleSanitizer($r3$.ɵɵdefaultStyleSanitizer);
|
||||
$r3$.ɵɵstyleMap(ctx.myStyle);
|
||||
$r3$.ɵɵclassMap(ctx.myClasses);
|
||||
$r3$.ɵɵstyleProp("height", ctx.myHeightProp, "pt");
|
||||
$r3$.ɵɵstyleProp("width", ctx.myWidthProp);
|
||||
$r3$.ɵɵclassProp("bar", ctx.myBarClass);
|
||||
$r3$.ɵɵclassProp("foo", ctx.myFooClass);
|
||||
$r3$.ɵɵstyleProp("height", ctx.myHeightProp, "pt")("width", ctx.myWidthProp);
|
||||
$r3$.ɵɵclassProp("bar", ctx.myBarClass)("foo", ctx.myFooClass);
|
||||
}
|
||||
},
|
||||
decls: 0,
|
||||
|
@ -1461,6 +1456,351 @@ describe('compiler compliance: styling', () => {
|
|||
|
||||
});
|
||||
|
||||
describe('instruction chaining', () => {
|
||||
it('should chain classProp instruction calls', () => {
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
template: \`<div [class.apple]="yesToApple"
|
||||
[class.orange]="yesToOrange"
|
||||
[class.tomato]="yesToTomato"></div>\`
|
||||
})
|
||||
export class MyComponent {
|
||||
yesToApple = true;
|
||||
yesToOrange = true;
|
||||
tesToTomato = false;
|
||||
}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
const template = `
|
||||
…
|
||||
MyComponent.ɵcmp = $r3$.ɵɵdefineComponent({
|
||||
…
|
||||
template: function MyComponent_Template(rf, $ctx$) {
|
||||
…
|
||||
if (rf & 2) {
|
||||
$r3$.ɵɵclassProp("apple", $ctx$.yesToApple)("orange", $ctx$.yesToOrange)("tomato", $ctx$.yesToTomato);
|
||||
}
|
||||
},
|
||||
encapsulation: 2
|
||||
});
|
||||
`;
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
expectEmit(result.source, template, 'Incorrect template');
|
||||
});
|
||||
|
||||
it('should chain styleProp instruction calls', () => {
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
template: \`<div [style.color]="color"
|
||||
[style.border]="border"
|
||||
[style.transition]="transition"></div>\`
|
||||
})
|
||||
export class MyComponent {
|
||||
color = 'red';
|
||||
border = '1px solid purple';
|
||||
transition = 'all 1337ms ease';
|
||||
}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
const template = `
|
||||
…
|
||||
MyComponent.ɵcmp = $r3$.ɵɵdefineComponent({
|
||||
…
|
||||
template: function MyComponent_Template(rf, $ctx$) {
|
||||
…
|
||||
if (rf & 2) {
|
||||
$r3$.ɵɵstyleProp("color", $ctx$.color)("border", $ctx$.border)("transition", $ctx$.transition);
|
||||
}
|
||||
},
|
||||
encapsulation: 2
|
||||
});
|
||||
`;
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
expectEmit(result.source, template, 'Incorrect template');
|
||||
});
|
||||
|
||||
it('should chain mixed styleProp and classProp calls', () => {
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
template: \`<div
|
||||
[class.apple]="yesToApple"
|
||||
[style.color]="color"
|
||||
[class.orange]="yesToOrange"
|
||||
[style.border]="border"
|
||||
[class.tomato]="yesToTomato"
|
||||
[style.transition]="transition"></div>\`
|
||||
})
|
||||
export class MyComponent {
|
||||
color = 'red';
|
||||
border = '1px solid purple';
|
||||
transition = 'all 1337ms ease';
|
||||
yesToApple = true;
|
||||
yesToOrange = true;
|
||||
tesToTomato = false;
|
||||
}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
const template = `
|
||||
…
|
||||
MyComponent.ɵcmp = $r3$.ɵɵdefineComponent({
|
||||
…
|
||||
template: function MyComponent_Template(rf, $ctx$) {
|
||||
…
|
||||
if (rf & 2) {
|
||||
$r3$.ɵɵstyleProp("color", $ctx$.color)("border", $ctx$.border)("transition", $ctx$.transition);
|
||||
$r3$.ɵɵclassProp("apple", $ctx$.yesToApple)("orange", $ctx$.yesToOrange)("tomato", $ctx$.yesToTomato);
|
||||
}
|
||||
},
|
||||
encapsulation: 2
|
||||
});
|
||||
`;
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
expectEmit(result.source, template, 'Incorrect template');
|
||||
});
|
||||
|
||||
it('should chain style interpolations of the same kind', () => {
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
template: \`<div
|
||||
style.color="a{{one}}b"
|
||||
style.border="a{{one}}b"
|
||||
style.transition="a{{one}}b"></div>\`
|
||||
})
|
||||
export class MyComponent {
|
||||
}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
const template = `
|
||||
…
|
||||
MyComponent.ɵcmp = $r3$.ɵɵdefineComponent({
|
||||
…
|
||||
template: function MyComponent_Template(rf, $ctx$) {
|
||||
…
|
||||
if (rf & 2) {
|
||||
$r3$.ɵɵstylePropInterpolate1("color", "a", ctx.one, "b")("border", "a", ctx.one, "b")("transition", "a", ctx.one, "b");
|
||||
}
|
||||
},
|
||||
encapsulation: 2
|
||||
});
|
||||
`;
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
expectEmit(result.source, template, 'Incorrect template');
|
||||
});
|
||||
|
||||
it('should chain style interpolations of multiple kinds', () => {
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
template: \`<div
|
||||
style.color="a{{one}}b"
|
||||
style.border="a{{one}}b"
|
||||
style.transition="a{{one}}b{{two}}c"
|
||||
style.width="a{{one}}b{{two}}c"
|
||||
style.height="a{{one}}b{{two}}c{{three}}d"
|
||||
style.top="a{{one}}b{{two}}c{{three}}d"></div>\`
|
||||
})
|
||||
export class MyComponent {
|
||||
}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
const template = `
|
||||
…
|
||||
MyComponent.ɵcmp = $r3$.ɵɵdefineComponent({
|
||||
…
|
||||
template: function MyComponent_Template(rf, $ctx$) {
|
||||
…
|
||||
if (rf & 2) {
|
||||
$r3$.ɵɵstylePropInterpolate1("color", "a", ctx.one, "b")("border", "a", ctx.one, "b");
|
||||
$r3$.ɵɵstylePropInterpolate2("transition", "a", ctx.one, "b", ctx.two, "c")("width", "a", ctx.one, "b", ctx.two, "c");
|
||||
$r3$.ɵɵstylePropInterpolate3("height", "a", ctx.one, "b", ctx.two, "c", ctx.three, "d")("top", "a", ctx.one, "b", ctx.two, "c", ctx.three, "d");
|
||||
}
|
||||
},
|
||||
encapsulation: 2
|
||||
});
|
||||
`;
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
expectEmit(result.source, template, 'Incorrect template');
|
||||
});
|
||||
|
||||
it('should break into multiple chains if there are other styling instructions in between',
|
||||
() => {
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
template: \`<div
|
||||
style.color="a{{one}}b"
|
||||
style.border="a{{one}}b"
|
||||
[class.apple]="yesToApple"
|
||||
[style.transition]="transition"
|
||||
[class.orange]="yesToOrange"
|
||||
[style.width]="width"
|
||||
style.height="a{{one}}b"
|
||||
style.top="a{{one}}b"></div>\`
|
||||
})
|
||||
export class MyComponent {
|
||||
transition = 'all 1337ms ease';
|
||||
width = '42px';
|
||||
yesToApple = true;
|
||||
yesToOrange = true;
|
||||
}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
const template = `
|
||||
…
|
||||
MyComponent.ɵcmp = $r3$.ɵɵdefineComponent({
|
||||
…
|
||||
template: function MyComponent_Template(rf, $ctx$) {
|
||||
…
|
||||
if (rf & 2) {
|
||||
$r3$.ɵɵstylePropInterpolate1("color", "a", ctx.one, "b")("border", "a", ctx.one, "b");
|
||||
$r3$.ɵɵstyleProp("transition", ctx.transition)("width", ctx.width);
|
||||
$r3$.ɵɵstylePropInterpolate1("height", "a", ctx.one, "b")("top", "a", ctx.one, "b");
|
||||
$r3$.ɵɵclassProp("apple", ctx.yesToApple)("orange", ctx.yesToOrange);
|
||||
}
|
||||
},
|
||||
encapsulation: 2
|
||||
});
|
||||
`;
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
expectEmit(result.source, template, 'Incorrect template');
|
||||
});
|
||||
|
||||
it('should break into multiple chains if there are other styling interpolation instructions in between',
|
||||
() => {
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
template: \`<div
|
||||
style.color="a{{one}}b"
|
||||
style.border="a{{one}}b"
|
||||
style.transition="a{{one}}b{{two}}c"
|
||||
style.width="a{{one}}b{{two}}c{{three}}d"
|
||||
style.height="a{{one}}b"
|
||||
style.top="a{{one}}b"></div>\`
|
||||
})
|
||||
export class MyComponent {
|
||||
transition = 'all 1337ms ease';
|
||||
width = '42px';
|
||||
}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
const template = `
|
||||
…
|
||||
MyComponent.ɵcmp = $r3$.ɵɵdefineComponent({
|
||||
…
|
||||
template: function MyComponent_Template(rf, $ctx$) {
|
||||
…
|
||||
if (rf & 2) {
|
||||
$r3$.ɵɵstylePropInterpolate1("color", "a", ctx.one, "b")("border", "a", ctx.one, "b");
|
||||
$r3$.ɵɵstylePropInterpolate2("transition", "a", ctx.one, "b", ctx.two, "c");
|
||||
$r3$.ɵɵstylePropInterpolate3("width", "a", ctx.one, "b", ctx.two, "c", ctx.three, "d");
|
||||
$r3$.ɵɵstylePropInterpolate1("height", "a", ctx.one, "b")("top", "a", ctx.one, "b");
|
||||
}
|
||||
},
|
||||
encapsulation: 2
|
||||
});
|
||||
`;
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
expectEmit(result.source, template, 'Incorrect template');
|
||||
});
|
||||
|
||||
it('should chain styling instructions inside host bindings', () => {
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Component, HostBinding} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
template: '',
|
||||
host: {
|
||||
'[class.apple]': 'yesToApple',
|
||||
'[style.color]': 'color',
|
||||
'[class.tomato]': 'yesToTomato',
|
||||
'[style.transition]': 'transition'
|
||||
}
|
||||
})
|
||||
export class MyComponent {
|
||||
color = 'red';
|
||||
transition = 'all 1337ms ease';
|
||||
yesToApple = true;
|
||||
tesToTomato = false;
|
||||
|
||||
@HostBinding('style.border')
|
||||
border = '1px solid purple';
|
||||
|
||||
@HostBinding('class.orange')
|
||||
yesToOrange = true;
|
||||
}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
const template = `
|
||||
…
|
||||
MyComponent.ɵcmp = $r3$.ɵɵdefineComponent({
|
||||
…
|
||||
hostBindings: function MyComponent_HostBindings(rf, $ctx$, elIndex) {
|
||||
…
|
||||
if (rf & 2) {
|
||||
$r3$.ɵɵstyleProp("color", $ctx$.color)("transition", $ctx$.transition)("border", $ctx$.border);
|
||||
$r3$.ɵɵclassProp("apple", $ctx$.yesToApple)("tomato", $ctx$.yesToTomato)("orange", $ctx$.yesToOrange);
|
||||
}
|
||||
},
|
||||
…
|
||||
});
|
||||
`;
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
expectEmit(result.source, template, 'Incorrect template');
|
||||
});
|
||||
});
|
||||
|
||||
it('should count only non-style and non-class host bindings on Components', () => {
|
||||
const files = {
|
||||
app: {
|
||||
|
|
|
@ -28,7 +28,7 @@ import {Render3ParseResult} from '../r3_template_transform';
|
|||
import {prepareSyntheticListenerFunctionName, prepareSyntheticPropertyName, typeWithParameters} from '../util';
|
||||
|
||||
import {R3ComponentDef, R3ComponentMetadata, R3DirectiveDef, R3DirectiveMetadata, R3HostMetadata, R3QueryMetadata} from './api';
|
||||
import {StylingBuilder, StylingInstruction} from './styling_builder';
|
||||
import {StylingBuilder, StylingInstructionCall} from './styling_builder';
|
||||
import {BindingScope, TemplateDefinitionBuilder, ValueConverter, makeBindingParser, prepareEventListenerParameters, renderFlagCheckIfStmt, resolveSanitizationFn} from './template';
|
||||
import {CONTEXT_NAME, DefinitionMap, RENDER_FLAGS, TEMPORARY_NAME, asLiteral, chainedInstruction, conditionallyCreateMapObjectLiteral, getQueryPredicate, temporaryAllocator} from './util';
|
||||
|
||||
|
@ -652,8 +652,12 @@ function createHostBindingsFunction(
|
|||
// collected earlier.
|
||||
const hostAttrs = convertAttributesToExpressions(hostBindingsMetadata.attributes);
|
||||
const hostInstruction = styleBuilder.buildHostAttrsInstruction(null, hostAttrs, constantPool);
|
||||
if (hostInstruction) {
|
||||
createStatements.push(createStylingStmt(hostInstruction, bindingContext, bindingFn));
|
||||
if (hostInstruction && hostInstruction.calls.length > 0) {
|
||||
createStatements.push(
|
||||
chainedInstruction(
|
||||
hostInstruction.reference,
|
||||
hostInstruction.calls.map(call => convertStylingCall(call, bindingContext, bindingFn)))
|
||||
.toStmt());
|
||||
}
|
||||
|
||||
if (styleBuilder.hasBindings) {
|
||||
|
@ -661,11 +665,18 @@ function createHostBindingsFunction(
|
|||
// the update block of a component/directive templateFn/hostBindingsFn so that the bindings
|
||||
// are evaluated and updated for the element.
|
||||
styleBuilder.buildUpdateLevelInstructions(getValueConverter()).forEach(instruction => {
|
||||
// we subtract a value of `1` here because the binding slot was already
|
||||
// allocated at the top of this method when all the input bindings were
|
||||
// counted.
|
||||
totalHostVarsCount += Math.max(instruction.allocateBindingSlots - 1, 0);
|
||||
updateStatements.push(createStylingStmt(instruction, bindingContext, bindingFn));
|
||||
if (instruction.calls.length > 0) {
|
||||
const calls: o.Expression[][] = [];
|
||||
|
||||
instruction.calls.forEach(call => {
|
||||
// we subtract a value of `1` here because the binding slot was already allocated
|
||||
// at the top of this method when all the input bindings were counted.
|
||||
totalHostVarsCount += Math.max(call.allocateBindingSlots - 1, 0);
|
||||
calls.push(convertStylingCall(call, bindingContext, bindingFn));
|
||||
});
|
||||
|
||||
updateStatements.push(chainedInstruction(instruction.reference, calls).toStmt());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -699,12 +710,9 @@ function bindingFn(implicit: any, value: AST) {
|
|||
null, implicit, value, 'b', BindingForm.TrySimple, () => error('Unexpected interpolation'));
|
||||
}
|
||||
|
||||
function createStylingStmt(
|
||||
instruction: StylingInstruction, bindingContext: any, bindingFn: Function): o.Statement {
|
||||
const params = instruction.params(value => bindingFn(bindingContext, value).currValExpr);
|
||||
return o.importExpr(instruction.reference, null, instruction.sourceSpan)
|
||||
.callFn(params, instruction.sourceSpan)
|
||||
.toStmt();
|
||||
function convertStylingCall(
|
||||
call: StylingInstructionCall, bindingContext: any, bindingFn: Function) {
|
||||
return call.params(value => bindingFn(bindingContext, value).currValExpr);
|
||||
}
|
||||
|
||||
function getBindingNameAndInstruction(binding: ParsedProperty):
|
||||
|
|
|
@ -11,7 +11,6 @@ import {AST, ASTWithSource, BindingPipe, BindingType, Interpolation} from '../..
|
|||
import * as o from '../../output/output_ast';
|
||||
import {ParseSourceSpan} from '../../parse_util';
|
||||
import {isEmptyExpression} from '../../template_parser/template_parser';
|
||||
import {error} from '../../util';
|
||||
import * as t from '../r3_ast';
|
||||
import {Identifiers as R3} from '../r3_identifiers';
|
||||
|
||||
|
@ -25,10 +24,15 @@ const IMPORTANT_FLAG = '!important';
|
|||
* A styling expression summary that is to be processed by the compiler
|
||||
*/
|
||||
export interface StylingInstruction {
|
||||
sourceSpan: ParseSourceSpan|null;
|
||||
reference: o.ExternalReference;
|
||||
allocateBindingSlots: number;
|
||||
/** Calls to individual styling instructions. Used when chaining calls to the same instruction. */
|
||||
calls: StylingInstructionCall[];
|
||||
}
|
||||
|
||||
export interface StylingInstructionCall {
|
||||
sourceSpan: ParseSourceSpan|null;
|
||||
supportsInterpolation?: boolean;
|
||||
allocateBindingSlots: number;
|
||||
params: ((convertFn: (value: any) => o.Expression | o.Expression[]) => o.Expression[]);
|
||||
}
|
||||
|
||||
|
@ -280,17 +284,19 @@ export class StylingBuilder {
|
|||
constantPool: ConstantPool): StylingInstruction|null {
|
||||
if (this._directiveExpr && (attrs.length || this._hasInitialValues)) {
|
||||
return {
|
||||
sourceSpan,
|
||||
reference: R3.elementHostAttrs,
|
||||
allocateBindingSlots: 0,
|
||||
params: () => {
|
||||
// params => elementHostAttrs(attrs)
|
||||
this.populateInitialStylingAttrs(attrs);
|
||||
const attrArray = !attrs.some(attr => attr instanceof o.WrappedNodeExpr) ?
|
||||
getConstantLiteralFromArray(constantPool, attrs) :
|
||||
o.literalArr(attrs);
|
||||
return [attrArray];
|
||||
}
|
||||
calls: [{
|
||||
sourceSpan,
|
||||
allocateBindingSlots: 0,
|
||||
params: () => {
|
||||
// params => elementHostAttrs(attrs)
|
||||
this.populateInitialStylingAttrs(attrs);
|
||||
const attrArray = !attrs.some(attr => attr instanceof o.WrappedNodeExpr) ?
|
||||
getConstantLiteralFromArray(constantPool, attrs) :
|
||||
o.literalArr(attrs);
|
||||
return [attrArray];
|
||||
}
|
||||
}]
|
||||
};
|
||||
}
|
||||
return null;
|
||||
|
@ -344,14 +350,16 @@ export class StylingBuilder {
|
|||
}
|
||||
|
||||
return {
|
||||
sourceSpan: stylingInput.sourceSpan,
|
||||
reference,
|
||||
allocateBindingSlots: totalBindingSlotsRequired,
|
||||
supportsInterpolation: isClassBased,
|
||||
params: (convertFn: (value: any) => o.Expression | o.Expression[]) => {
|
||||
const convertResult = convertFn(mapValue);
|
||||
return Array.isArray(convertResult) ? convertResult : [convertResult];
|
||||
}
|
||||
calls: [{
|
||||
supportsInterpolation: isClassBased,
|
||||
sourceSpan: stylingInput.sourceSpan,
|
||||
allocateBindingSlots: totalBindingSlotsRequired,
|
||||
params: (convertFn: (value: any) => o.Expression | o.Expression[]) => {
|
||||
const convertResult = convertFn(mapValue);
|
||||
return Array.isArray(convertResult) ? convertResult : [convertResult];
|
||||
}
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -360,25 +368,27 @@ export class StylingBuilder {
|
|||
allowUnits: boolean, valueConverter: ValueConverter,
|
||||
getInterpolationExpressionFn?: (value: Interpolation) => o.ExternalReference):
|
||||
StylingInstruction[] {
|
||||
let totalBindingSlotsRequired = 0;
|
||||
return inputs.map(input => {
|
||||
const value = input.value.visit(valueConverter);
|
||||
const instructions: StylingInstruction[] = [];
|
||||
|
||||
// each styling binding value is stored in the LView
|
||||
let totalBindingSlotsRequired = 1;
|
||||
inputs.forEach(input => {
|
||||
const previousInstruction: StylingInstruction|undefined =
|
||||
instructions[instructions.length - 1];
|
||||
const value = input.value.visit(valueConverter);
|
||||
let referenceForCall = reference;
|
||||
let totalBindingSlotsRequired = 1; // each styling binding value is stored in the LView
|
||||
|
||||
if (value instanceof Interpolation) {
|
||||
totalBindingSlotsRequired += value.expressions.length;
|
||||
|
||||
if (getInterpolationExpressionFn) {
|
||||
reference = getInterpolationExpressionFn(value);
|
||||
referenceForCall = getInterpolationExpressionFn(value);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
const call = {
|
||||
sourceSpan: input.sourceSpan,
|
||||
allocateBindingSlots: totalBindingSlotsRequired,
|
||||
supportsInterpolation: !!getInterpolationExpressionFn,
|
||||
allocateBindingSlots: totalBindingSlotsRequired, reference,
|
||||
params: (convertFn: (value: any) => o.Expression | o.Expression[]) => {
|
||||
// params => stylingProp(propName, value)
|
||||
const params: o.Expression[] = [];
|
||||
|
@ -398,7 +408,20 @@ export class StylingBuilder {
|
|||
return params;
|
||||
}
|
||||
};
|
||||
|
||||
// If we ended up generating a call to the same instruction as the previous styling property
|
||||
// we can chain the calls together safely to save some bytes, otherwise we have to generate
|
||||
// a separate instruction call. This is primarily a concern with interpolation instructions
|
||||
// where we may start off with one `reference`, but end up using another based on the
|
||||
// number of interpolations.
|
||||
if (previousInstruction && previousInstruction.reference === referenceForCall) {
|
||||
previousInstruction.calls.push(call);
|
||||
} else {
|
||||
instructions.push({reference: referenceForCall, calls: [call]});
|
||||
}
|
||||
});
|
||||
|
||||
return instructions;
|
||||
}
|
||||
|
||||
private _buildClassInputs(valueConverter: ValueConverter): StylingInstruction[] {
|
||||
|
@ -420,10 +443,12 @@ export class StylingBuilder {
|
|||
|
||||
private _buildSanitizerFn(): StylingInstruction {
|
||||
return {
|
||||
sourceSpan: this._firstStylingInput ? this._firstStylingInput.sourceSpan : null,
|
||||
reference: R3.styleSanitizer,
|
||||
allocateBindingSlots: 0,
|
||||
params: () => [o.importExpr(R3.defaultStyleSanitizer)]
|
||||
calls: [{
|
||||
sourceSpan: this._firstStylingInput ? this._firstStylingInput.sourceSpan : null,
|
||||
allocateBindingSlots: 0,
|
||||
params: () => [o.importExpr(R3.defaultStyleSanitizer)]
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -476,20 +501,6 @@ function getConstantLiteralFromArray(
|
|||
return values.length ? constantPool.getConstLiteral(o.literalArr(values), true) : o.NULL_EXPR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple helper function that adds a parameter or does nothing at all depending on the provided
|
||||
* predicate and totalExpectedArgs values
|
||||
*/
|
||||
function addParam(
|
||||
params: o.Expression[], predicate: any, value: o.Expression | null, argNumber: number,
|
||||
totalExpectedArgs: number) {
|
||||
if (predicate && value) {
|
||||
params.push(value);
|
||||
} else if (argNumber < totalExpectedArgs) {
|
||||
params.push(o.NULL_EXPR);
|
||||
}
|
||||
}
|
||||
|
||||
export function parseProperty(name: string):
|
||||
{property: string, unit: string, hasOverrideFlag: boolean} {
|
||||
let hasOverrideFlag = false;
|
||||
|
|
|
@ -708,8 +708,7 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
|||
const limit = stylingInstructions.length - 1;
|
||||
for (let i = 0; i <= limit; i++) {
|
||||
const instruction = stylingInstructions[i];
|
||||
this._bindingSlots += instruction.allocateBindingSlots;
|
||||
this.processStylingInstruction(elementIndex, instruction, false);
|
||||
this._bindingSlots += this.processStylingUpdateInstruction(elementIndex, instruction);
|
||||
}
|
||||
|
||||
// the reason why `undefined` is used is because the renderer understands this as a
|
||||
|
@ -1071,25 +1070,30 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
|||
});
|
||||
}
|
||||
|
||||
private processStylingInstruction(
|
||||
elementIndex: number, instruction: StylingInstruction|null, createMode: boolean) {
|
||||
private processStylingUpdateInstruction(
|
||||
elementIndex: number, instruction: StylingInstruction|null) {
|
||||
let allocateBindingSlots = 0;
|
||||
if (instruction) {
|
||||
if (createMode) {
|
||||
this.creationInstruction(instruction.sourceSpan, instruction.reference, () => {
|
||||
return instruction.params(value => this.convertPropertyBinding(value)) as o.Expression[];
|
||||
});
|
||||
} else {
|
||||
this.updateInstructionWithAdvance(
|
||||
elementIndex, instruction.sourceSpan, instruction.reference, () => {
|
||||
return instruction
|
||||
.params(value => {
|
||||
return (instruction.supportsInterpolation && value instanceof Interpolation) ?
|
||||
const calls: ChainableBindingInstruction[] = [];
|
||||
|
||||
instruction.calls.forEach(call => {
|
||||
allocateBindingSlots += call.allocateBindingSlots;
|
||||
calls.push({
|
||||
sourceSpan: call.sourceSpan,
|
||||
value: () => {
|
||||
return call
|
||||
.params(
|
||||
value => (call.supportsInterpolation && value instanceof Interpolation) ?
|
||||
this.getUpdateInstructionArguments(value) :
|
||||
this.convertPropertyBinding(value);
|
||||
}) as o.Expression[];
|
||||
});
|
||||
}
|
||||
this.convertPropertyBinding(value)) as o.Expression[];
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
this.updateInstructionChainWithAdvance(elementIndex, instruction.reference, calls);
|
||||
}
|
||||
|
||||
return allocateBindingSlots;
|
||||
}
|
||||
|
||||
private creationInstruction(
|
||||
|
@ -1127,8 +1131,13 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
|||
|
||||
this._updateCodeFns.push(() => {
|
||||
const calls = bindings.map(property => {
|
||||
const fnParams = [property.value(), ...(property.params || [])];
|
||||
const value = property.value();
|
||||
const fnParams = Array.isArray(value) ? value : [value];
|
||||
if (property.params) {
|
||||
fnParams.push(...property.params);
|
||||
}
|
||||
if (property.name) {
|
||||
// We want the property name to always be the first function parameter.
|
||||
fnParams.unshift(o.literal(property.name));
|
||||
}
|
||||
return fnParams;
|
||||
|
@ -2045,7 +2054,7 @@ function hasTextChildrenOnly(children: t.Node[]): boolean {
|
|||
interface ChainableBindingInstruction {
|
||||
name?: string;
|
||||
sourceSpan: ParseSourceSpan|null;
|
||||
value: () => o.Expression;
|
||||
value: () => o.Expression | o.Expression[];
|
||||
params?: any[];
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import {SafeValue} from '../../sanitization/bypass';
|
||||
import {StyleSanitizeFn} from '../../sanitization/style_sanitizer';
|
||||
import {throwErrorIfNoChangesMode} from '../errors';
|
||||
import {setInputsForProperty} from '../instructions/shared';
|
||||
import {TsickleIssue1009, setInputsForProperty} from '../instructions/shared';
|
||||
import {AttributeMarker, TAttributes, TNode, TNodeFlags, TNodeType} from '../interfaces/node';
|
||||
import {RElement} from '../interfaces/renderer';
|
||||
import {StylingMapArray, StylingMapArrayIndex, TStylingContext} from '../interfaces/styling';
|
||||
|
@ -77,8 +77,10 @@ export function ɵɵstyleSanitizer(sanitizer: StyleSanitizeFn | null): void {
|
|||
* @codeGenApi
|
||||
*/
|
||||
export function ɵɵstyleProp(
|
||||
prop: string, value: string | number | SafeValue | null, suffix?: string | null): void {
|
||||
prop: string, value: string | number | SafeValue | null,
|
||||
suffix?: string | null): TsickleIssue1009 {
|
||||
stylePropInternal(getSelectedIndex(), prop, value, suffix);
|
||||
return ɵɵstyleProp;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -133,7 +135,7 @@ export function stylePropInternal(
|
|||
*
|
||||
* @codeGenApi
|
||||
*/
|
||||
export function ɵɵclassProp(className: string, value: boolean | null): void {
|
||||
export function ɵɵclassProp(className: string, value: boolean | null): TsickleIssue1009 {
|
||||
// if a value is interpolated then it may render a `NO_CHANGE` value.
|
||||
// in this case we do not need to do anything, but the binding index
|
||||
// still needs to be incremented because all styling binding values
|
||||
|
@ -159,6 +161,7 @@ export function ɵɵclassProp(className: string, value: boolean | null): void {
|
|||
ngDevMode.classPropCacheMiss++;
|
||||
}
|
||||
}
|
||||
return ɵɵclassProp;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -724,7 +724,7 @@ export declare function ɵɵclassMapInterpolate8(prefix: string, v0: any, i0: st
|
|||
|
||||
export declare function ɵɵclassMapInterpolateV(values: any[]): void;
|
||||
|
||||
export declare function ɵɵclassProp(className: string, value: boolean | null): void;
|
||||
export declare function ɵɵclassProp(className: string, value: boolean | null): TsickleIssue1009;
|
||||
|
||||
export declare type ɵɵComponentDefWithMeta<T, Selector extends String, ExportAs extends string[], InputMap extends {
|
||||
[key: string]: string;
|
||||
|
@ -1033,7 +1033,7 @@ export declare function ɵɵstyleMap(styles: {
|
|||
[styleName: string]: any;
|
||||
} | NO_CHANGE | null): void;
|
||||
|
||||
export declare function ɵɵstyleProp(prop: string, value: string | number | SafeValue | null, suffix?: string | null): void;
|
||||
export declare function ɵɵstyleProp(prop: string, value: string | number | SafeValue | null, suffix?: string | null): TsickleIssue1009;
|
||||
|
||||
export declare function ɵɵstylePropInterpolate1(prop: string, prefix: string, v0: any, suffix: string, valueSuffix?: string | null): TsickleIssue1009;
|
||||
|
||||
|
|
Loading…
Reference in New Issue