refactor(ivy): Switch styling to new reconcile algorithm (#34616)

NOTE: This change must be reverted with previous deletes so that it code remains in build-able state.

This change deletes old styling code and replaces it with a simplified styling algorithm.

The mental model for the new algorithm is:
- Create a linked list of styling bindings in the order of priority. All styling bindings ere executed in compiled order and than a linked list of bindings is created in priority order.
- Flush the style bindings at the end of `advance()` instruction. This implies that there are two flush events. One at the end of template `advance` instruction in the template. Second one at the end of `hostBindings` `advance` instruction when processing host bindings (if any).
- Each binding instructions effectively updates the string to represent the string at that location. Because most of the bindings are additive, this is a cheap strategy in most cases. In rare cases the strategy requires removing tokens from the styling up to this point. (We expect that to be rare case)S Because, the bindings are presorted in the order of priority, it is safe to resume the processing of the concatenated string from the last change binding.

PR Close #34616
This commit is contained in:
Miško Hevery
2019-12-17 15:40:37 -08:00
parent b4a711ea9f
commit 5aabe93abe
60 changed files with 2439 additions and 1413 deletions

View File

@ -1032,7 +1032,7 @@ describe('acceptance integration tests', () => {
fixture.componentInstance.value = false;
fixture.detectChanges();
expect(structuralCompEl.getAttribute('class')).toEqual('');
expect(structuralCompEl.getAttribute('class')).toBeFalsy();
});
@Directive({selector: '[DirWithClass]'})
@ -1071,7 +1071,7 @@ describe('acceptance integration tests', () => {
it('should delegate initial styles to a [style] input binding if present on a directive on the same element',
() => {
@Component({template: '<div style="width:100px;height:200px" DirWithStyle></div>'})
@Component({template: '<div style="width: 100px; height: 200px" DirWithStyle></div>'})
class App {
@ViewChild(DirWithStyleDirective)
mockStyleDirective !: DirWithStyleDirective;
@ -1084,8 +1084,8 @@ describe('acceptance integration tests', () => {
const styles = fixture.componentInstance.mockStyleDirective.stylesVal;
// Use `toContain` since Ivy and ViewEngine have some slight differences in formatting.
expect(styles).toContain('width:100px');
expect(styles).toContain('height:200px');
expect(styles).toContain('width: 100px');
expect(styles).toContain('height: 200px');
});
it('should update `[class]` and bindings in the provided directive if the input is matched',
@ -1122,7 +1122,7 @@ describe('acceptance integration tests', () => {
fixture.detectChanges();
expect(fixture.componentInstance.mockStyleDirective.stylesVal)
.toEqual({'width': '200px', 'height': '500px'});
.toEqual({width: '200px', height: '500px'});
});
onlyInIvy('Style binding merging works differently in Ivy')
@ -1177,8 +1177,8 @@ describe('acceptance integration tests', () => {
}
})
class DirWithSingleStylingBindings {
width: null|string = null;
height: null|string = null;
width: string|null|undefined = undefined;
height: string|null|undefined = undefined;
activateXYZClass: boolean = false;
}
@ -1214,8 +1214,8 @@ describe('acceptance integration tests', () => {
expect(target.classList.contains('def')).toBeTruthy();
expect(target.classList.contains('xyz')).toBeTruthy();
dirInstance.width = null;
dirInstance.height = null;
dirInstance.width = undefined;
dirInstance.height = undefined;
fixture.detectChanges();
expect(target.style.getPropertyValue('width')).toEqual('100px');
@ -1230,7 +1230,7 @@ describe('acceptance integration tests', () => {
() => {
@Directive({selector: '[Dir1WithStyle]', host: {'[style.width]': 'width'}})
class Dir1WithStyle {
width: null|string = null;
width: null|string|undefined = undefined;
}
@Directive({
@ -1238,7 +1238,7 @@ describe('acceptance integration tests', () => {
host: {'style': 'width: 111px', '[style.width]': 'width'}
})
class Dir2WithStyle {
width: null|string = null;
width: null|string|undefined = undefined;
}
@Component(
@ -1246,10 +1246,10 @@ describe('acceptance integration tests', () => {
class App {
@ViewChild(Dir1WithStyle) dir1Instance !: Dir1WithStyle;
@ViewChild(Dir2WithStyle) dir2Instance !: Dir2WithStyle;
width: string|null = null;
width: string|null|undefined = undefined;
}
TestBed.configureTestingModule({declarations: [App, Dir1WithStyle, Dir2WithStyle]});
TestBed.configureTestingModule({declarations: [App, Dir2WithStyle, Dir1WithStyle]});
const fixture = TestBed.createComponent(App);
fixture.detectChanges();
const {dir1Instance, dir2Instance} = fixture.componentInstance;
@ -1263,15 +1263,15 @@ describe('acceptance integration tests', () => {
fixture.detectChanges();
expect(target.style.getPropertyValue('width')).toEqual('999px');
fixture.componentInstance.width = null;
fixture.componentInstance.width = undefined;
fixture.detectChanges();
expect(target.style.getPropertyValue('width')).toEqual('222px');
dir1Instance.width = null;
dir1Instance.width = undefined;
fixture.detectChanges();
expect(target.style.getPropertyValue('width')).toEqual('333px');
dir2Instance.width = null;
dir2Instance.width = undefined;
fixture.detectChanges();
expect(target.style.getPropertyValue('width')).toEqual('111px');
@ -1316,7 +1316,7 @@ describe('acceptance integration tests', () => {
}
TestBed.configureTestingModule(
{declarations: [App, Dir1WithStyling, Dir2WithStyling]});
{declarations: [App, Dir2WithStyling, Dir1WithStyling]});
const fixture = TestBed.createComponent(App);
fixture.detectChanges();
const {dir1Instance, dir2Instance} = fixture.componentInstance;
@ -1325,7 +1325,7 @@ describe('acceptance integration tests', () => {
expect(target.style.getPropertyValue('width')).toEqual('111px');
const compInstance = fixture.componentInstance;
compInstance.stylesExp = {width: '999px', height: null};
compInstance.stylesExp = {width: '999px', height: undefined};
compInstance.classesExp = {one: true, two: false};
dir1Instance.stylesExp = {width: '222px'};
dir1Instance.classesExp = {two: true, three: false};