perf(compiler): introduce direct rendering
This allows to attach / detach embedded views and projected nodes in a faster way.
This commit is contained in:
parent
d708a8859c
commit
9c23884da4
|
@ -156,13 +156,7 @@ class ViewBuilderVisitor implements TemplateAstVisitor {
|
|||
var parentRenderNode = this._getParentRenderNode(parent);
|
||||
if (parentRenderNode !== o.NULL_EXPR) {
|
||||
this.view.createMethod.addStmt(
|
||||
ViewProperties.renderer
|
||||
.callMethod(
|
||||
'projectNodes',
|
||||
[
|
||||
parentRenderNode,
|
||||
o.THIS_EXPR.callMethod('projectedNodes', [o.literal(ast.index)])
|
||||
])
|
||||
o.THIS_EXPR.callMethod('projectNodes', [parentRenderNode, o.literal(ast.index)])
|
||||
.toStmt());
|
||||
} else if (this._isRootNode(parent)) {
|
||||
if (this.view.viewType !== ViewType.COMPONENT) {
|
||||
|
@ -533,13 +527,17 @@ function generateCreateMethod(view: CompileView): o.Statement[] {
|
|||
} else {
|
||||
resultExpr = o.NULL_EXPR;
|
||||
}
|
||||
let allNodesExpr: o.Expression =
|
||||
ViewProperties.renderer.prop('directRenderer')
|
||||
.conditional(o.NULL_EXPR, o.literalArr(view.nodes.map(node => node.renderNode)));
|
||||
|
||||
return parentRenderNodeStmts.concat(view.createMethod.finish(), [
|
||||
o.THIS_EXPR
|
||||
.callMethod(
|
||||
'init',
|
||||
[
|
||||
view.lastRenderNode,
|
||||
o.literalArr(view.nodes.map(node => node.renderNode)),
|
||||
allNodesExpr,
|
||||
view.disposables.length ? o.literalArr(view.disposables) : o.NULL_EXPR,
|
||||
])
|
||||
.toStmt(),
|
||||
|
|
|
@ -75,7 +75,9 @@ export var __core_private__: {
|
|||
UNINITIALIZED: typeof change_detection_util.UNINITIALIZED,
|
||||
ValueUnwrapper: typeof change_detection_util.ValueUnwrapper,
|
||||
_ValueUnwrapper?: change_detection_util.ValueUnwrapper,
|
||||
RenderDebugInfo: typeof api.RenderDebugInfo, _RenderDebugInfo?: api.RenderDebugInfo,
|
||||
RenderDebugInfo: typeof api.RenderDebugInfo,
|
||||
_RenderDebugInfo?: api.RenderDebugInfo,
|
||||
_DirectRenderer?: api.DirectRenderer,
|
||||
TemplateRef_: typeof template_ref.TemplateRef_, _TemplateRef_?: template_ref.TemplateRef_<any>,
|
||||
ReflectionCapabilities: typeof reflection_capabilities.ReflectionCapabilities,
|
||||
_ReflectionCapabilities?: reflection_capabilities.ReflectionCapabilities,
|
||||
|
|
|
@ -89,6 +89,7 @@ export class DebugDomRenderer implements Renderer {
|
|||
}
|
||||
|
||||
destroyView(hostElement: any, viewAllNodes: any[]) {
|
||||
viewAllNodes = viewAllNodes || [];
|
||||
viewAllNodes.forEach((node) => { removeDebugNodeFromIndex(getDebugNode(node)); });
|
||||
this._delegate.destroyView(hostElement, viewAllNodes);
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ import {Injector, THROW_IF_NOT_FOUND} from '../di/injector';
|
|||
import {ListWrapper} from '../facade/collection';
|
||||
import {isPresent} from '../facade/lang';
|
||||
import {WtfScopeFn, wtfCreateScope, wtfLeave} from '../profile/profile';
|
||||
import {RenderComponentType, RenderDebugInfo, Renderer} from '../render/api';
|
||||
import {DirectRenderer, RenderComponentType, RenderDebugInfo, Renderer} from '../render/api';
|
||||
|
||||
import {AnimationViewContext} from './animation_view_context';
|
||||
import {ComponentRef} from './component_factory';
|
||||
|
@ -51,6 +51,7 @@ export abstract class AppView<T> {
|
|||
private _hostInjector: Injector;
|
||||
private _hostProjectableNodes: any[][];
|
||||
private _animationContext: AnimationViewContext;
|
||||
private _directRenderer: DirectRenderer;
|
||||
|
||||
public context: T;
|
||||
|
||||
|
@ -64,6 +65,7 @@ export abstract class AppView<T> {
|
|||
} else {
|
||||
this.renderer = parentView.renderer;
|
||||
}
|
||||
this._directRenderer = (this.renderer as any).directRenderer;
|
||||
}
|
||||
|
||||
get animationContext(): AnimationViewContext {
|
||||
|
@ -136,7 +138,7 @@ export abstract class AppView<T> {
|
|||
|
||||
detachAndDestroy() {
|
||||
if (this._hasExternalHostElement) {
|
||||
this.renderer.detachView(this.flatRootNodes);
|
||||
this.detach();
|
||||
} else if (isPresent(this.viewContainerElement)) {
|
||||
this.viewContainerElement.detachView(this.viewContainerElement.nestedViews.indexOf(this));
|
||||
}
|
||||
|
@ -179,13 +181,34 @@ export abstract class AppView<T> {
|
|||
detach(): void {
|
||||
this.detachInternal();
|
||||
if (this._animationContext) {
|
||||
this._animationContext.onAllActiveAnimationsDone(
|
||||
() => this.renderer.detachView(this.flatRootNodes));
|
||||
this._animationContext.onAllActiveAnimationsDone(() => this._renderDetach());
|
||||
} else {
|
||||
this._renderDetach();
|
||||
}
|
||||
}
|
||||
|
||||
private _renderDetach() {
|
||||
if (this._directRenderer) {
|
||||
this.visitRootNodesInternal(this._directRenderer.remove, null);
|
||||
} else {
|
||||
this.renderer.detachView(this.flatRootNodes);
|
||||
}
|
||||
}
|
||||
|
||||
attachAfter(prevNode: any) {
|
||||
if (this._directRenderer) {
|
||||
const nextSibling = this._directRenderer.nextSibling(prevNode);
|
||||
if (nextSibling) {
|
||||
this.visitRootNodesInternal(this._directRenderer.insertBefore, nextSibling);
|
||||
} else {
|
||||
this.visitRootNodesInternal(
|
||||
this._directRenderer.appendChild, this._directRenderer.parentElement(prevNode));
|
||||
}
|
||||
} else {
|
||||
this.renderer.attachViewAfter(prevNode, this.flatRootNodes);
|
||||
}
|
||||
}
|
||||
|
||||
get changeDetectorRef(): ChangeDetectorRef { return this.ref; }
|
||||
|
||||
get flatRootNodes(): any[] {
|
||||
|
@ -194,10 +217,14 @@ export abstract class AppView<T> {
|
|||
return nodes;
|
||||
}
|
||||
|
||||
projectedNodes(ngContentIndex: number): any[] {
|
||||
const nodes: any[] = [];
|
||||
this.visitProjectedNodes(ngContentIndex, addToArray, nodes);
|
||||
return nodes;
|
||||
projectNodes(parentElement: any, ngContentIndex: number) {
|
||||
if (this._directRenderer) {
|
||||
this.visitProjectedNodes(ngContentIndex, this._directRenderer.appendChild, parentElement);
|
||||
} else {
|
||||
const nodes: any[] = [];
|
||||
this.visitProjectedNodes(ngContentIndex, addToArray, nodes);
|
||||
this.renderer.projectNodes(parentElement, nodes);
|
||||
}
|
||||
}
|
||||
|
||||
visitProjectedNodes<C>(ngContentIndex: number, cb: (node: any, ctx: C) => void, c: C): void {
|
||||
|
|
|
@ -90,7 +90,7 @@ export class ViewContainer {
|
|||
refRenderNode = this.nativeElement;
|
||||
}
|
||||
if (isPresent(refRenderNode)) {
|
||||
view.renderer.attachViewAfter(refRenderNode, view.flatRootNodes);
|
||||
view.attachAfter(refRenderNode);
|
||||
}
|
||||
view.markContentChildAsMoved(this);
|
||||
}
|
||||
|
@ -118,7 +118,7 @@ export class ViewContainer {
|
|||
refRenderNode = this.nativeElement;
|
||||
}
|
||||
if (isPresent(refRenderNode)) {
|
||||
view.renderer.attachViewAfter(refRenderNode, view.flatRootNodes);
|
||||
view.attachAfter(refRenderNode);
|
||||
}
|
||||
view.addToContentChildren(this);
|
||||
}
|
||||
|
|
|
@ -33,6 +33,14 @@ export abstract class RenderDebugInfo {
|
|||
get source(): string { return unimplemented(); }
|
||||
}
|
||||
|
||||
export interface DirectRenderer {
|
||||
remove(node: any): void;
|
||||
appendChild(node: any, parent: any): void;
|
||||
insertBefore(node: any, refNode: any): void;
|
||||
nextSibling(node: any): any;
|
||||
parentElement(node: any): any;
|
||||
}
|
||||
|
||||
/**
|
||||
* @experimental
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,144 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Component, Injectable, RenderComponentType, Renderer, RootRenderer} from '@angular/core';
|
||||
import {DebugDomRenderer} from '@angular/core/src/debug/debug_renderer';
|
||||
import {DirectRenderer} from '@angular/core/src/render/api';
|
||||
import {TestBed, inject} from '@angular/core/testing';
|
||||
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||
import {DIRECT_DOM_RENDERER, DomRootRenderer} from '@angular/platform-browser/src/dom/dom_renderer';
|
||||
import {expect} from '@angular/platform-browser/testing/matchers';
|
||||
|
||||
let directRenderer: any;
|
||||
let destroyViewLogs: any[];
|
||||
|
||||
export function main() {
|
||||
// Don't run on server...
|
||||
if (!getDOM().supportsDOMEvents()) return;
|
||||
describe('direct dom integration tests', function() {
|
||||
|
||||
beforeEach(() => {
|
||||
directRenderer = DIRECT_DOM_RENDERER;
|
||||
destroyViewLogs = [];
|
||||
spyOn(directRenderer, 'remove').and.callThrough();
|
||||
spyOn(directRenderer, 'appendChild').and.callThrough();
|
||||
spyOn(directRenderer, 'insertBefore').and.callThrough();
|
||||
|
||||
TestBed.configureTestingModule(
|
||||
{providers: [{provide: RootRenderer, useClass: DirectRootRenderer}]});
|
||||
});
|
||||
|
||||
it('should attach views as last nodes in a parent', () => {
|
||||
@Component({template: '<div *ngIf="true">hello</div>'})
|
||||
class MyComp {
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [MyComp]});
|
||||
|
||||
const fixture = TestBed.createComponent(MyComp);
|
||||
|
||||
directRenderer.insertBefore.calls.reset();
|
||||
directRenderer.appendChild.calls.reset();
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.nativeElement).toHaveText('hello');
|
||||
expect(directRenderer.insertBefore).not.toHaveBeenCalled();
|
||||
expect(directRenderer.appendChild).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should attach views as non last nodes in a parent', () => {
|
||||
@Component({template: '<div *ngIf="true">hello</div>after'})
|
||||
class MyComp {
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [MyComp]});
|
||||
|
||||
const fixture = TestBed.createComponent(MyComp);
|
||||
|
||||
directRenderer.insertBefore.calls.reset();
|
||||
directRenderer.appendChild.calls.reset();
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.nativeElement).toHaveText('helloafter');
|
||||
expect(directRenderer.insertBefore).toHaveBeenCalled();
|
||||
expect(directRenderer.appendChild).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should detach views', () => {
|
||||
@Component({template: '<div *ngIf="shown">hello</div>'})
|
||||
class MyComp {
|
||||
shown = true;
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [MyComp]});
|
||||
const fixture = TestBed.createComponent(MyComp);
|
||||
fixture.detectChanges();
|
||||
|
||||
directRenderer.remove.calls.reset();
|
||||
|
||||
fixture.componentInstance.shown = false;
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.nativeElement).toHaveText('');
|
||||
expect(directRenderer.remove).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should pass null as all nodes to destroyView', () => {
|
||||
@Component({template: '<div *ngIf="shown">hello</div>'})
|
||||
class MyComp {
|
||||
shown = true;
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [MyComp]});
|
||||
const fixture = TestBed.createComponent(MyComp);
|
||||
fixture.detectChanges();
|
||||
|
||||
destroyViewLogs.splice(0, destroyViewLogs.length);
|
||||
|
||||
fixture.componentInstance.shown = false;
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(destroyViewLogs).toEqual([[null, null]]);
|
||||
});
|
||||
|
||||
it('should project nodes', () => {
|
||||
@Component({template: '<child>hello</child>'})
|
||||
class Parent {
|
||||
}
|
||||
|
||||
@Component({selector: 'child', template: '(<ng-content></ng-content>)'})
|
||||
class Child {
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [Parent, Child]});
|
||||
const fixture = TestBed.createComponent(Parent);
|
||||
|
||||
expect(fixture.nativeElement).toHaveText('(hello)');
|
||||
const childHostEl = fixture.nativeElement.children[0];
|
||||
const projectedNode = childHostEl.childNodes[1];
|
||||
expect(directRenderer.appendChild).toHaveBeenCalledWith(projectedNode, childHostEl);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
class DirectRootRenderer implements RootRenderer {
|
||||
constructor(private _delegate: DomRootRenderer) {}
|
||||
renderComponent(componentType: RenderComponentType): Renderer {
|
||||
const renderer = new DebugDomRenderer(this._delegate.renderComponent(componentType));
|
||||
(renderer as any).directRenderer = directRenderer;
|
||||
const originalDestroyView = renderer.destroyView;
|
||||
renderer.destroyView = function(...args: any[]) {
|
||||
destroyViewLogs.push(args);
|
||||
return originalDestroyView.apply(this, args);
|
||||
};
|
||||
return renderer;
|
||||
}
|
||||
}
|
|
@ -9,7 +9,7 @@
|
|||
import {APP_ID, Inject, Injectable, RenderComponentType, Renderer, RootRenderer, ViewEncapsulation} from '@angular/core';
|
||||
|
||||
import {isBlank, isPresent, stringify} from '../facade/lang';
|
||||
import {AnimationKeyframe, AnimationPlayer, AnimationStyles, RenderDebugInfo} from '../private_import_core';
|
||||
import {AnimationKeyframe, AnimationPlayer, AnimationStyles, DirectRenderer, RenderDebugInfo} from '../private_import_core';
|
||||
|
||||
import {AnimationDriver} from './animation_driver';
|
||||
import {DOCUMENT} from './dom_tokens';
|
||||
|
@ -54,11 +54,21 @@ export class DomRootRenderer_ extends DomRootRenderer {
|
|||
}
|
||||
}
|
||||
|
||||
export const DIRECT_DOM_RENDERER: DirectRenderer = {
|
||||
remove(node: Text | Comment | Element) { node.remove();},
|
||||
appendChild(node: Node, parent: Element) { parent.appendChild(node);},
|
||||
insertBefore(node: Node, refNode: Node) { refNode.parentElement.insertBefore(node, refNode);},
|
||||
nextSibling(node: Node) { return node.nextSibling;},
|
||||
parentElement(node: Node): Element{return node.parentElement;}
|
||||
};
|
||||
|
||||
export class DomRenderer implements Renderer {
|
||||
private _contentAttr: string;
|
||||
private _hostAttr: string;
|
||||
private _styles: string[];
|
||||
|
||||
directRenderer: DirectRenderer = DIRECT_DOM_RENDERER;
|
||||
|
||||
constructor(
|
||||
private _rootRenderer: DomRootRenderer, private componentProto: RenderComponentType,
|
||||
private _animationDriver: AnimationDriver, styleShimId: string) {
|
||||
|
|
|
@ -10,6 +10,7 @@ import {__core_private__ as r} from '@angular/core';
|
|||
|
||||
export type RenderDebugInfo = typeof r._RenderDebugInfo;
|
||||
export var RenderDebugInfo: typeof r.RenderDebugInfo = r.RenderDebugInfo;
|
||||
export type DirectRenderer = typeof r._DirectRenderer;
|
||||
|
||||
export var ReflectionCapabilities: typeof r.ReflectionCapabilities = r.ReflectionCapabilities;
|
||||
|
||||
|
|
Loading…
Reference in New Issue