From a9896ed3918d0a8d08847b8ba401cb757083515b Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Fri, 10 Oct 2014 20:44:55 -0700 Subject: [PATCH] design: view instantiation test --- modules/core/src/compiler/element_injector.js | 85 +++++++++++++++++-- modules/core/src/compiler/selector.js | 2 +- modules/core/src/compiler/view.js | 81 +++++++++++------- modules/core/test/compiler/view_spec.js | 40 ++++++++- modules/facade/src/dom.dart | 10 +++ modules/facade/src/dom.es6 | 9 ++ modules/facade/src/lang.dart | 1 + tools/transpiler/src/parser.js | 3 +- 8 files changed, 191 insertions(+), 40 deletions(-) diff --git a/modules/core/src/compiler/element_injector.js b/modules/core/src/compiler/element_injector.js index 33dc688a7f..d7bf8a1832 100644 --- a/modules/core/src/compiler/element_injector.js +++ b/modules/core/src/compiler/element_injector.js @@ -2,7 +2,7 @@ import {FIELD} from 'facade/lang'; /** -Difference beteween di.Injector and ElementInjector +Difference between di.Injector and ElementInjector di.Injector (di.Module): - imperative based (can create child injectors imperativly) @@ -16,14 +16,47 @@ ElementInjector (ElementModule): - Fast - Query mechanism for children - 1:1 to DOM structure. + + PERF BENCHMARK: http://www.williambrownstreet.net/blog/2014/04/faster-angularjs-rendering-angularjs-and-reactjs/ + */ export class ProtoElementInjector { - @FIELD('final _parent:ProtoElementInjector') - /// Temporory instance while instantiating - @FIELD('_clone:ElementInjector') + /** + parent:ProtoDirectiveInjector; + next:ProtoDirectiveInjector; + prev:ProtoDirectiveInjector; + head:ProtoDirectiveInjector; + tail:ProtoDirectiveInjector; + DirectiveInjector cloneingInstance; + KeyMap keyMap; + /// Because DI tree is sparse, this shows how far away is the Parent DI + parentDistance:int = 1; /// 1 for non-sparse/normal depth. + + cKey:int; cFactory:Function; cParams:List; + keyId0:int; factory0:Function; params0:List; + keyId1:int; factory1:Function; params1:List; + keyId2:int; factory2:Function; params2:List; + keyId3:int; factory3:Function; params3:List; + keyId4:int; factory4:Function; params4:List; + keyId5:int; factory5:Function; params5:List; + keyId6:int; factory6:Function; params6:List; + keyId7:int; factory7:Function; params7:List; + keyId8:int; factory8:Function; params8:List; + keyId9:int; factory9:Function; params9:List; + + queryKeyId0:int; + queryKeyId1:int; + + textNodes:List; + hasProperties:bool; + events:Map; + + elementInjector:ElementInjector; + */ constructor(parent:ProtoElementInjector) { - this._parent = parent; + this.hasProperties = false; + this.textNodes = null; } instantiate():ElementInjector { @@ -32,6 +65,48 @@ export class ProtoElementInjector { } export class ElementInjector { + /* + _protoInjector:ProtoElementInjector; + injector:Injector; + _parent:ElementInjector; + _next:ElementInjector; + _prev:ElementInjector; + _head:ElementInjector; + _tail:ElementInjector; + + + // For performance reasons the Injector only supports 10 directives per element. + // NOTE: linear search over fields is faster than HashMap lookup. + _cObj; // Component only + _obj0; + _obj1; + _obj2; + _obj3; + _obj4; + _obj5; + _obj6; + _obj7; + _obj8; + _obj9; + + element:Element; + ngElement:NgElement; + shadowRoot:ShadowRoot; + elementProbe:ElementProbe; + view:View; + viewPort:ViewPort; + viewFactory:ViewFactory; + animate:Animate; + destinationLightDom:DestinationLightDom; + sourceLightDom:SourceLightDom; + + + // For performance reasons the Injector only supports 2 [Query] per element. + // NOTE: linear search over fields is faster than HashMap lookup. + _query0:Query; + _query1:Query; + + */ @FIELD('final protoInjector:ProtoElementInjector') constructor(protoInjector:ProtoElementInjector) { this.protoInjector = protoInjector; diff --git a/modules/core/src/compiler/selector.js b/modules/core/src/compiler/selector.js index 4aee0862b3..aed4c2ac41 100644 --- a/modules/core/src/compiler/selector.js +++ b/modules/core/src/compiler/selector.js @@ -13,7 +13,7 @@ export class Selector { * @param elementName Name of the element * @param attributes Attributes on the Element. */ - visitElement(elementName:String, attributes:Map):List { + visitElement(elementName:string, attributes:Map):List { return null; } } diff --git a/modules/core/src/compiler/view.js b/modules/core/src/compiler/view.js index 1b839a5700..49d3a05d0f 100644 --- a/modules/core/src/compiler/view.js +++ b/modules/core/src/compiler/view.js @@ -1,4 +1,4 @@ -import {DOM, Node, DocumentFragment, TemplateElement} from 'facade/dom'; +import {DOM, Element, Node, Text, DocumentFragment, TemplateElement} from 'facade/dom'; import {ListWrapper} from 'facade/collection'; import {ProtoWatchGroup, WatchGroup, WatchGroupDispatcher} from 'change_detection/watch_group'; import {Record} from 'change_detection/record'; @@ -8,56 +8,66 @@ import {SetterFn} from 'change_detection/facade'; import {FIELD, IMPLEMENTS} from 'facade/lang'; import {List} from 'facade/collection'; +/*** + * Const of making objects: http://jsperf.com/instantiate-size-of-object + */ @IMPLEMENTS(WatchGroupDispatcher) export class View { - @FIELD('final _fragment:DocumentFragment') + @FIELD('final fragment:DocumentFragment') /// This list matches the _nodes list. It is sparse, since only Elements have ElementInjector - @FIELD('final _rootElementInjectors:List') - @FIELD('final _elementInjectors:List') - @FIELD('final _textNodes:List') - @FIELD('final _watchGroup:WatchGroup') + @FIELD('final rootElementInjectors:List') + @FIELD('final elementInjectors:List') + @FIELD('final bindElements:List') + @FIELD('final textNodes:List') + @FIELD('final watchGroup:WatchGroup') /// When the view is part of render tree, the DocumentFragment is empty, which is why we need /// to keep track of the nodes. - @FIELD('final _nodes:List') - @FIELD('final _onChangeDispatcher:OnChangeDispatcher') + @FIELD('final nodes:List') + @FIELD('final onChangeDispatcher:OnChangeDispatcher') constructor(fragment:DocumentFragment) { - this._fragment = fragment; - this._nodes = ListWrapper.clone(fragment.childNodes); - this._onChangeDispatcher = null; - this._elementInjectors = null; - this._textNodes = null; + this.fragment = fragment; + this.nodes = ListWrapper.clone(fragment.childNodes); + this.onChangeDispatcher = null; + this.elementInjectors = null; + this.textNodes = null; + this.bindElements = null; } onRecordChange(record:Record, target) { // dispatch to element injector or text nodes based on context - if (target instanceof ElementInjectorTarget) { - // we know that it is ElementInjectorTarget - var eTarget:ElementInjectorTarget = target; - this._onChangeDispatcher.notify(this, eTarget); - eTarget.invoke(record, this._elementInjectors); + if (target instanceof DirectivePropertyMemento) { + // we know that it is DirectivePropertyMemento + var directiveMemento:DirectivePropertyMemento = target; + directiveMemento.invoke(record, this.elementInjectors); + } else if (target instanceof ElementPropertyMemento) { + var elementMemento:ElementPropertyMemento = target; + elementMemento.invoke(record, this.bindElements); } else { - // we know it refferst to _textNodes. + // we know it refers to _textNodes. var textNodeIndex:number = target; - DOM.setText(this._textNodes[textNodeIndex], record.currentValue); + DOM.setText(this.textNodes[textNodeIndex], record.currentValue); } } } export class ProtoView { -@FIELD('final _template:TemplateElement') -@FIELD('final _module:Module') -@FIELD('final _protoElementInjectors:List') -@FIELD('final _protoWatchGroup:ProtoWatchGroup') + @FIELD('final _template:TemplateElement') + @FIELD('final _module:Module') + @FIELD('final _protoElementInjectors:List') + @FIELD('final _protoWatchGroup:ProtoWatchGroup') + @FIELD('final _useRootElement:bool') constructor( template:TemplateElement, module:Module, - protoElementInjector:ProtoElementInjector, - protoWatchGroup:ProtoWatchGroup) + protoElementInjector:List, + protoWatchGroup:ProtoWatchGroup, + useRootElement:bool) { this._template = template; this._module = module; this._protoElementInjectors = protoElementInjector; this._protoWatchGroup = protoWatchGroup; + this._useRootElement = useRootElement; } instantiate():View { @@ -65,8 +75,21 @@ export class ProtoView { } } +export class ElementPropertyMemento { + @FIELD('final _elementIndex:int') + @FIELD('final _propertyIndex:string') + constructor(elementIndex:int, propertyName:string) { + this._elementIndex = elementIndex; + this._propertyName = propertyName; + } -export class ElementInjectorTarget { + invoke(record:Record, elementInjectors:List) { + var element:Element = elementInjectors[this._elementIndex]; + DOM.setProperty(element, this._propertyName, record.currentValue); + } +} + +export class DirectivePropertyMemento { @FIELD('final _elementInjectorIndex:int') @FIELD('final _directiveIndex:int') @FIELD('final _setterName:String') @@ -97,13 +120,13 @@ export class ElementInjectorTarget { export class OnChangeDispatcher { @FIELD('_lastView:View') - @FIELD('_lastTarget:ElementInjectorTarget') + @FIELD('_lastTarget:DirectivePropertyMemento') constructor() { this._lastView = null; this._lastTarget = null; } - notify(view:View, eTarget:ElementInjectorTarget) { + notify(view:View, eTarget:DirectivePropertyMemento) { } diff --git a/modules/core/test/compiler/view_spec.js b/modules/core/test/compiler/view_spec.js index 0eb7d4793c..d76b4b7861 100644 --- a/modules/core/test/compiler/view_spec.js +++ b/modules/core/test/compiler/view_spec.js @@ -1,16 +1,50 @@ -import {describe, it, expect} from 'test_lib/test_lib'; +import {describe, xit, it, expect} from 'test_lib/test_lib'; +import {ProtoWatchGroup} from 'change_detection/watch_group'; import {ProtoView, View} from 'core/compiler/view'; -import {DOM} from 'facade/dom'; +import {ProtoElementInjector, ElementInjector} from 'core/compiler/element_injector'; +import {DOM, Element} from 'facade/dom'; +import {Module} from 'di/di'; export function main() { describe('view', function() { describe('ProtoView', function() { it('should create an instance of view', function() { var template = DOM.createTemplate('Hello world!'); - var pv = new ProtoView(template, null, null, null); + var pv = new ProtoView(template, null, null, null, false); var view:View = pv.instantiate(); expect(view instanceof View).toBe(true); }); + + + xit('should create view instance and locate basic parts', function() { + var template = DOM.createTemplate( + '
' + + 'Hello {}!' + + '
' + + 'don\'t show me' + + '
' + + '
'); + var module:Module = null; + var sectionPI = new ProtoElementInjector(null); + sectionPI.textNodes = [0]; + var divPI = new ProtoElementInjector(null); + var spanPI = new ProtoElementInjector(null); + spanPI.hasProperties = true; + var protoElementInjector:List = [sectionPI, divPI, spanPI]; + var protoWatchGroup:ProtoWatchGroup = null; + var hasSingleRoot:bool = false; + var pv = new ProtoView(template, module, protoElementInjector, protoWatchGroup, hasSingleRoot); + var view:View = pv.instantiate(); + var section:Element = template.content.firstChild; + var div:Element = DOM.getElementsByTagName(section, 'div'); + var span:Element = DOM.getElementsByTagName(div, 'span'); + expect(DOM.getInnerHTML(view.fragment)).toEqual(DOM.getInnerHTML(section)); // exclude top level
+ expect(view.nodes).toEqual([view.fragment.firstChild.childNodes]); // TextNode(Hello...),
+ var elementInjector:ElementInjector = view.elementInjectors[1]; + expect(view.elementInjectors).toEqual([null, elementInjector, null]); // only second one has directive + expect(view.bindElements).toEqual([span]); + expect(view.textNodes).toEqual([section.childNodes[0]]); + }); }); }); } diff --git a/modules/facade/src/dom.dart b/modules/facade/src/dom.dart index 7efc313b59..fb80840461 100644 --- a/modules/facade/src/dom.dart +++ b/modules/facade/src/dom.dart @@ -1,6 +1,7 @@ library angular.core.facade.dom; import 'dart:html'; +import 'dart:js' show JsObject; export 'dart:html' show DocumentFragment, Node, Element, TemplateElement, Text; @@ -28,4 +29,13 @@ class DOM { static clone(Node node) { return node.clone(true); } + static setProperty(Element element, String name, value) { + new JsObject.fromBrowserObject(element)[name] = value; + } + static getElementsByClassName(Element element, String name) { + return element.getElementsByClassName(name); + } + static getElementsByTagName(Element element, String name) { + return element.querySelectorAll(name); + } } diff --git a/modules/facade/src/dom.es6 b/modules/facade/src/dom.es6 index 519c9dfee6..b80e3563c7 100644 --- a/modules/facade/src/dom.es6 +++ b/modules/facade/src/dom.es6 @@ -28,4 +28,13 @@ export class DOM { static clone(node:Node) { return node.cloneNode(true); } + static setProperty(element:Element, name:string, value) { + element[name] = value; + } + static getElementsByClassName(element:Element, name:string) { + return element.getElementsByClassName(name); + } + static getElementsByTagName(element:Element, name:string) { + return element.getElementsByTagName(name); + } } diff --git a/modules/facade/src/lang.dart b/modules/facade/src/lang.dart index ef17c7382c..24843950e2 100644 --- a/modules/facade/src/lang.dart +++ b/modules/facade/src/lang.dart @@ -57,3 +57,4 @@ class NumberWrapper { return double.parse(text); } } + diff --git a/tools/transpiler/src/parser.js b/tools/transpiler/src/parser.js index a1ea24bd9e..e0b75f61a8 100644 --- a/tools/transpiler/src/parser.js +++ b/tools/transpiler/src/parser.js @@ -11,7 +11,6 @@ export class Parser extends TraceurParser { parseTypeName_() { // Copy of original implementation var typeName = super.parseTypeName_(); - var next = this.peekType_(); // Generics support if (this.eatIf_(OPEN_ANGLE)) { var generics = []; @@ -34,4 +33,4 @@ export class Parser extends TraceurParser { } while (this.eatIf_(COMMA)); this.eat_(CLOSE_CURLY); } -} \ No newline at end of file +}