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) {
|
if (rf & 2) {
|
||||||
$r3$.ɵɵstyleSanitizer($r3$.ɵɵdefaultStyleSanitizer);
|
$r3$.ɵɵstyleSanitizer($r3$.ɵɵdefaultStyleSanitizer);
|
||||||
$r3$.ɵɵstyleMap($ctx$.myStyleExp);
|
$r3$.ɵɵstyleMap($ctx$.myStyleExp);
|
||||||
$r3$.ɵɵstyleProp("width", $ctx$.myWidth);
|
$r3$.ɵɵstyleProp("width", $ctx$.myWidth)("height", $ctx$.myHeight);
|
||||||
$r3$.ɵɵstyleProp("height", $ctx$.myHeight);
|
|
||||||
$r3$.ɵɵattribute("style", "border-width: 10px", $r3$.ɵɵsanitizeStyle);
|
$r3$.ɵɵattribute("style", "border-width: 10px", $r3$.ɵɵsanitizeStyle);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -706,8 +705,7 @@ describe('compiler compliance: styling', () => {
|
||||||
}
|
}
|
||||||
if (rf & 2) {
|
if (rf & 2) {
|
||||||
$r3$.ɵɵclassMap($ctx$.myClassExp);
|
$r3$.ɵɵclassMap($ctx$.myClassExp);
|
||||||
$r3$.ɵɵclassProp("apple", $ctx$.yesToApple);
|
$r3$.ɵɵclassProp("apple", $ctx$.yesToApple)("orange", $ctx$.yesToOrange);
|
||||||
$r3$.ɵɵclassProp("orange", $ctx$.yesToOrange);
|
|
||||||
$r3$.ɵɵattribute("class", "banana");
|
$r3$.ɵɵattribute("class", "banana");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -916,8 +914,7 @@ describe('compiler compliance: styling', () => {
|
||||||
$r3$.ɵɵstyleSanitizer($r3$.ɵɵdefaultStyleSanitizer);
|
$r3$.ɵɵstyleSanitizer($r3$.ɵɵdefaultStyleSanitizer);
|
||||||
$r3$.ɵɵstyleMap($r3$.ɵɵpipeBind2(1, 8, $ctx$.myStyleExp, 1000));
|
$r3$.ɵɵstyleMap($r3$.ɵɵpipeBind2(1, 8, $ctx$.myStyleExp, 1000));
|
||||||
$r3$.ɵɵclassMap($r3$.ɵɵpureFunction0(20, _c0));
|
$r3$.ɵɵclassMap($r3$.ɵɵpureFunction0(20, _c0));
|
||||||
$r3$.ɵɵstyleProp("bar", $r3$.ɵɵpipeBind2(2, 11, $ctx$.barExp, 3000));
|
$r3$.ɵɵstyleProp("bar", $r3$.ɵɵpipeBind2(2, 11, $ctx$.barExp, 3000))("baz", $r3$.ɵɵpipeBind2(3, 14, $ctx$.bazExp, 4000));
|
||||||
$r3$.ɵɵstyleProp("baz", $r3$.ɵɵpipeBind2(3, 14, $ctx$.bazExp, 4000));
|
|
||||||
$r3$.ɵɵclassProp("foo", $r3$.ɵɵpipeBind2(4, 17, $ctx$.fooExp, 2000));
|
$r3$.ɵɵclassProp("foo", $r3$.ɵɵpipeBind2(4, 17, $ctx$.fooExp, 2000));
|
||||||
$r3$.ɵɵadvance(5);
|
$r3$.ɵɵadvance(5);
|
||||||
$r3$.ɵɵtextInterpolate1(" ", $ctx$.item, "");
|
$r3$.ɵɵtextInterpolate1(" ", $ctx$.item, "");
|
||||||
|
@ -1081,10 +1078,8 @@ describe('compiler compliance: styling', () => {
|
||||||
$r3$.ɵɵstyleSanitizer($r3$.ɵɵdefaultStyleSanitizer);
|
$r3$.ɵɵstyleSanitizer($r3$.ɵɵdefaultStyleSanitizer);
|
||||||
$r3$.ɵɵstyleMap(ctx.myStyle);
|
$r3$.ɵɵstyleMap(ctx.myStyle);
|
||||||
$r3$.ɵɵclassMap(ctx.myClasses);
|
$r3$.ɵɵclassMap(ctx.myClasses);
|
||||||
$r3$.ɵɵstyleProp("height", ctx.myHeightProp, "pt");
|
$r3$.ɵɵstyleProp("height", ctx.myHeightProp, "pt")("width", ctx.myWidthProp);
|
||||||
$r3$.ɵɵstyleProp("width", ctx.myWidthProp);
|
$r3$.ɵɵclassProp("bar", ctx.myBarClass)("foo", ctx.myFooClass);
|
||||||
$r3$.ɵɵclassProp("bar", ctx.myBarClass);
|
|
||||||
$r3$.ɵɵclassProp("foo", ctx.myFooClass);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
decls: 0,
|
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', () => {
|
it('should count only non-style and non-class host bindings on Components', () => {
|
||||||
const files = {
|
const files = {
|
||||||
app: {
|
app: {
|
||||||
|
|
|
@ -28,7 +28,7 @@ import {Render3ParseResult} from '../r3_template_transform';
|
||||||
import {prepareSyntheticListenerFunctionName, prepareSyntheticPropertyName, typeWithParameters} from '../util';
|
import {prepareSyntheticListenerFunctionName, prepareSyntheticPropertyName, typeWithParameters} from '../util';
|
||||||
|
|
||||||
import {R3ComponentDef, R3ComponentMetadata, R3DirectiveDef, R3DirectiveMetadata, R3HostMetadata, R3QueryMetadata} from './api';
|
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 {BindingScope, TemplateDefinitionBuilder, ValueConverter, makeBindingParser, prepareEventListenerParameters, renderFlagCheckIfStmt, resolveSanitizationFn} from './template';
|
||||||
import {CONTEXT_NAME, DefinitionMap, RENDER_FLAGS, TEMPORARY_NAME, asLiteral, chainedInstruction, conditionallyCreateMapObjectLiteral, getQueryPredicate, temporaryAllocator} from './util';
|
import {CONTEXT_NAME, DefinitionMap, RENDER_FLAGS, TEMPORARY_NAME, asLiteral, chainedInstruction, conditionallyCreateMapObjectLiteral, getQueryPredicate, temporaryAllocator} from './util';
|
||||||
|
|
||||||
|
@ -652,8 +652,12 @@ function createHostBindingsFunction(
|
||||||
// collected earlier.
|
// collected earlier.
|
||||||
const hostAttrs = convertAttributesToExpressions(hostBindingsMetadata.attributes);
|
const hostAttrs = convertAttributesToExpressions(hostBindingsMetadata.attributes);
|
||||||
const hostInstruction = styleBuilder.buildHostAttrsInstruction(null, hostAttrs, constantPool);
|
const hostInstruction = styleBuilder.buildHostAttrsInstruction(null, hostAttrs, constantPool);
|
||||||
if (hostInstruction) {
|
if (hostInstruction && hostInstruction.calls.length > 0) {
|
||||||
createStatements.push(createStylingStmt(hostInstruction, bindingContext, bindingFn));
|
createStatements.push(
|
||||||
|
chainedInstruction(
|
||||||
|
hostInstruction.reference,
|
||||||
|
hostInstruction.calls.map(call => convertStylingCall(call, bindingContext, bindingFn)))
|
||||||
|
.toStmt());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (styleBuilder.hasBindings) {
|
if (styleBuilder.hasBindings) {
|
||||||
|
@ -661,11 +665,18 @@ function createHostBindingsFunction(
|
||||||
// the update block of a component/directive templateFn/hostBindingsFn so that the bindings
|
// the update block of a component/directive templateFn/hostBindingsFn so that the bindings
|
||||||
// are evaluated and updated for the element.
|
// are evaluated and updated for the element.
|
||||||
styleBuilder.buildUpdateLevelInstructions(getValueConverter()).forEach(instruction => {
|
styleBuilder.buildUpdateLevelInstructions(getValueConverter()).forEach(instruction => {
|
||||||
// we subtract a value of `1` here because the binding slot was already
|
if (instruction.calls.length > 0) {
|
||||||
// allocated at the top of this method when all the input bindings were
|
const calls: o.Expression[][] = [];
|
||||||
// counted.
|
|
||||||
totalHostVarsCount += Math.max(instruction.allocateBindingSlots - 1, 0);
|
instruction.calls.forEach(call => {
|
||||||
updateStatements.push(createStylingStmt(instruction, bindingContext, bindingFn));
|
// 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'));
|
null, implicit, value, 'b', BindingForm.TrySimple, () => error('Unexpected interpolation'));
|
||||||
}
|
}
|
||||||
|
|
||||||
function createStylingStmt(
|
function convertStylingCall(
|
||||||
instruction: StylingInstruction, bindingContext: any, bindingFn: Function): o.Statement {
|
call: StylingInstructionCall, bindingContext: any, bindingFn: Function) {
|
||||||
const params = instruction.params(value => bindingFn(bindingContext, value).currValExpr);
|
return call.params(value => bindingFn(bindingContext, value).currValExpr);
|
||||||
return o.importExpr(instruction.reference, null, instruction.sourceSpan)
|
|
||||||
.callFn(params, instruction.sourceSpan)
|
|
||||||
.toStmt();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getBindingNameAndInstruction(binding: ParsedProperty):
|
function getBindingNameAndInstruction(binding: ParsedProperty):
|
||||||
|
|
|
@ -11,7 +11,6 @@ import {AST, ASTWithSource, BindingPipe, BindingType, Interpolation} from '../..
|
||||||
import * as o from '../../output/output_ast';
|
import * as o from '../../output/output_ast';
|
||||||
import {ParseSourceSpan} from '../../parse_util';
|
import {ParseSourceSpan} from '../../parse_util';
|
||||||
import {isEmptyExpression} from '../../template_parser/template_parser';
|
import {isEmptyExpression} from '../../template_parser/template_parser';
|
||||||
import {error} from '../../util';
|
|
||||||
import * as t from '../r3_ast';
|
import * as t from '../r3_ast';
|
||||||
import {Identifiers as R3} from '../r3_identifiers';
|
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
|
* A styling expression summary that is to be processed by the compiler
|
||||||
*/
|
*/
|
||||||
export interface StylingInstruction {
|
export interface StylingInstruction {
|
||||||
sourceSpan: ParseSourceSpan|null;
|
|
||||||
reference: o.ExternalReference;
|
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;
|
supportsInterpolation?: boolean;
|
||||||
|
allocateBindingSlots: number;
|
||||||
params: ((convertFn: (value: any) => o.Expression | o.Expression[]) => o.Expression[]);
|
params: ((convertFn: (value: any) => o.Expression | o.Expression[]) => o.Expression[]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -280,17 +284,19 @@ export class StylingBuilder {
|
||||||
constantPool: ConstantPool): StylingInstruction|null {
|
constantPool: ConstantPool): StylingInstruction|null {
|
||||||
if (this._directiveExpr && (attrs.length || this._hasInitialValues)) {
|
if (this._directiveExpr && (attrs.length || this._hasInitialValues)) {
|
||||||
return {
|
return {
|
||||||
sourceSpan,
|
|
||||||
reference: R3.elementHostAttrs,
|
reference: R3.elementHostAttrs,
|
||||||
allocateBindingSlots: 0,
|
calls: [{
|
||||||
params: () => {
|
sourceSpan,
|
||||||
// params => elementHostAttrs(attrs)
|
allocateBindingSlots: 0,
|
||||||
this.populateInitialStylingAttrs(attrs);
|
params: () => {
|
||||||
const attrArray = !attrs.some(attr => attr instanceof o.WrappedNodeExpr) ?
|
// params => elementHostAttrs(attrs)
|
||||||
getConstantLiteralFromArray(constantPool, attrs) :
|
this.populateInitialStylingAttrs(attrs);
|
||||||
o.literalArr(attrs);
|
const attrArray = !attrs.some(attr => attr instanceof o.WrappedNodeExpr) ?
|
||||||
return [attrArray];
|
getConstantLiteralFromArray(constantPool, attrs) :
|
||||||
}
|
o.literalArr(attrs);
|
||||||
|
return [attrArray];
|
||||||
|
}
|
||||||
|
}]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
@ -344,14 +350,16 @@ export class StylingBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
sourceSpan: stylingInput.sourceSpan,
|
|
||||||
reference,
|
reference,
|
||||||
allocateBindingSlots: totalBindingSlotsRequired,
|
calls: [{
|
||||||
supportsInterpolation: isClassBased,
|
supportsInterpolation: isClassBased,
|
||||||
params: (convertFn: (value: any) => o.Expression | o.Expression[]) => {
|
sourceSpan: stylingInput.sourceSpan,
|
||||||
const convertResult = convertFn(mapValue);
|
allocateBindingSlots: totalBindingSlotsRequired,
|
||||||
return Array.isArray(convertResult) ? convertResult : [convertResult];
|
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,
|
allowUnits: boolean, valueConverter: ValueConverter,
|
||||||
getInterpolationExpressionFn?: (value: Interpolation) => o.ExternalReference):
|
getInterpolationExpressionFn?: (value: Interpolation) => o.ExternalReference):
|
||||||
StylingInstruction[] {
|
StylingInstruction[] {
|
||||||
let totalBindingSlotsRequired = 0;
|
const instructions: StylingInstruction[] = [];
|
||||||
return inputs.map(input => {
|
|
||||||
const value = input.value.visit(valueConverter);
|
|
||||||
|
|
||||||
// each styling binding value is stored in the LView
|
inputs.forEach(input => {
|
||||||
let totalBindingSlotsRequired = 1;
|
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) {
|
if (value instanceof Interpolation) {
|
||||||
totalBindingSlotsRequired += value.expressions.length;
|
totalBindingSlotsRequired += value.expressions.length;
|
||||||
|
|
||||||
if (getInterpolationExpressionFn) {
|
if (getInterpolationExpressionFn) {
|
||||||
reference = getInterpolationExpressionFn(value);
|
referenceForCall = getInterpolationExpressionFn(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
const call = {
|
||||||
sourceSpan: input.sourceSpan,
|
sourceSpan: input.sourceSpan,
|
||||||
|
allocateBindingSlots: totalBindingSlotsRequired,
|
||||||
supportsInterpolation: !!getInterpolationExpressionFn,
|
supportsInterpolation: !!getInterpolationExpressionFn,
|
||||||
allocateBindingSlots: totalBindingSlotsRequired, reference,
|
|
||||||
params: (convertFn: (value: any) => o.Expression | o.Expression[]) => {
|
params: (convertFn: (value: any) => o.Expression | o.Expression[]) => {
|
||||||
// params => stylingProp(propName, value)
|
// params => stylingProp(propName, value)
|
||||||
const params: o.Expression[] = [];
|
const params: o.Expression[] = [];
|
||||||
|
@ -398,7 +408,20 @@ export class StylingBuilder {
|
||||||
return params;
|
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[] {
|
private _buildClassInputs(valueConverter: ValueConverter): StylingInstruction[] {
|
||||||
|
@ -420,10 +443,12 @@ export class StylingBuilder {
|
||||||
|
|
||||||
private _buildSanitizerFn(): StylingInstruction {
|
private _buildSanitizerFn(): StylingInstruction {
|
||||||
return {
|
return {
|
||||||
sourceSpan: this._firstStylingInput ? this._firstStylingInput.sourceSpan : null,
|
|
||||||
reference: R3.styleSanitizer,
|
reference: R3.styleSanitizer,
|
||||||
allocateBindingSlots: 0,
|
calls: [{
|
||||||
params: () => [o.importExpr(R3.defaultStyleSanitizer)]
|
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;
|
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):
|
export function parseProperty(name: string):
|
||||||
{property: string, unit: string, hasOverrideFlag: boolean} {
|
{property: string, unit: string, hasOverrideFlag: boolean} {
|
||||||
let hasOverrideFlag = false;
|
let hasOverrideFlag = false;
|
||||||
|
|
|
@ -708,8 +708,7 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
||||||
const limit = stylingInstructions.length - 1;
|
const limit = stylingInstructions.length - 1;
|
||||||
for (let i = 0; i <= limit; i++) {
|
for (let i = 0; i <= limit; i++) {
|
||||||
const instruction = stylingInstructions[i];
|
const instruction = stylingInstructions[i];
|
||||||
this._bindingSlots += instruction.allocateBindingSlots;
|
this._bindingSlots += this.processStylingUpdateInstruction(elementIndex, instruction);
|
||||||
this.processStylingInstruction(elementIndex, instruction, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// the reason why `undefined` is used is because the renderer understands this as a
|
// 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(
|
private processStylingUpdateInstruction(
|
||||||
elementIndex: number, instruction: StylingInstruction|null, createMode: boolean) {
|
elementIndex: number, instruction: StylingInstruction|null) {
|
||||||
|
let allocateBindingSlots = 0;
|
||||||
if (instruction) {
|
if (instruction) {
|
||||||
if (createMode) {
|
const calls: ChainableBindingInstruction[] = [];
|
||||||
this.creationInstruction(instruction.sourceSpan, instruction.reference, () => {
|
|
||||||
return instruction.params(value => this.convertPropertyBinding(value)) as o.Expression[];
|
instruction.calls.forEach(call => {
|
||||||
});
|
allocateBindingSlots += call.allocateBindingSlots;
|
||||||
} else {
|
calls.push({
|
||||||
this.updateInstructionWithAdvance(
|
sourceSpan: call.sourceSpan,
|
||||||
elementIndex, instruction.sourceSpan, instruction.reference, () => {
|
value: () => {
|
||||||
return instruction
|
return call
|
||||||
.params(value => {
|
.params(
|
||||||
return (instruction.supportsInterpolation && value instanceof Interpolation) ?
|
value => (call.supportsInterpolation && value instanceof Interpolation) ?
|
||||||
this.getUpdateInstructionArguments(value) :
|
this.getUpdateInstructionArguments(value) :
|
||||||
this.convertPropertyBinding(value);
|
this.convertPropertyBinding(value)) as o.Expression[];
|
||||||
}) as o.Expression[];
|
}
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
|
|
||||||
|
this.updateInstructionChainWithAdvance(elementIndex, instruction.reference, calls);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return allocateBindingSlots;
|
||||||
}
|
}
|
||||||
|
|
||||||
private creationInstruction(
|
private creationInstruction(
|
||||||
|
@ -1127,8 +1131,13 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
||||||
|
|
||||||
this._updateCodeFns.push(() => {
|
this._updateCodeFns.push(() => {
|
||||||
const calls = bindings.map(property => {
|
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) {
|
if (property.name) {
|
||||||
|
// We want the property name to always be the first function parameter.
|
||||||
fnParams.unshift(o.literal(property.name));
|
fnParams.unshift(o.literal(property.name));
|
||||||
}
|
}
|
||||||
return fnParams;
|
return fnParams;
|
||||||
|
@ -2045,7 +2054,7 @@ function hasTextChildrenOnly(children: t.Node[]): boolean {
|
||||||
interface ChainableBindingInstruction {
|
interface ChainableBindingInstruction {
|
||||||
name?: string;
|
name?: string;
|
||||||
sourceSpan: ParseSourceSpan|null;
|
sourceSpan: ParseSourceSpan|null;
|
||||||
value: () => o.Expression;
|
value: () => o.Expression | o.Expression[];
|
||||||
params?: any[];
|
params?: any[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
import {SafeValue} from '../../sanitization/bypass';
|
import {SafeValue} from '../../sanitization/bypass';
|
||||||
import {StyleSanitizeFn} from '../../sanitization/style_sanitizer';
|
import {StyleSanitizeFn} from '../../sanitization/style_sanitizer';
|
||||||
import {throwErrorIfNoChangesMode} from '../errors';
|
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 {AttributeMarker, TAttributes, TNode, TNodeFlags, TNodeType} from '../interfaces/node';
|
||||||
import {RElement} from '../interfaces/renderer';
|
import {RElement} from '../interfaces/renderer';
|
||||||
import {StylingMapArray, StylingMapArrayIndex, TStylingContext} from '../interfaces/styling';
|
import {StylingMapArray, StylingMapArrayIndex, TStylingContext} from '../interfaces/styling';
|
||||||
|
@ -77,8 +77,10 @@ export function ɵɵstyleSanitizer(sanitizer: StyleSanitizeFn | null): void {
|
||||||
* @codeGenApi
|
* @codeGenApi
|
||||||
*/
|
*/
|
||||||
export function ɵɵstyleProp(
|
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);
|
stylePropInternal(getSelectedIndex(), prop, value, suffix);
|
||||||
|
return ɵɵstyleProp;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -133,7 +135,7 @@ export function stylePropInternal(
|
||||||
*
|
*
|
||||||
* @codeGenApi
|
* @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.
|
// 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
|
// in this case we do not need to do anything, but the binding index
|
||||||
// still needs to be incremented because all styling binding values
|
// 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++;
|
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 ɵɵ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 {
|
export declare type ɵɵComponentDefWithMeta<T, Selector extends String, ExportAs extends string[], InputMap extends {
|
||||||
[key: string]: string;
|
[key: string]: string;
|
||||||
|
@ -1033,7 +1033,7 @@ export declare function ɵɵstyleMap(styles: {
|
||||||
[styleName: string]: any;
|
[styleName: string]: any;
|
||||||
} | NO_CHANGE | null): void;
|
} | 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;
|
export declare function ɵɵstylePropInterpolate1(prop: string, prefix: string, v0: any, suffix: string, valueSuffix?: string | null): TsickleIssue1009;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue