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:
Kristiyan Kostadinov
2019-11-18 20:13:23 +01:00
committed by Alex Rickabaugh
parent f69c6e204a
commit 8a052dc858
6 changed files with 465 additions and 94 deletions

View File

@ -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: {