feat(core): speed up view creation via code gen for view factories.

BREAKING CHANGE:
- Platform pipes can only contain types and arrays of types,
  but no bindings any more.
- When using transformers, platform pipes need to be specified explicitly
  in the pubspec.yaml via the new config option
  `platform_pipes`.
- `Compiler.compileInHost` now returns a `HostViewFactoryRef`
- Component view is not yet created when component constructor is called.
  -> use `onInit` lifecycle callback to access the view of a component
- `ViewRef#setLocal` has been moved to new type `EmbeddedViewRef`
- `internalView` is gone, use `EmbeddedViewRef.rootNodes` to access
  the root nodes of an embedded view
- `renderer.setElementProperty`, `..setElementStyle`, `..setElementAttribute` now
  take a native element instead of an ElementRef
- `Renderer` interface now operates on plain native nodes,
  instead of `RenderElementRef`s or `RenderViewRef`s

Closes #5993
This commit is contained in:
Tobias Bosch
2015-12-02 10:35:51 -08:00
parent a08f50badd
commit 7ae23adaff
191 changed files with 6476 additions and 10232 deletions
@@ -1,56 +0,0 @@
import {
AsyncTestCompleter,
inject,
describe,
ddescribe,
beforeEach,
it,
expect
} from "angular2/testing_internal";
import {RenderProtoViewRef} from "angular2/src/core/render/api";
import {RenderProtoViewRefStore} from "angular2/src/web_workers/shared/render_proto_view_ref_store";
import {
WebWorkerRenderProtoViewRef
} from "angular2/src/web_workers/shared/render_proto_view_ref_store";
export function main() {
describe("RenderProtoViewRefStore", () => {
describe("on WebWorker", () => {
var store: RenderProtoViewRefStore;
beforeEach(() => { store = new RenderProtoViewRefStore(true); });
it("should allocate refs", () => {
expect((<WebWorkerRenderProtoViewRef>store.allocate()).refNumber).toBe(0);
expect((<WebWorkerRenderProtoViewRef>store.allocate()).refNumber).toBe(1);
});
it("should be serializable", () => {
var protoView = store.allocate();
expect(store.deserialize(store.serialize(protoView))).toEqual(protoView);
});
});
describe("on UI", () => {
var store: RenderProtoViewRefStore;
beforeEach(() => { store = new RenderProtoViewRefStore(false); });
it("should associate views with the correct references", () => {
var renderProtoViewRef = new RenderProtoViewRef();
store.store(renderProtoViewRef, 100);
expect(store.deserialize(100)).toBe(renderProtoViewRef);
});
it("should be serializable", () => {
var renderProtoViewRef = new RenderProtoViewRef();
store.store(renderProtoViewRef, 0);
var deserialized = store.deserialize(store.serialize(renderProtoViewRef));
expect(deserialized).toBe(renderProtoViewRef);
});
});
});
}
@@ -0,0 +1,37 @@
import {
AsyncTestCompleter,
inject,
describe,
ddescribe,
beforeEach,
it,
expect
} from "angular2/testing_internal";
import {RenderStore} from "angular2/src/web_workers/shared/render_store";
export function main() {
describe("RenderStoreSpec", () => {
var store: RenderStore;
beforeEach(() => { store = new RenderStore(); });
it('should allocate ids', () => {
expect(store.allocateId()).toBe(0);
expect(store.allocateId()).toBe(1);
});
it('should serialize objects', () => {
var id = store.allocateId();
var obj = 'testObject';
store.store(obj, id);
expect(store.serialize(obj)).toBe(id);
});
it('should deserialize objects', () => {
var id = store.allocateId();
var obj = 'testObject';
store.store(obj, id);
expect(store.deserialize(id)).toBe(obj);
});
});
}
@@ -1,144 +0,0 @@
import {
AsyncTestCompleter,
beforeEach,
inject,
describe,
it,
expect
} from "angular2/testing_internal";
import {
RenderViewWithFragments,
RenderViewRef,
RenderFragmentRef
} from "angular2/src/core/render/api";
import {
RenderViewWithFragmentsStore,
WebWorkerRenderViewRef,
WebWorkerRenderFragmentRef
} from "angular2/src/web_workers/shared/render_view_with_fragments_store";
import {ListWrapper} from "angular2/src/facade/collection";
export function main() {
describe("RenderViewWithFragmentsStore", () => {
describe("on WebWorker", () => {
var store: RenderViewWithFragmentsStore;
beforeEach(() => { store = new RenderViewWithFragmentsStore(true); });
it("should allocate fragmentCount + 1 refs", () => {
var view: RenderViewWithFragments = store.allocate(10);
var viewRef: WebWorkerRenderViewRef = <WebWorkerRenderViewRef>view.viewRef;
expect(viewRef.refNumber).toEqual(0);
var fragmentRefs: WebWorkerRenderFragmentRef[] =
<WebWorkerRenderFragmentRef[]>view.fragmentRefs;
expect(fragmentRefs.length).toEqual(10);
for (var i = 0; i < fragmentRefs.length; i++) {
expect(fragmentRefs[i].refNumber).toEqual(i + 1);
}
});
it("should not reuse a reference", () => {
store.allocate(10);
var view = store.allocate(0);
var viewRef = <WebWorkerRenderViewRef>view.viewRef;
expect(viewRef.refNumber).toEqual(11);
});
it("should be serializable", () => {
var view = store.allocate(1);
expect(store.deserializeViewWithFragments(store.serializeViewWithFragments(view)))
.toEqual(view);
});
it("should remove a view and all attached fragments", () => {
const NUM_FRAGMENTS = 5;
var view = store.allocate(NUM_FRAGMENTS);
var viewRef = (<WebWorkerRenderViewRef>view.viewRef).refNumber;
store.remove(view.viewRef);
expect(store.deserializeRenderViewRef(viewRef++)).toBeNull();
for (var i = 0; i < NUM_FRAGMENTS; i++) {
expect(store.deserializeRenderFragmentRef(viewRef++)).toBeNull();
}
});
});
describe("on UI", () => {
var store: RenderViewWithFragmentsStore;
beforeEach(() => { store = new RenderViewWithFragmentsStore(false); });
function createMockRenderViewWithFragments(): RenderViewWithFragments {
var view = new MockRenderViewRef();
var fragments = ListWrapper.createGrowableSize(20);
for (var i = 0; i < 20; i++) {
fragments[i] = new MockRenderFragmentRef();
}
return new RenderViewWithFragments(view, fragments);
}
it("should associate views with the correct references", () => {
var renderViewWithFragments = createMockRenderViewWithFragments();
store.store(renderViewWithFragments, 100);
expect(store.deserializeRenderViewRef(100)).toBe(renderViewWithFragments.viewRef);
for (var i = 0; i < renderViewWithFragments.fragmentRefs.length; i++) {
expect(store.deserializeRenderFragmentRef(101 + i))
.toBe(renderViewWithFragments.fragmentRefs[i]);
}
});
describe("RenderViewWithFragments", () => {
it("should be serializable", () => {
var renderViewWithFragments = createMockRenderViewWithFragments();
store.store(renderViewWithFragments, 0);
var deserialized = store.deserializeViewWithFragments(
store.serializeViewWithFragments(renderViewWithFragments));
expect(deserialized.viewRef).toBe(renderViewWithFragments.viewRef);
expect(deserialized.fragmentRefs.length)
.toEqual(renderViewWithFragments.fragmentRefs.length);
for (var i = 0; i < deserialized.fragmentRefs.length; i++) {
var val = deserialized.fragmentRefs[i];
expect(val).toBe(renderViewWithFragments.fragmentRefs[i]);
};
});
});
describe("RenderViewRef", () => {
it("should be serializable", () => {
var renderViewWithFragments = createMockRenderViewWithFragments();
store.store(renderViewWithFragments, 0);
var deserialized = store.deserializeRenderViewRef(
store.serializeRenderViewRef(renderViewWithFragments.viewRef));
expect(deserialized).toBe(renderViewWithFragments.viewRef);
});
});
describe("RenderFragmentRef", () => {
it("should be serializable", () => {
var renderViewWithFragments = createMockRenderViewWithFragments();
store.store(renderViewWithFragments, 0);
var serialized =
store.serializeRenderFragmentRef(renderViewWithFragments.fragmentRefs[0]);
var deserialized = store.deserializeRenderFragmentRef(serialized);
expect(deserialized).toBe(renderViewWithFragments.fragmentRefs[0]);
});
});
});
});
}
class MockRenderViewRef extends RenderViewRef {
constructor() { super(); }
}
class MockRenderFragmentRef extends RenderFragmentRef {
constructor() { super(); }
}
@@ -18,10 +18,7 @@ import {
import {ObservableWrapper, PromiseWrapper} from 'angular2/src/facade/async';
import {provide} from 'angular2/core';
import {ON_WEB_WORKER} from 'angular2/src/web_workers/shared/api';
import {RenderProtoViewRefStore} from 'angular2/src/web_workers/shared/render_proto_view_ref_store';
import {
RenderViewWithFragmentsStore
} from 'angular2/src/web_workers/shared/render_view_with_fragments_store';
import {RenderStore} from 'angular2/src/web_workers/shared/render_store';
export function main() {
const CHANNEL = "UIMessageBroker Test Channel";
@@ -31,11 +28,7 @@ export function main() {
const RESULT = 20;
const ID = "methodId";
beforeEachProviders(() => [
provide(ON_WEB_WORKER, {useValue: true}),
RenderProtoViewRefStore,
RenderViewWithFragmentsStore
]);
beforeEachProviders(() => [provide(ON_WEB_WORKER, {useValue: true}), RenderStore]);
describe("UIMessageBroker", () => {
var messageBuses;
@@ -1,71 +0,0 @@
import {
AsyncTestCompleter,
inject,
describe,
it,
expect,
beforeEach,
beforeEachProviders,
SpyObject,
proxy
} from 'angular2/testing_internal';
import {Serializer} from 'angular2/src/web_workers/shared/serializer';
import {ON_WEB_WORKER} from 'angular2/src/web_workers/shared/api';
import {provide} from 'angular2/core';
import {RenderProtoViewRefStore} from 'angular2/src/web_workers/shared/render_proto_view_ref_store';
import {
RenderViewWithFragmentsStore,
WebWorkerRenderViewRef
} from 'angular2/src/web_workers/shared/render_view_with_fragments_store';
import {RenderEventDispatcher, RenderViewRef} from 'angular2/src/core/render/api';
import {createPairedMessageBuses} from '../shared/web_worker_test_util';
import {WebWorkerEventDispatcher} from 'angular2/src/web_workers/worker/event_dispatcher';
import {ObservableWrapper} from 'angular2/src/facade/async';
import {EVENT_CHANNEL} from 'angular2/src/web_workers/shared/messaging_api';
export function main() {
describe("EventDispatcher", () => {
beforeEachProviders(() => [
provide(ON_WEB_WORKER, {useValue: true}),
RenderProtoViewRefStore,
RenderViewWithFragmentsStore
]);
it("should dispatch events", inject([Serializer, AsyncTestCompleter], (serializer, async) => {
var messageBuses = createPairedMessageBuses();
var webWorkerEventDispatcher =
new WebWorkerEventDispatcher(messageBuses.worker, serializer);
var elementIndex = 15;
var eventName = 'click';
var eventDispatcher = new SpyEventDispatcher((elementIndex, eventName, locals) => {
expect(elementIndex).toEqual(elementIndex);
expect(eventName).toEqual(eventName);
async.done();
});
var viewRef = new WebWorkerRenderViewRef(0);
serializer.allocateRenderViews(0); // serialize the ref so it's in the store
viewRef =
serializer.deserialize(serializer.serialize(viewRef, RenderViewRef), RenderViewRef);
webWorkerEventDispatcher.registerEventDispatcher(viewRef, eventDispatcher);
ObservableWrapper.callEmit(messageBuses.ui.to(EVENT_CHANNEL), {
'viewRef': viewRef.serialize(),
'elementIndex': elementIndex,
'eventName': eventName,
'locals': {'$event': {'target': {value: null}}}
});
}));
});
}
class SpyEventDispatcher implements RenderEventDispatcher {
constructor(private _callback: Function) {}
dispatchRenderEvent(elementIndex: number, eventName: string, locals: Map<string, any>): boolean {
this._callback(elementIndex, eventName, locals);
return false;
}
}
@@ -3,6 +3,7 @@ import {
inject,
ddescribe,
describe,
dispatchEvent,
it,
iit,
expect,
@@ -23,8 +24,9 @@ import {
Injectable,
ElementRef
} from 'angular2/core';
import {AppViewListener} from 'angular2/src/core/linker/view_listener';
import {NgIf} from 'angular2/common';
import {WebWorkerRenderer} from "angular2/src/web_workers/worker/renderer";
import {WebWorkerRootRenderer} from "angular2/src/web_workers/worker/renderer";
import {
ClientMessageBrokerFactory,
ClientMessageBrokerFactory_,
@@ -32,38 +34,22 @@ import {
FnArg
} from "angular2/src/web_workers/shared/client_message_broker";
import {Serializer} from "angular2/src/web_workers/shared/serializer";
import {
RenderProtoViewRef,
RenderViewWithFragments,
RenderViewRef,
RenderFragmentRef,
Renderer
} from "angular2/src/core/render/api";
import {DomRenderer, DomRenderer_} from 'angular2/src/platform/dom/dom_renderer';
import {DefaultRenderView} from 'angular2/src/core/render/view';
import {
RenderProtoViewRefStore,
WebWorkerRenderProtoViewRef
} from "angular2/src/web_workers/shared/render_proto_view_ref_store";
import {
RenderViewWithFragmentsStore,
WebWorkerRenderViewRef
} from 'angular2/src/web_workers/shared/render_view_with_fragments_store';
import {RootRenderer} from "angular2/src/core/render/api";
import {DomRootRenderer, DomRootRenderer_} from 'angular2/src/platform/dom/dom_renderer';
import {RenderStore} from "angular2/src/web_workers/shared/render_store";
import {MessageBasedRenderer} from 'angular2/src/web_workers/ui/renderer';
import {createPairedMessageBuses, PairedMessageBuses} from '../shared/web_worker_test_util';
import {
ServiceMessageBrokerFactory,
ServiceMessageBrokerFactory_
} from 'angular2/src/web_workers/shared/service_message_broker';
import {WebWorkerEventDispatcher} from 'angular2/src/web_workers/worker/event_dispatcher';
import {ChangeDetectorGenConfig} from 'angular2/src/core/change_detection/change_detection';
export function main() {
function createWebWorkerBrokerFactory(
messageBuses: PairedMessageBuses, workerSerializer: Serializer, uiSerializer: Serializer,
domRenderer: DomRenderer, uiRenderProtoViewStore: RenderProtoViewRefStore,
uiRenderViewStore: RenderViewWithFragmentsStore): ClientMessageBrokerFactory {
domRootRenderer: DomRootRenderer, uiRenderStore: RenderStore): ClientMessageBrokerFactory {
var uiMessageBus = messageBuses.ui;
var workerMessageBus = messageBuses.worker;
@@ -74,67 +60,62 @@ export function main() {
// set up the ui side
var uiMessageBrokerFactory = new ServiceMessageBrokerFactory_(uiMessageBus, uiSerializer);
var renderer = new MessageBasedRenderer(uiMessageBrokerFactory, uiMessageBus, uiSerializer,
uiRenderProtoViewStore, uiRenderViewStore, domRenderer);
uiRenderStore, domRootRenderer);
renderer.start();
return webWorkerBrokerFactory;
}
function createWorkerRenderer(
workerSerializer: Serializer, uiSerializer: Serializer, domRenderer: DomRenderer,
uiRenderProtoViewStore: RenderProtoViewRefStore,
uiRenderViewStore: RenderViewWithFragmentsStore,
workerRenderProtoViewStore: RenderProtoViewRefStore,
workerRenderViewStore: RenderViewWithFragmentsStore): WebWorkerRenderer {
function createWorkerRenderer(workerSerializer: Serializer, uiSerializer: Serializer,
domRootRenderer: DomRootRenderer, uiRenderStore: RenderStore,
workerRenderStore: RenderStore): WebWorkerRootRenderer {
var messageBuses = createPairedMessageBuses();
var brokerFactory =
createWebWorkerBrokerFactory(messageBuses, workerSerializer, uiSerializer, domRenderer,
uiRenderProtoViewStore, uiRenderViewStore);
var workerEventDispatcher = new WebWorkerEventDispatcher(messageBuses.worker, workerSerializer);
return new WebWorkerRenderer(brokerFactory, workerRenderProtoViewStore, workerRenderViewStore,
workerEventDispatcher);
var brokerFactory = createWebWorkerBrokerFactory(messageBuses, workerSerializer, uiSerializer,
domRootRenderer, uiRenderStore);
return new WebWorkerRootRenderer(brokerFactory, messageBuses.worker, workerSerializer,
workerRenderStore);
}
describe("Web Worker Renderer", () => {
var uiInjector: Injector;
var uiRenderViewStore: RenderViewWithFragmentsStore;
var uiRenderStore: RenderStore;
var workerRenderStore: RenderStore;
beforeEachProviders(() => {
var uiRenderProtoViewStore = new RenderProtoViewRefStore(false);
uiRenderViewStore = new RenderViewWithFragmentsStore(false);
uiRenderStore = new RenderStore();
var testInjector = new TestInjector();
testInjector.addProviders([
provide(RenderProtoViewRefStore, {useValue: uiRenderProtoViewStore}),
provide(RenderViewWithFragmentsStore, {useValue: uiRenderViewStore}),
provide(DomRenderer, {useClass: DomRenderer_}),
provide(Renderer, {useExisting: DomRenderer})
provide(RenderStore, {useValue: uiRenderStore}),
provide(DomRootRenderer, {useClass: DomRootRenderer_}),
provide(RootRenderer, {useExisting: DomRootRenderer})
]);
uiInjector = testInjector.createInjector();
var uiSerializer = uiInjector.get(Serializer);
var domRenderer = uiInjector.get(DomRenderer);
var workerRenderProtoViewStore = new RenderProtoViewRefStore(true);
var workerRenderViewStore = new RenderViewWithFragmentsStore(true);
var domRootRenderer = uiInjector.get(DomRootRenderer);
workerRenderStore = new RenderStore();
return [
provide(ChangeDetectorGenConfig,
{useValue: new ChangeDetectorGenConfig(true, true, false)}),
provide(RenderProtoViewRefStore, {useValue: workerRenderProtoViewStore}),
provide(RenderViewWithFragmentsStore, {useValue: workerRenderViewStore}),
provide(Renderer,
provide(RenderStore, {useValue: workerRenderStore}),
provide(RootRenderer,
{
useFactory: (workerSerializer) => {
return createWorkerRenderer(workerSerializer, uiSerializer, domRenderer,
uiRenderProtoViewStore, uiRenderViewStore,
workerRenderProtoViewStore, workerRenderViewStore);
return createWorkerRenderer(workerSerializer, uiSerializer, domRootRenderer,
uiRenderStore, workerRenderStore);
},
deps: [Serializer]
})
}),
provide(AppViewListener, {useClass: AppViewListener})
];
});
function getRenderElement(elementRef: ElementRef) {
var renderView = <DefaultRenderView<Node>>uiRenderViewStore.deserializeRenderViewRef(
(<WebWorkerRenderViewRef>elementRef.renderView).refNumber);
return renderView.boundElements[elementRef.boundElementIndex];
var id = workerRenderStore.serialize(elementRef.nativeElement);
return uiRenderStore.deserialize(id);
}
function getRenderer(elementRef: ElementRef) {
return (<any>elementRef).internalElement.parentView.renderer;
}
it('should update text nodes',
@@ -154,28 +135,28 @@ export function main() {
}));
it('should update any element property/attributes/class/style independent of the compilation on the root element and other elements',
inject([TestComponentBuilder, Renderer, AsyncTestCompleter], (tcb: TestComponentBuilder,
renderer: Renderer, async) => {
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
tcb.overrideView(MyComp, new ViewMetadata(
{template: '<input [title]="y" style="position:absolute">'}))
.createAsync(MyComp)
.then((fixture) => {
var checkSetters = (elr) => {
var renderer = getRenderer(elr);
var el = getRenderElement(elr);
renderer.setElementProperty(elr, 'tabIndex', 1);
renderer.setElementProperty(elr.nativeElement, 'tabIndex', 1);
expect((<HTMLInputElement>el).tabIndex).toEqual(1);
renderer.setElementClass(elr, 'a', true);
renderer.setElementClass(elr.nativeElement, 'a', true);
expect(DOM.hasClass(el, 'a')).toBe(true);
renderer.setElementClass(elr, 'a', false);
renderer.setElementClass(elr.nativeElement, 'a', false);
expect(DOM.hasClass(el, 'a')).toBe(false);
renderer.setElementStyle(elr, 'width', '10px');
renderer.setElementStyle(elr.nativeElement, 'width', '10px');
expect(DOM.getStyle(el, 'width')).toEqual('10px');
renderer.setElementStyle(elr, 'width', null);
renderer.setElementStyle(elr.nativeElement, 'width', null);
expect(DOM.getStyle(el, 'width')).toEqual('');
renderer.setElementAttribute(elr, 'someattr', 'someValue');
renderer.setElementAttribute(elr.nativeElement, 'someattr', 'someValue');
expect(DOM.getAttribute(el, 'someattr')).toEqual('someValue');
};
@@ -189,20 +170,19 @@ export function main() {
}));
it('should update any template comment property/attributes',
inject([TestComponentBuilder, Renderer, AsyncTestCompleter],
(tcb: TestComponentBuilder, renderer: Renderer, async) => {
var tpl = '<template [ngIf]="ctxBoolProp"></template>';
tcb.overrideView(MyComp, new ViewMetadata({template: tpl, directives: [NgIf]}))
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var tpl = '<template [ngIf]="ctxBoolProp"></template>';
tcb.overrideView(MyComp, new ViewMetadata({template: tpl, directives: [NgIf]}))
.createAsync(MyComp)
.then((fixture) => {
(<MyComp>fixture.debugElement.componentInstance).ctxBoolProp = true;
fixture.detectChanges();
var el = getRenderElement(fixture.debugElement.elementRef);
expect(DOM.getInnerHTML(el)).toContain('"ng-reflect-ng-if": "true"');
async.done();
});
}));
.createAsync(MyComp)
.then((fixture) => {
(<MyComp>fixture.debugElement.componentInstance).ctxBoolProp = true;
fixture.detectChanges();
var el = getRenderElement(fixture.debugElement.elementRef);
expect(DOM.getInnerHTML(el)).toContain('"ng-reflect-ng-if": "true"');
async.done();
});
}));
it('should add and remove fragments',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
@@ -229,19 +209,35 @@ export function main() {
}));
if (DOM.supportsDOMEvents()) {
it('should call actions on the element independent of the compilation',
inject([TestComponentBuilder, Renderer, AsyncTestCompleter],
(tcb: TestComponentBuilder, renderer: Renderer, async) => {
tcb.overrideView(MyComp, new ViewMetadata({template: '<input [title]="y">'}))
.createAsync(MyComp)
.then((fixture) => {
var elRef = fixture.debugElement.componentViewChildren[0].elementRef;
renderer.invokeElementMethod(elRef, 'setAttribute', ['a', 'b']);
it('should call actions on the element',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
tcb.overrideView(MyComp, new ViewMetadata({template: '<input [title]="y">'}))
.createAsync(MyComp)
.then((fixture) => {
var elRef = fixture.debugElement.componentViewChildren[0].elementRef;
getRenderer(elRef)
.invokeElementMethod(elRef.nativeElement, 'setAttribute', ['a', 'b']);
expect(DOM.getAttribute(getRenderElement(elRef), 'a')).toEqual('b');
async.done();
});
}));
expect(DOM.getAttribute(getRenderElement(elRef), 'a')).toEqual('b');
async.done();
});
}));
it('should listen to events',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
tcb.overrideView(MyComp,
new ViewMetadata({template: '<input (change)="ctxNumProp = 1">'}))
.createAsync(MyComp)
.then((fixture) => {
var elRef = fixture.debugElement.componentViewChildren[0].elementRef;
dispatchEvent(getRenderElement(elRef), 'change');
expect(fixture.componentInstance.ctxNumProp).toBe(1);
fixture.destroy();
async.done();
});
}));
}
});
}