Skip to content

Commit 8a1c158

Browse files
mheverydsalsbury
authored andcommitted
perf(view): increase view instantiation speed 40%
1) Stop using futures when the value is already cached 2) Remove adding nodes to fake parent during view instantiation Closes dart-archive#1358
1 parent 2e9c5e9 commit 8a1c158

File tree

8 files changed

+165
-113
lines changed

8 files changed

+165
-113
lines changed

lib/core_dom/directive.dart

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class NodeAttrs {
2222

2323
NodeAttrs(this.element);
2424

25-
operator [](String attrName) => element.attributes[attrName];
25+
operator [](String attrName) => element.getAttribute(attrName);
2626

2727
void operator []=(String attrName, String value) {
2828
if (_mustacheAttrs.containsKey(attrName)) {
@@ -31,7 +31,7 @@ class NodeAttrs {
3131
if (value == null) {
3232
element.attributes.remove(attrName);
3333
} else {
34-
element.attributes[attrName] = value;
34+
element.setAttribute(attrName, value);
3535
}
3636

3737
if (_observers != null && _observers.containsKey(attrName)) {
@@ -86,9 +86,18 @@ class NodeAttrs {
8686
* ShadowRoot is ready.
8787
*/
8888
class TemplateLoader {
89-
final async.Future<dom.Node> template;
89+
async.Future<dom.Node> _template;
90+
List<async.Future> _futures;
91+
final dom.Node _shadowRoot;
9092

91-
TemplateLoader(this.template);
93+
TemplateLoader(this._shadowRoot, this._futures);
94+
95+
async.Future<dom.Node> get template {
96+
if (_template == null) {
97+
_template = async.Future.wait(_futures).then((_) => _shadowRoot);
98+
}
99+
return _template;
100+
}
92101
}
93102

94103
class _MustacheAttr {

lib/core_dom/shadow_dom_component_factory.dart

Lines changed: 86 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
part of angular.core.dom_internal;
22

33
var _ComponentFactory_call = traceCreateScope('ComponentFactory#call()');
4+
var _ComponentFactory_styles = traceCreateScope('ComponentFactory#styles()');
45

56
abstract class ComponentFactory {
67
BoundComponentFactory bind(DirectiveRef ref, directives);
@@ -13,7 +14,7 @@ abstract class BoundComponentFactory {
1314
List<Key> get callArgs;
1415
Function call(dom.Element element);
1516

16-
static async.Future<ViewFactory> _viewFuture(
17+
static async.Future<ViewFactory> _viewFactoryFuture(
1718
Component component, ViewCache viewCache, DirectiveMap directives) {
1819
if (component.template != null) {
1920
return new async.Future.value(viewCache.fromHtml(component.template, directives));
@@ -67,20 +68,27 @@ class BoundShadowDomComponentFactory implements BoundComponentFactory {
6768
Component get _component => _ref.annotation as Component;
6869

6970
String _tag;
70-
async.Future<Iterable<dom.StyleElement>> _styleElementsFuture;
71-
async.Future<ViewFactory> _viewFuture;
71+
async.Future<List<dom.StyleElement>> _styleElementsFuture;
72+
List<dom.StyleElement> _styleElements;
73+
async.Future<ViewFactory> _shadowViewFactoryFuture;
74+
ViewFactory _shadowViewFactory;
7275

7376
BoundShadowDomComponentFactory(this._componentFactory, this._ref, this._directives) {
7477
_tag = _component.selector.toLowerCase();
75-
_styleElementsFuture = async.Future.wait(_component.cssUrls.map(_styleFuture));
78+
_styleElementsFuture = async.Future.wait(_component.cssUrls.map(_urlToStyle))
79+
..then((stylesElements) => _styleElements = stylesElements);
7680

77-
_viewFuture = BoundComponentFactory._viewFuture(
81+
_shadowViewFactoryFuture = BoundComponentFactory._viewFactoryFuture(
7882
_component,
83+
// TODO(misko): Why do we create a new one per Component. This kind of defeats the caching.
7984
new PlatformViewCache(_componentFactory.viewCache, _tag, _componentFactory.platform),
8085
_directives);
86+
if (_shadowViewFactoryFuture != null) {
87+
_shadowViewFactoryFuture.then((viewFactory) => _shadowViewFactory = viewFactory);
88+
}
8189
}
8290

83-
async.Future<dom.StyleElement> _styleFuture(cssUrl) {
91+
async.Future<dom.StyleElement> _urlToStyle(cssUrl) {
8492
Http http = _componentFactory.http;
8593
TemplateCache templateCache = _componentFactory.templateCache;
8694
WebPlatform platform = _componentFactory.platform;
@@ -109,7 +117,7 @@ class BoundShadowDomComponentFactory implements BoundComponentFactory {
109117

110118
// If the css shim is required, it means that scoping does not
111119
// work, and adding the style to the head of the document is
112-
// preferrable.
120+
// preferable.
113121
if (platform.cssShimRequired) {
114122
dom.document.head.append(styleElement);
115123
return null;
@@ -128,47 +136,57 @@ class BoundShadowDomComponentFactory implements BoundComponentFactory {
128136
EventHandler eventHandler) {
129137
var s = traceEnter(_ComponentFactory_call);
130138
try {
131-
var shadowDom = element.createShadowRoot()
139+
var shadowScope = scope.createChild(new HashMap()); // Isolate
140+
ComponentDirectiveInjector shadowInjector;
141+
dom.ShadowRoot shadowRoot = element.createShadowRoot();
142+
shadowRoot
132143
..applyAuthorStyles = _component.applyAuthorStyles
133144
..resetStyleInheritance = _component.resetStyleInheritance;
134145

135-
var shadowScope = scope.createChild(new HashMap()); // Isolate
146+
List<async.Future> futures = <async.Future>[];
147+
TemplateLoader templateLoader = new TemplateLoader(shadowRoot, futures);
148+
shadowInjector = new ShadowDomComponentDirectiveInjector(
149+
injector, injector.appInjector, shadowScope, templateLoader, shadowRoot);
150+
shadowInjector.bindByKey(_ref.typeKey, _ref.factory, _ref.paramKeys,
151+
_ref.annotation.visibility);
152+
dom.Node firstViewNode = null;
153+
154+
// Load ngBase CSS
155+
if (_component.useNgBaseCss == true && baseCss.urls.isNotEmpty) {
156+
if (baseCss.styles == null) {
157+
futures.add(async.Future
158+
.wait(baseCss.urls.map(_urlToStyle))
159+
.then((List<dom.StyleElement> cssList) {
160+
baseCss.styles = cssList;
161+
_insertCss(cssList, shadowRoot, shadowRoot.firstChild);
162+
}));
163+
} else {
164+
_insertCss(baseCss.styles, shadowRoot, shadowRoot.firstChild);
165+
}
166+
}
136167

137-
async.Future<Iterable<dom.StyleElement>> cssFuture;
138-
if (_component.useNgBaseCss == true) {
139-
cssFuture = async.Future.wait([async.Future.wait(baseCss.urls.map(_styleFuture)), _styleElementsFuture]).then((twoLists) {
140-
assert(twoLists.length == 2);return []
141-
..addAll(twoLists[0])
142-
..addAll(twoLists[1]);
143-
});
144-
} else {
145-
cssFuture = _styleElementsFuture;
168+
if (_styleElementsFuture != null) {
169+
if (_styleElements == null) {
170+
futures.add(_styleElementsFuture .then((List<dom.StyleElement> styles) =>
171+
_insertCss(styles, shadowRoot, firstViewNode)));
172+
} else {
173+
_insertCss(_styleElements, shadowRoot);
174+
}
146175
}
147176

148-
ComponentDirectiveInjector shadowInjector;
149177

150-
TemplateLoader templateLoader = new TemplateLoader(cssFuture.then((Iterable<dom.StyleElement> cssList) {
151-
cssList.where((styleElement) => styleElement != null).forEach((styleElement) {
152-
shadowDom.append(styleElement.clone(true));
153-
});
154-
if (_viewFuture != null) {
155-
return _viewFuture.then((ViewFactory viewFactory) {
156-
if (shadowScope.isAttached) {
157-
shadowDom.nodes.addAll(viewFactory.call(shadowInjector.scope, shadowInjector).nodes);
158-
}
159-
return shadowDom;
160-
});
178+
if (_shadowViewFactoryFuture != null) {
179+
if (_shadowViewFactory == null) {
180+
futures.add(_shadowViewFactoryFuture.then((ViewFactory viewFactory) =>
181+
firstViewNode = _insertView(viewFactory, shadowRoot, shadowScope, shadowInjector)));
182+
} else {
183+
_insertView(_shadowViewFactory, shadowRoot, shadowScope, shadowInjector);
161184
}
162-
return shadowDom;
163-
}));
164-
165-
var probe;
166-
shadowInjector = new ShadowDomComponentDirectiveInjector(injector, injector.appInjector, shadowScope, templateLoader, shadowDom);
167-
shadowInjector.bindByKey(_ref.typeKey, _ref.factory, _ref.paramKeys, _ref.annotation.visibility);
185+
}
168186

169187
if (_componentFactory.config.elementProbeEnabled) {
170-
probe = _componentFactory.expando[shadowDom] = shadowInjector.elementProbe;
171-
shadowScope.on(ScopeEvent.DESTROY).listen((ScopeEvent) => _componentFactory.expando[shadowDom] = null);
188+
ElementProbe probe = _componentFactory.expando[shadowRoot] = shadowInjector.elementProbe;
189+
shadowScope.on(ScopeEvent.DESTROY).listen((ScopeEvent) => _componentFactory.expando[shadowRoot] = null);
172190
}
173191

174192
var controller = shadowInjector.getByKey(_ref.typeKey);
@@ -181,6 +199,36 @@ class BoundShadowDomComponentFactory implements BoundComponentFactory {
181199
}
182200
};
183201
}
202+
203+
_insertCss(List<dom.StyleElement> cssList,
204+
dom.ShadowRoot shadowRoot,
205+
[dom.Node insertBefore = null]) {
206+
var s = traceEnter(_ComponentFactory_styles);
207+
for(int i = 0; i < cssList.length; i++) {
208+
var styleElement = cssList[i];
209+
if (styleElement != null) {
210+
shadowRoot.insertBefore(styleElement.clone(true), insertBefore);
211+
}
212+
}
213+
traceLeave(s);
214+
}
215+
216+
dom.Node _insertView(ViewFactory viewFactory,
217+
dom.ShadowRoot shadowRoot,
218+
Scope shadowScope,
219+
ShadowDomComponentDirectiveInjector shadowInjector) {
220+
dom.Node first = null;
221+
if (shadowScope.isAttached) {
222+
View shadowView = viewFactory.call(shadowScope, shadowInjector);
223+
List<dom.Node> shadowViewNodes = shadowView.nodes;
224+
for (var j = 0; j < shadowViewNodes.length; j++) {
225+
var node = shadowViewNodes[j];
226+
if (j == 0) first = node;
227+
shadowRoot.append(node);
228+
}
229+
}
230+
return first;
231+
}
184232
}
185233

186234
class _ComponentAssetKey {

lib/core_dom/transcluding_component_factory.dart

Lines changed: 32 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -87,13 +87,14 @@ class BoundTranscludingComponentFactory implements BoundComponentFactory {
8787
final DirectiveMap _directives;
8888

8989
Component get _component => _ref.annotation as Component;
90-
async.Future<ViewFactory> _viewFuture;
90+
async.Future<ViewFactory> _viewFactoryFuture;
91+
ViewFactory _viewFactory;
9192

9293
BoundTranscludingComponentFactory(this._f, this._ref, this._directives) {
93-
_viewFuture = BoundComponentFactory._viewFuture(
94-
_component,
95-
_f.viewCache,
96-
_directives);
94+
_viewFactoryFuture = BoundComponentFactory._viewFactoryFuture(_component, _f.viewCache, _directives);
95+
if (_viewFactoryFuture != null) {
96+
_viewFactoryFuture.then((viewFactory) => _viewFactory = viewFactory);
97+
}
9798
}
9899

99100
List<Key> get callArgs => _CALL_ARGS;
@@ -110,51 +111,39 @@ class BoundTranscludingComponentFactory implements BoundComponentFactory {
110111
ViewCache viewCache, Http http, TemplateCache templateCache,
111112
DirectiveMap directives, NgBaseCss baseCss, EventHandler eventHandler) {
112113

113-
DirectiveInjector childInjector;
114-
var childInjectorCompleter; // Used if the ViewFuture is available before the childInjector.
115-
116-
var component = _component;
114+
List<async.Future> futures = [];
117115
var contentPort = new ContentPort(element);
118-
119-
// Append the component's template as children
120-
var elementFuture;
121-
122-
if (_viewFuture != null) {
123-
elementFuture = _viewFuture.then((ViewFactory viewFactory) {
124-
contentPort.pullNodes();
125-
if (childInjector != null) {
126-
element.nodes.addAll(
127-
viewFactory.call(childInjector.scope, childInjector).nodes);
128-
return element;
129-
} else {
130-
childInjectorCompleter = new async.Completer();
131-
return childInjectorCompleter.future.then((childInjector) {
132-
element.nodes.addAll(
133-
viewFactory.call(childInjector.scope, childInjector).nodes);
134-
return element;
135-
});
136-
}
137-
});
138-
} else {
139-
elementFuture = new async.Future.microtask(() => contentPort.pullNodes());
140-
}
141-
TemplateLoader templateLoader = new TemplateLoader(elementFuture);
142-
116+
TemplateLoader templateLoader = new TemplateLoader(element, futures);
143117
Scope shadowScope = scope.createChild(new HashMap());
144-
145-
childInjector = new ShadowlessComponentDirectiveInjector(injector, injector.appInjector,
146-
eventHandler, shadowScope, templateLoader, new ShadowlessShadowRoot(element),
147-
contentPort);
118+
DirectiveInjector childInjector = new ShadowlessComponentDirectiveInjector(
119+
injector, injector.appInjector, eventHandler, shadowScope, templateLoader,
120+
new ShadowlessShadowRoot(element), contentPort);
148121
childInjector.bindByKey(_ref.typeKey, _ref.factory, _ref.paramKeys, _ref.annotation.visibility);
149122

150-
if (childInjectorCompleter != null) {
151-
childInjectorCompleter.complete(childInjector);
152-
}
153-
154123
var controller = childInjector.getByKey(_ref.typeKey);
155-
shadowScope.context[component.publishAs] = controller;
124+
shadowScope.context[_component.publishAs] = controller;
156125
BoundComponentFactory._setupOnShadowDomAttach(controller, templateLoader, shadowScope);
126+
127+
if (_viewFactoryFuture != null && _viewFactory == null) {
128+
futures.add(_viewFactoryFuture.then((ViewFactory viewFactory) =>
129+
_insert(viewFactory, element, childInjector, contentPort)));
130+
} else {
131+
scope.rootScope.runAsync(() {
132+
_insert(_viewFactory, element, childInjector, contentPort);
133+
});
134+
}
157135
return controller;
158136
};
159137
}
138+
139+
_insert(ViewFactory viewFactory, dom.Element element, DirectiveInjector childInjector,
140+
ContentPort contentPort) {
141+
contentPort.pullNodes();
142+
if (viewFactory != null) {
143+
var viewNodes = viewFactory.call(childInjector.scope, childInjector).nodes;
144+
for(var i = 0; i < viewNodes.length; i++) {
145+
element.append(viewNodes[i]);
146+
}
147+
}
148+
}
160149
}

lib/core_dom/view_factory.dart

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -94,10 +94,12 @@ class ViewFactory implements Function {
9494
}
9595
elementInjectors[elementBinderIndex] = elementInjector;
9696

97-
if (tagged.textBinders != null) {
98-
for (var k = 0; k < tagged.textBinders.length; k++) {
99-
TaggedTextBinder taggedText = tagged.textBinders[k];
100-
var childNode = boundNode.childNodes[taggedText.offsetIndex];
97+
var textBinders = tagged.textBinders;
98+
if (textBinders != null && textBinders.length > 0) {
99+
var childNodes = boundNode.childNodes;
100+
for (var k = 0; k < textBinders.length; k++) {
101+
TaggedTextBinder taggedText = textBinders[k];
102+
var childNode = childNodes[taggedText.offsetIndex];
101103
taggedText.binder.bind(view, scope, elementInjector, childNode, eventHandler, animate);
102104
}
103105
}
@@ -113,15 +115,6 @@ class ViewFactory implements Function {
113115
dom.Node node = nodeList[i];
114116
NodeLinkingInfo linkingInfo = nodeLinkingInfos[i];
115117

116-
// if node isn't attached to the DOM, create a parent for it.
117-
var parentNode = node.parentNode;
118-
var fakeParent = false;
119-
if (parentNode == null) {
120-
fakeParent = true;
121-
parentNode = new dom.DivElement();
122-
parentNode.append(node);
123-
}
124-
125118
if (linkingInfo.isElement) {
126119
if (linkingInfo.containsNgBinding) {
127120
var tagged = elementBinders[elementBinderIndex];
@@ -149,11 +142,6 @@ class ViewFactory implements Function {
149142
}
150143
elementBinderIndex++;
151144
}
152-
153-
if (fakeParent) {
154-
// extract the node from the parentNode.
155-
nodeList[i] = parentNode.nodes[0];
156-
}
157145
}
158146
return view;
159147
}

lib/core_dom/web_platform.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ class WebPlatform {
4242
//
4343
// TODO Remove the try-catch once https://github.com/angular/angular.dart/issues/1189 is fixed.
4444
try {
45-
root.querySelectorAll("*").forEach((n) => n.attributes[selector] = "");
45+
root.querySelectorAll("*").forEach((dom.Element n) => n.setAttribute(selector, ""));
4646
} catch (e, s) {
4747
print("WARNING: Failed to set up Shadow DOM shim for $selector.\n$e\n$s");
4848
}
@@ -69,6 +69,7 @@ class PlatformViewCache implements ViewCache {
6969
if (selector != null && selector != "" && platform.shadowDomShimRequired) {
7070
// By adding a comment with the tag name we ensure the template html is unique per selector
7171
// name when used as a key in the view factory cache.
72+
//TODO(misko): This will always be miss, since we never put it in cache under such key.
7273
viewFactory = viewFactoryCache.get("<!-- Shimmed template for: <$selector> -->$html");
7374
} else {
7475
viewFactory = viewFactoryCache.get(html);

lib/directive/ng_base_css.dart

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,14 @@ part of angular.directive;
1212
selector: '[ng-base-css]',
1313
visibility: Visibility.CHILDREN)
1414
class NgBaseCss {
15+
List<dom.StyleElement> styles;
1516
List<String> _urls = const [];
1617

1718
@NgAttr('ng-base-css')
18-
set urls(v) => _urls = v is List ? v : [v];
19+
set urls(v) {
20+
_urls = v is List ? v : [v];
21+
styles = null;
22+
}
1923

2024
List<String> get urls => _urls;
2125
}

0 commit comments

Comments
 (0)