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);
|
var parentRenderNode = this._getParentRenderNode(parent);
|
||||||
if (parentRenderNode !== o.NULL_EXPR) {
|
if (parentRenderNode !== o.NULL_EXPR) {
|
||||||
this.view.createMethod.addStmt(
|
this.view.createMethod.addStmt(
|
||||||
ViewProperties.renderer
|
o.THIS_EXPR.callMethod('projectNodes', [parentRenderNode, o.literal(ast.index)])
|
||||||
.callMethod(
|
|
||||||
'projectNodes',
|
|
||||||
[
|
|
||||||
parentRenderNode,
|
|
||||||
o.THIS_EXPR.callMethod('projectedNodes', [o.literal(ast.index)])
|
|
||||||
])
|
|
||||||
.toStmt());
|
.toStmt());
|
||||||
} else if (this._isRootNode(parent)) {
|
} else if (this._isRootNode(parent)) {
|
||||||
if (this.view.viewType !== ViewType.COMPONENT) {
|
if (this.view.viewType !== ViewType.COMPONENT) {
|
||||||
@ -533,13 +527,17 @@ function generateCreateMethod(view: CompileView): o.Statement[] {
|
|||||||
} else {
|
} else {
|
||||||
resultExpr = o.NULL_EXPR;
|
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(), [
|
return parentRenderNodeStmts.concat(view.createMethod.finish(), [
|
||||||
o.THIS_EXPR
|
o.THIS_EXPR
|
||||||
.callMethod(
|
.callMethod(
|
||||||
'init',
|
'init',
|
||||||
[
|
[
|
||||||
view.lastRenderNode,
|
view.lastRenderNode,
|
||||||
o.literalArr(view.nodes.map(node => node.renderNode)),
|
allNodesExpr,
|
||||||
view.disposables.length ? o.literalArr(view.disposables) : o.NULL_EXPR,
|
view.disposables.length ? o.literalArr(view.disposables) : o.NULL_EXPR,
|
||||||
])
|
])
|
||||||
.toStmt(),
|
.toStmt(),
|
||||||
|
@ -75,7 +75,9 @@ export var __core_private__: {
|
|||||||
UNINITIALIZED: typeof change_detection_util.UNINITIALIZED,
|
UNINITIALIZED: typeof change_detection_util.UNINITIALIZED,
|
||||||
ValueUnwrapper: typeof change_detection_util.ValueUnwrapper,
|
ValueUnwrapper: typeof change_detection_util.ValueUnwrapper,
|
||||||
_ValueUnwrapper?: 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>,
|
TemplateRef_: typeof template_ref.TemplateRef_, _TemplateRef_?: template_ref.TemplateRef_<any>,
|
||||||
ReflectionCapabilities: typeof reflection_capabilities.ReflectionCapabilities,
|
ReflectionCapabilities: typeof reflection_capabilities.ReflectionCapabilities,
|
||||||
_ReflectionCapabilities?: reflection_capabilities.ReflectionCapabilities,
|
_ReflectionCapabilities?: reflection_capabilities.ReflectionCapabilities,
|
||||||
|
@ -89,6 +89,7 @@ export class DebugDomRenderer implements Renderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
destroyView(hostElement: any, viewAllNodes: any[]) {
|
destroyView(hostElement: any, viewAllNodes: any[]) {
|
||||||
|
viewAllNodes = viewAllNodes || [];
|
||||||
viewAllNodes.forEach((node) => { removeDebugNodeFromIndex(getDebugNode(node)); });
|
viewAllNodes.forEach((node) => { removeDebugNodeFromIndex(getDebugNode(node)); });
|
||||||
this._delegate.destroyView(hostElement, viewAllNodes);
|
this._delegate.destroyView(hostElement, viewAllNodes);
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ import {Injector, THROW_IF_NOT_FOUND} from '../di/injector';
|
|||||||
import {ListWrapper} from '../facade/collection';
|
import {ListWrapper} from '../facade/collection';
|
||||||
import {isPresent} from '../facade/lang';
|
import {isPresent} from '../facade/lang';
|
||||||
import {WtfScopeFn, wtfCreateScope, wtfLeave} from '../profile/profile';
|
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 {AnimationViewContext} from './animation_view_context';
|
||||||
import {ComponentRef} from './component_factory';
|
import {ComponentRef} from './component_factory';
|
||||||
@ -51,6 +51,7 @@ export abstract class AppView<T> {
|
|||||||
private _hostInjector: Injector;
|
private _hostInjector: Injector;
|
||||||
private _hostProjectableNodes: any[][];
|
private _hostProjectableNodes: any[][];
|
||||||
private _animationContext: AnimationViewContext;
|
private _animationContext: AnimationViewContext;
|
||||||
|
private _directRenderer: DirectRenderer;
|
||||||
|
|
||||||
public context: T;
|
public context: T;
|
||||||
|
|
||||||
@ -64,6 +65,7 @@ export abstract class AppView<T> {
|
|||||||
} else {
|
} else {
|
||||||
this.renderer = parentView.renderer;
|
this.renderer = parentView.renderer;
|
||||||
}
|
}
|
||||||
|
this._directRenderer = (this.renderer as any).directRenderer;
|
||||||
}
|
}
|
||||||
|
|
||||||
get animationContext(): AnimationViewContext {
|
get animationContext(): AnimationViewContext {
|
||||||
@ -136,7 +138,7 @@ export abstract class AppView<T> {
|
|||||||
|
|
||||||
detachAndDestroy() {
|
detachAndDestroy() {
|
||||||
if (this._hasExternalHostElement) {
|
if (this._hasExternalHostElement) {
|
||||||
this.renderer.detachView(this.flatRootNodes);
|
this.detach();
|
||||||
} else if (isPresent(this.viewContainerElement)) {
|
} else if (isPresent(this.viewContainerElement)) {
|
||||||
this.viewContainerElement.detachView(this.viewContainerElement.nestedViews.indexOf(this));
|
this.viewContainerElement.detachView(this.viewContainerElement.nestedViews.indexOf(this));
|
||||||
}
|
}
|
||||||
@ -179,13 +181,34 @@ export abstract class AppView<T> {
|
|||||||
detach(): void {
|
detach(): void {
|
||||||
this.detachInternal();
|
this.detachInternal();
|
||||||
if (this._animationContext) {
|
if (this._animationContext) {
|
||||||
this._animationContext.onAllActiveAnimationsDone(
|
this._animationContext.onAllActiveAnimationsDone(() => this._renderDetach());
|
||||||
() => this.renderer.detachView(this.flatRootNodes));
|
} else {
|
||||||
|
this._renderDetach();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _renderDetach() {
|
||||||
|
if (this._directRenderer) {
|
||||||
|
this.visitRootNodesInternal(this._directRenderer.remove, null);
|
||||||
} else {
|
} else {
|
||||||
this.renderer.detachView(this.flatRootNodes);
|
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 changeDetectorRef(): ChangeDetectorRef { return this.ref; }
|
||||||
|
|
||||||
get flatRootNodes(): any[] {
|
get flatRootNodes(): any[] {
|
||||||
@ -194,10 +217,14 @@ export abstract class AppView<T> {
|
|||||||
return nodes;
|
return nodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
projectedNodes(ngContentIndex: number): any[] {
|
projectNodes(parentElement: any, ngContentIndex: number) {
|
||||||
|
if (this._directRenderer) {
|
||||||
|
this.visitProjectedNodes(ngContentIndex, this._directRenderer.appendChild, parentElement);
|
||||||
|
} else {
|
||||||
const nodes: any[] = [];
|
const nodes: any[] = [];
|
||||||
this.visitProjectedNodes(ngContentIndex, addToArray, nodes);
|
this.visitProjectedNodes(ngContentIndex, addToArray, nodes);
|
||||||
return nodes;
|
this.renderer.projectNodes(parentElement, nodes);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
visitProjectedNodes<C>(ngContentIndex: number, cb: (node: any, ctx: C) => void, c: C): void {
|
visitProjectedNodes<C>(ngContentIndex: number, cb: (node: any, ctx: C) => void, c: C): void {
|
||||||
|
@ -90,7 +90,7 @@ export class ViewContainer {
|
|||||||
refRenderNode = this.nativeElement;
|
refRenderNode = this.nativeElement;
|
||||||
}
|
}
|
||||||
if (isPresent(refRenderNode)) {
|
if (isPresent(refRenderNode)) {
|
||||||
view.renderer.attachViewAfter(refRenderNode, view.flatRootNodes);
|
view.attachAfter(refRenderNode);
|
||||||
}
|
}
|
||||||
view.markContentChildAsMoved(this);
|
view.markContentChildAsMoved(this);
|
||||||
}
|
}
|
||||||
@ -118,7 +118,7 @@ export class ViewContainer {
|
|||||||
refRenderNode = this.nativeElement;
|
refRenderNode = this.nativeElement;
|
||||||
}
|
}
|
||||||
if (isPresent(refRenderNode)) {
|
if (isPresent(refRenderNode)) {
|
||||||
view.renderer.attachViewAfter(refRenderNode, view.flatRootNodes);
|
view.attachAfter(refRenderNode);
|
||||||
}
|
}
|
||||||
view.addToContentChildren(this);
|
view.addToContentChildren(this);
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,14 @@ export abstract class RenderDebugInfo {
|
|||||||
get source(): string { return unimplemented(); }
|
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
|
* @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 {APP_ID, Inject, Injectable, RenderComponentType, Renderer, RootRenderer, ViewEncapsulation} from '@angular/core';
|
||||||
|
|
||||||
import {isBlank, isPresent, stringify} from '../facade/lang';
|
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 {AnimationDriver} from './animation_driver';
|
||||||
import {DOCUMENT} from './dom_tokens';
|
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 {
|
export class DomRenderer implements Renderer {
|
||||||
private _contentAttr: string;
|
private _contentAttr: string;
|
||||||
private _hostAttr: string;
|
private _hostAttr: string;
|
||||||
private _styles: string[];
|
private _styles: string[];
|
||||||
|
|
||||||
|
directRenderer: DirectRenderer = DIRECT_DOM_RENDERER;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private _rootRenderer: DomRootRenderer, private componentProto: RenderComponentType,
|
private _rootRenderer: DomRootRenderer, private componentProto: RenderComponentType,
|
||||||
private _animationDriver: AnimationDriver, styleShimId: string) {
|
private _animationDriver: AnimationDriver, styleShimId: string) {
|
||||||
|
@ -10,6 +10,7 @@ import {__core_private__ as r} from '@angular/core';
|
|||||||
|
|
||||||
export type RenderDebugInfo = typeof r._RenderDebugInfo;
|
export type RenderDebugInfo = typeof r._RenderDebugInfo;
|
||||||
export var RenderDebugInfo: typeof r.RenderDebugInfo = r.RenderDebugInfo;
|
export var RenderDebugInfo: typeof r.RenderDebugInfo = r.RenderDebugInfo;
|
||||||
|
export type DirectRenderer = typeof r._DirectRenderer;
|
||||||
|
|
||||||
export var ReflectionCapabilities: typeof r.ReflectionCapabilities = r.ReflectionCapabilities;
|
export var ReflectionCapabilities: typeof r.ReflectionCapabilities = r.ReflectionCapabilities;
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user