Skip to content

Commit ed93edb

Browse files
mheveryvsavkin
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 3983b5f commit ed93edb

File tree

9 files changed

+136
-68
lines changed

9 files changed

+136
-68
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: 57 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,9 @@ abstract class BoundComponentFactory {
1111
List<Key> get callArgs;
1212
Function call(dom.Element element);
1313

14-
static async.Future<ViewFactory> _viewFuture(
14+
static async.Future<ViewFactory> _viewFactoryFuture(
1515
Component component, ViewCache viewCache, DirectiveMap directives,
1616
TypeToUriMapper uriMapper, ResourceUrlResolver resourceResolver, Type type) {
17-
1817
if (component.template != null) {
1918
// TODO(chirayu): Replace this line with
2019
// var baseUri = uriMapper.uriForType(type);
@@ -77,8 +76,10 @@ class BoundShadowDomComponentFactory implements BoundComponentFactory {
7776
Component get _component => _ref.annotation as Component;
7877

7978
String _tag;
80-
async.Future<Iterable<dom.StyleElement>> _styleElementsFuture;
81-
async.Future<ViewFactory> _viewFuture;
79+
async.Future<List<dom.StyleElement>> _styleElementsFuture;
80+
List<dom.StyleElement> _styleElements;
81+
async.Future<ViewFactory> _shadowViewFactoryFuture;
82+
ViewFactory _shadowViewFactory;
8283

8384
BoundShadowDomComponentFactory(this._componentFactory, this._ref,
8485
DirectiveMap directives, this._injector) {
@@ -87,13 +88,20 @@ class BoundShadowDomComponentFactory implements BoundComponentFactory {
8788

8889
final viewCache = new ShimmingViewCache(_componentFactory.viewCache,
8990
_tag, _componentFactory.platformShim);
90-
_viewFuture = BoundComponentFactory._viewFuture(_component, viewCache, directives,
91-
_componentFactory.uriMapper, _componentFactory.resourceResolver, _ref.type);
91+
92+
_shadowViewFactoryFuture = BoundComponentFactory._viewFactoryFuture(_component,
93+
viewCache, directives, _componentFactory.uriMapper,
94+
_componentFactory.resourceResolver, _ref.type);
95+
96+
if (_shadowViewFactoryFuture != null) {
97+
_shadowViewFactoryFuture.then((viewFactory) => _shadowViewFactory = viewFactory);
98+
}
9299
}
93100

94101
List<Key> get callArgs => _CALL_ARGS;
95102
static final _CALL_ARGS = [DIRECTIVE_INJECTOR_KEY, SCOPE_KEY, VIEW_KEY, NG_BASE_CSS_KEY,
96103
SHADOW_BOUNDARY_KEY];
104+
97105
Function call(dom.Element element) {
98106
return (DirectiveInjector injector, Scope scope, View view, NgBaseCss baseCss,
99107
ShadowBoundary parentShadowBoundary) {
@@ -108,40 +116,54 @@ class BoundShadowDomComponentFactory implements BoundComponentFactory {
108116
shadowBoundary = new ShadowRootBoundary(shadowDom);
109117
}
110118

111-
//_styleFuture(cssUrl, resolveUri: false)
112119
var shadowScope = scope.createChild(new HashMap()); // Isolate
113120
ComponentDirectiveInjector shadowInjector;
114121

115-
final baseUrls = (_component.useNgBaseCss) ? baseCss.urls : [];
116-
final baseUrlsFuture = _componentFactory.cssLoader(_tag, baseUrls);
117-
final cssFuture = mergeFutures(baseUrlsFuture, _styleElementsFuture);
118-
119-
async.Future<dom.Node> initShadowDom(_) {
120-
if (_viewFuture == null) return new async.Future.value(shadowDom);
121-
return _viewFuture.then((ViewFactory viewFactory) {
122-
if (shadowScope.isAttached) {
123-
shadowDom.nodes.addAll(
124-
viewFactory.call(shadowInjector.scope, shadowInjector).nodes);
125-
}
126-
return shadowDom;
127-
});
122+
List<async.Future> futures = <async.Future>[];
123+
124+
if (_component.useNgBaseCss && baseCss.urls.isNotEmpty) {
125+
if (baseCss.styles == null) {
126+
final f = _componentFactory.cssLoader(_tag, baseCss.urls).then((cssList) {
127+
baseCss.styles = cssList;
128+
shadowBoundary.insertStyleElements(cssList);
129+
});
130+
futures.add(f);
131+
} else {
132+
shadowBoundary.insertStyleElements(cssList);
133+
}
128134
}
129135

130-
TemplateLoader templateLoader = new TemplateLoader(
131-
cssFuture.then(shadowBoundary.insertStyleElements).then(initShadowDom));
136+
if (_styleElementsFuture != null) {
137+
if (_styleElements == null) {
138+
final f = _styleElementsFuture.then(shadowBoundary.insertStyleElements);
139+
futures.add(f);
140+
} else {
141+
shadowBoundary.insertStyleElements(_styleElements);
142+
}
143+
}
144+
145+
if (_shadowViewFactoryFuture != null) {
146+
if (_shadowViewFactory == null) {
147+
futures.add(_shadowViewFactoryFuture.then((ViewFactory viewFactory) =>
148+
firstViewNode = _insertView(viewFactory, shadowRoot, shadowScope, shadowInjector)));
149+
} else {
150+
_insertView(_shadowViewFactory, shadowRoot, shadowScope, shadowInjector);
151+
}
152+
}
153+
154+
TemplateLoader templateLoader = new TemplateLoader(shadowRoot, futures);
132155

133156
var probe;
134157
var eventHandler = new ShadowRootEventHandler(
135158
shadowDom, injector.getByKey(EXPANDO_KEY), injector.getByKey(EXCEPTION_HANDLER_KEY));
136159
shadowInjector = new ComponentDirectiveInjector(injector, _injector, eventHandler, shadowScope,
137160
templateLoader, shadowDom, null, view, shadowBoundary);
138161

139-
140162
shadowInjector.bindByKey(_ref.typeKey, _ref.factory, _ref.paramKeys, _ref.annotation.visibility);
141163

142164
if (_componentFactory.config.elementProbeEnabled) {
143-
probe = _componentFactory.expando[shadowDom] = shadowInjector.elementProbe;
144-
shadowScope.on(ScopeEvent.DESTROY).listen((ScopeEvent) => _componentFactory.expando[shadowDom] = null);
165+
ElementProbe probe = _componentFactory.expando[shadowRoot] = shadowInjector.elementProbe;
166+
shadowScope.on(ScopeEvent.DESTROY).listen((ScopeEvent) => _componentFactory.expando[shadowRoot] = null);
145167
}
146168

147169
var controller = shadowInjector.getByKey(_ref.typeKey);
@@ -154,6 +176,16 @@ class BoundShadowDomComponentFactory implements BoundComponentFactory {
154176
}
155177
};
156178
}
179+
180+
dom.Node _insertView(ViewFactory viewFactory,
181+
dom.ShadowRoot shadowRoot,
182+
Scope shadowScope,
183+
ComponentDirectiveInjector shadowInjector) {
184+
if (shadowScope.isAttached) {
185+
shadowRoot.nodes.addAll(
186+
viewFactory.call(shadowInjector.scope, shadowInjector).nodes);
187+
}
188+
}
157189
}
158190

159191
@Injectable()

lib/core_dom/transcluding_component_factory.dart

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -36,23 +36,27 @@ class BoundTranscludingComponentFactory implements BoundComponentFactory {
3636
async.Future<Iterable<dom.StyleElement>> _styleElementsFuture;
3737

3838
Component get _component => _ref.annotation as Component;
39-
async.Future<ViewFactory> _viewFuture;
39+
async.Future<ViewFactory> _viewFactoryFuture;
40+
ViewFactory _viewFactory;
4041

4142
BoundTranscludingComponentFactory(this._f, this._ref, this._directives, this._injector) {
42-
_viewFuture = BoundComponentFactory._viewFuture(
43+
final viewCache = new ShimmingViewCache(_f.viewCache, _tag, _f.platformShim);
44+
45+
_viewFactoryFuture = BoundComponentFactory._viewFuture(
4346
_component,
44-
_f.viewCache,
47+
viewCache,
4548
_directives,
4649
_f.uriMapper,
4750
_f.resourceResolver,
4851
_ref.type);
49-
52+
5053
_tag = _ref.annotation.selector.toLowerCase();
51-
_styleElementsFuture = _f.cssLoader(_tag, _component.cssUrls, type: _ref.type);
5254

53-
final viewCache = new ShimmingViewCache(_f.viewCache, _tag, _f.platformShim);
54-
_viewFuture = BoundComponentFactory._viewFuture(_component, viewCache, _directives,
55-
_f.uriMapper, _f.resourceResolver, _ref.type);
55+
if (_viewFactoryFuture != null) {
56+
_viewFactoryFuture.then((viewFactory) => _viewFactory = viewFactory);
57+
}
58+
59+
_styleElementsFuture = _f.cssLoader(_tag, _component.cssUrls, type: _ref.type);
5660
}
5761

5862
List<Key> get callArgs => _CALL_ARGS;
@@ -62,7 +66,8 @@ class BoundTranscludingComponentFactory implements BoundComponentFactory {
6266
SHADOW_BOUNDARY_KEY];
6367
Function call(dom.Node node) {
6468
var element = node as dom.Element;
65-
return (DirectiveInjector injector, Scope scope, View view,
69+
var component = _component;
70+
return (DirectiveInjector injector, Scope scope,
6671
ViewCache viewCache, Http http, TemplateCache templateCache,
6772
DirectiveMap directives, NgBaseCss baseCss, EventHandler eventHandler,
6873
ShadowBoundary shadowBoundary) {
@@ -108,14 +113,30 @@ class BoundTranscludingComponentFactory implements BoundComponentFactory {
108113

109114
childInjector.bindByKey(_ref.typeKey, _ref.factory, _ref.paramKeys, _ref.annotation.visibility);
110115

111-
if (childInjectorCompleter != null) {
112-
childInjectorCompleter.complete(childInjector);
113-
}
114-
115116
var controller = childInjector.getByKey(_ref.typeKey);
116117
shadowScope.context[component.publishAs] = controller;
117118
BoundComponentFactory._setupOnShadowDomAttach(controller, templateLoader, shadowScope);
119+
120+
if (_viewFactoryFuture != null && _viewFactory == null) {
121+
futures.add(_viewFactoryFuture.then((ViewFactory viewFactory) =>
122+
_insert(viewFactory, element, childInjector, contentPort)));
123+
} else {
124+
scope.rootScope.runAsync(() {
125+
_insert(_viewFactory, element, childInjector, contentPort);
126+
});
127+
}
118128
return controller;
119129
};
120130
}
131+
132+
_insert(ViewFactory viewFactory, dom.Element element, DirectiveInjector childInjector,
133+
ContentPort contentPort) {
134+
contentPort.pullNodes();
135+
if (viewFactory != null) {
136+
var viewNodes = viewFactory.call(childInjector.scope, childInjector).nodes;
137+
for(var i = 0; i < viewNodes.length; i++) {
138+
element.append(viewNodes[i]);
139+
}
140+
}
141+
}
121142
}

lib/core_dom/view_factory.dart

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

87-
if (tagged.textBinders != null) {
88-
for (var k = 0; k < tagged.textBinders.length; k++) {
89-
TaggedTextBinder taggedText = tagged.textBinders[k];
90-
var childNode = boundNode.childNodes[taggedText.offsetIndex];
87+
var textBinders = tagged.textBinders;
88+
if (textBinders != null && textBinders.length > 0) {
89+
var childNodes = boundNode.childNodes;
90+
for (var k = 0; k < textBinders.length; k++) {
91+
TaggedTextBinder taggedText = textBinders[k];
92+
var childNode = childNodes[taggedText.offsetIndex];
9193
taggedText.binder.bind(view, scope, elementInjector, childNode);
9294
}
9395
}
@@ -102,15 +104,6 @@ class ViewFactory implements Function {
102104
dom.Node node = nodeList[i];
103105
NodeLinkingInfo linkingInfo = nodeLinkingInfos[i];
104106

105-
// if node isn't attached to the DOM, create a parent for it.
106-
var parentNode = node.parentNode;
107-
var fakeParent = false;
108-
if (parentNode == null) {
109-
fakeParent = true;
110-
parentNode = new dom.DivElement();
111-
parentNode.append(node);
112-
}
113-
114107
if (linkingInfo.isElement) {
115108
if (linkingInfo.containsNgBinding) {
116109
var tagged = elementBinders[elementBinderIndex];
@@ -136,11 +129,6 @@ class ViewFactory implements Function {
136129
}
137130
elementBinderIndex++;
138131
}
139-
140-
if (fakeParent) {
141-
// extract the node from the parentNode.
142-
nodeList[i] = parentNode.nodes[0];
143-
}
144132
}
145133
return view;
146134
}

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
}
@@ -72,6 +72,7 @@ class PlatformViewCache implements ViewCache {
7272
if (selector != null && selector != "" && platform.shadowDomShimRequired) {
7373
// By adding a comment with the tag name we ensure the template html is unique per selector
7474
// name when used as a key in the view factory cache.
75+
//TODO(misko): This will always be miss, since we never put it in cache under such key.
7576
viewFactory = viewFactoryCache.get("<!-- Shimmed template for: <$selector> -->$html");
7677
} else {
7778
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
}

lib/ng_tracing_scopes.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,13 @@ final View_create = traceCreateScope('View#create(ascii html)');
171171
*/
172172
final View_createComponent = traceCreateScope('View#createComponent()');
173173

174+
/**
175+
* Name: `View#styles()`
176+
*
177+
* Designates where styles are inserted into component.
178+
*/
179+
final View_styles = traceCreateScope('View#styles()');
180+
174181
/**
175182
* Name: `Directive#create(ascii name)`
176183
*

test/core_dom/compiler_spec.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ forAllCompilersAndComponentFactories(fn) {
3636

3737
void main() {
3838
forAllCompilersAndComponentFactories((compilerType) =>
39-
describe('dte.compiler', () {
39+
ddescribe('dte.compiler', () {
4040
TestBed _;
4141

4242
beforeEachModule((Module module) {
@@ -1041,7 +1041,7 @@ void main() {
10411041
}));
10421042

10431043
/*
1044-
This test is dissabled becouse I (misko) thinks it has no real use case. It is easier
1044+
This test is disabled because I (misko) thinks it has no real use case. It is easier
10451045
to understand in terms of ng-repeat
10461046
10471047
<tabs>
@@ -1055,7 +1055,7 @@ void main() {
10551055
the DirectChild between tabs and pane.
10561056
10571057
It is not clear to me (misko) that there is a use case for getting hold of the
1058-
tranrscluding directive such a ng-repeat.
1058+
transcluding directive such a ng-repeat.
10591059
*/
10601060
xit('should reuse controllers for transclusions', async((Logger log) {
10611061
_.compile('<div simple-transclude-in-attach include-transclude>view</div>');

test/directive/ng_base_css_spec.dart

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,15 @@ main() => describe('NgBaseCss', () {
3333
..flushGET('base.css').respond(200, '.base{}');
3434
microLeap();
3535

36-
expect(element.children[0].shadowRoot).toHaveHtml(
37-
'<style>.base{}</style><style>.simple{}</style><div>Simple!</div>'
38-
);
36+
expect((view.nodes[0].firstChild as Element).shadowRoot).toHaveHtml(
37+
'<style>.base{}</style><style>.simple{}</style><div>Simple!</div>');
38+
expect(ngBaseCss.styles.first.innerHtml).toEqual('.base{}');
39+
40+
// Now it should be sync
41+
view = viewFactory.call(_.rootScope, directiveInjector);
42+
expect((view.nodes[0].firstChild as Element).shadowRoot).toHaveHtml(
43+
'<style>.base{}</style><style>.simple{}</style><div>Simple!</div>');
44+
3945
}));
4046

4147
it('ng-base-css should overwrite parent ng-base-csses', async((TestBed _, MockHttpBackend backend) {

0 commit comments

Comments
 (0)