Skip to content

Commit 166a5d7

Browse files
jbdeboerDiana Salsbury
authored and
Diana Salsbury
committed
perf(compiler): +6% Pre-compute ViewFactories, styles for components.
This sped up the TreeComponent benchmark by 6%. Closes dart-archive#1134
1 parent 3d3db9a commit 166a5d7

File tree

7 files changed

+150
-99
lines changed

7 files changed

+150
-99
lines changed

lib/core_dom/element_binder.dart

Lines changed: 8 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,10 @@ class TemplateElementBinder extends ElementBinder {
1414
return _directiveCache = [template];
1515
}
1616

17-
TemplateElementBinder(perf, expando, parser, config, componentFactory,
18-
transcludingComponentFactory, shadowDomComponentFactory,
17+
TemplateElementBinder(perf, expando, parser, config,
1918
this.template, this.templateBinder,
2019
onEvents, bindAttrs, childMode)
21-
: super(perf, expando, parser, config, componentFactory,
22-
transcludingComponentFactory, shadowDomComponentFactory,
20+
: super(perf, expando, parser, config,
2321
null, null, onEvents, bindAttrs, childMode);
2422

2523
String toString() => "[TemplateElementBinder template:$template]";
@@ -47,26 +45,19 @@ class ElementBinder {
4745
final Parser _parser;
4846
final CompilerConfig _config;
4947

50-
// The default component factory
51-
final ComponentFactory _componentFactory;
52-
final TranscludingComponentFactory _transcludingComponentFactory;
53-
final ShadowDomComponentFactory _shadowDomComponentFactory;
5448
final Map onEvents;
5549
final Map bindAttrs;
5650

5751
// Member fields
5852
final decorators;
5953

60-
final DirectiveRef component;
54+
final BoundComponentData componentData;
6155

6256
// Can be either COMPILE_CHILDREN or IGNORE_CHILDREN
6357
final String childMode;
6458

6559
ElementBinder(this._perf, this._expando, this._parser, this._config,
66-
this._componentFactory,
67-
this._transcludingComponentFactory,
68-
this._shadowDomComponentFactory,
69-
this.component, this.decorators,
60+
this.componentData, this.decorators,
7061
this.onEvents, this.bindAttrs, this.childMode);
7162

7263
final bool hasTemplate = false;
@@ -77,7 +68,7 @@ class ElementBinder {
7768
var _directiveCache;
7869
List<DirectiveRef> get _usableDirectiveRefs {
7970
if (_directiveCache != null) return _directiveCache;
80-
if (component != null) return _directiveCache = new List.from(decorators)..add(component);
71+
if (componentData != null) return _directiveCache = new List.from(decorators)..add(componentData.ref);
8172
return _directiveCache = decorators;
8273
}
8374

@@ -260,16 +251,9 @@ class ElementBinder {
260251
}
261252
nodesAttrsDirectives.add(ref);
262253
} else if (ref.annotation is Component) {
263-
var factory;
264-
var annotation = ref.annotation as Component;
265-
if (annotation.useShadowDom == true) {
266-
factory = _shadowDomComponentFactory;
267-
} else if (annotation.useShadowDom == false) {
268-
factory = _transcludingComponentFactory;
269-
} else {
270-
factory = _componentFactory;
271-
}
272-
nodeModule.bindByKey(ref.typeKey, toFactory: factory.call(node, ref), visibility: visibility);
254+
assert(ref == componentData.ref);
255+
256+
nodeModule.bindByKey(ref.typeKey, toFactory: componentData.factory.call(node), visibility: visibility);
273257
} else {
274258
nodeModule.bindByKey(ref.typeKey, visibility: visibility);
275259
}

lib/core_dom/element_binder_builder.dart

Lines changed: 56 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,29 +6,25 @@ class ElementBinderFactory {
66
final Profiler _perf;
77
final CompilerConfig _config;
88
final Expando _expando;
9-
final ComponentFactory _componentFactory;
10-
final TranscludingComponentFactory _transcludingComponentFactory;
11-
final ShadowDomComponentFactory _shadowDomComponentFactory;
12-
final ASTParser _astParser;
9+
final ASTParser astParser;
10+
final ComponentFactory componentFactory;
11+
final ShadowDomComponentFactory shadowDomComponentFactory;
12+
final TranscludingComponentFactory transcludingComponentFactory;
1313

1414
ElementBinderFactory(this._parser, this._perf, this._config, this._expando,
15-
this._componentFactory,
16-
this._transcludingComponentFactory,
17-
this._shadowDomComponentFactory,
18-
this._astParser);
15+
this.astParser, this.componentFactory, this.shadowDomComponentFactory, this.transcludingComponentFactory);
1916

2017
// TODO: Optimize this to re-use a builder.
21-
ElementBinderBuilder builder(FormatterMap formatters) =>
22-
new ElementBinderBuilder(this, _astParser, formatters);
18+
ElementBinderBuilder builder(FormatterMap formatters, DirectiveMap directives) =>
19+
new ElementBinderBuilder(this,formatters, directives);
2320

2421
ElementBinder binder(ElementBinderBuilder b) =>
25-
new ElementBinder(_perf, _expando, _parser, _config, _componentFactory,
26-
_transcludingComponentFactory, _shadowDomComponentFactory,
27-
b.component, b.decorators, b.onEvents, b.bindAttrs, b.childMode);
22+
23+
new ElementBinder(_perf, _expando, _parser, _config,
24+
b.componentData, b.decorators, b.onEvents, b.bindAttrs, b.childMode);
2825

2926
TemplateElementBinder templateBinder(ElementBinderBuilder b, ElementBinder transclude) =>
30-
new TemplateElementBinder(_perf, _expando, _parser, _config, _componentFactory,
31-
_transcludingComponentFactory, _shadowDomComponentFactory,
27+
new TemplateElementBinder(_perf, _expando, _parser, _config,
3228
b.template, transclude, b.onEvents, b.bindAttrs, b.childMode);
3329
}
3430

@@ -39,9 +35,9 @@ class ElementBinderFactory {
3935
class ElementBinderBuilder {
4036
static final RegExp _MAPPING = new RegExp(r'^(@|=>!|=>|<=>|&)\s*(.*)$');
4137

42-
ElementBinderFactory _factory;
43-
ASTParser _astParser;
44-
FormatterMap _formatters;
38+
final ElementBinderFactory _factory;
39+
final DirectiveMap _directives;
40+
final FormatterMap _formatters;
4541

4642
/// "on-*" attribute names and values, added by a [DirectiveSelector]
4743
final onEvents = <String, String>{};
@@ -50,12 +46,12 @@ class ElementBinderBuilder {
5046

5147
final decorators = <DirectiveRef>[];
5248
DirectiveRef template;
53-
DirectiveRef component;
49+
BoundComponentData componentData;
5450

5551
// Can be either COMPILE_CHILDREN or IGNORE_CHILDREN
5652
String childMode = Directive.COMPILE_CHILDREN;
5753

58-
ElementBinderBuilder(this._factory, this._astParser, this._formatters);
54+
ElementBinderBuilder(this._factory, this._formatters, this._directives);
5955

6056
/**
6157
* Adds [DirectiveRef]s to this [ElementBinderBuilder].
@@ -73,7 +69,17 @@ class ElementBinderBuilder {
7369
if (annotation.children == Directive.TRANSCLUDE_CHILDREN) {
7470
template = ref;
7571
} else if (annotation is Component) {
76-
component = ref;
72+
ComponentFactory factory;
73+
var annotation = ref.annotation as Component;
74+
if (annotation.useShadowDom == true) {
75+
factory = _factory.shadowDomComponentFactory;
76+
} else if (annotation.useShadowDom == false) {
77+
factory = _factory.transcludingComponentFactory;
78+
} else {
79+
factory = _factory.componentFactory;
80+
}
81+
82+
componentData = new BoundComponentData(ref, () => factory.bind(ref, _directives));
7783
} else {
7884
decorators.add(ref);
7985
}
@@ -92,14 +98,14 @@ class ElementBinderBuilder {
9298
var dstPath = match[2];
9399

94100
String dstExpression = dstPath.isEmpty ? attrName : dstPath;
95-
AST dstAST = _astParser(dstExpression); // no formatters
101+
AST dstAST = _factory.astParser(dstExpression); // no formatters
96102

97103
// Look up the value of attrName and compute an AST
98104
AST ast;
99105
if (mode != '@' && mode != '&') {
100106
var value = attrName == "." ? ref.value : (ref.element as dom.Element).attributes[attrName];
101107
if (value == null || value.isEmpty) { value = "''"; }
102-
ast = _astParser(value, formatters: _formatters);
108+
ast = _factory.astParser(value, formatters: _formatters);
103109
}
104110

105111
ref.mappings.add(new MappingParts(attrName, ast, mode, dstAST, mapping));
@@ -113,3 +119,30 @@ class ElementBinderBuilder {
113119
return template == null ? elBinder : _factory.templateBinder(this, elBinder);
114120
}
115121
}
122+
123+
/**
124+
* Data used by the ComponentFactory to construct components.
125+
*/
126+
class BoundComponentData {
127+
final DirectiveRef ref;
128+
BoundComponentFactory _instance;
129+
Function _gen;
130+
BoundComponentFactory get factory {
131+
if (_instance != null) return _instance;
132+
_instance = _gen();
133+
_gen = null; // Clear the gen function for GC.
134+
return _instance;
135+
}
136+
137+
Component get component => ref.annotation as Component;
138+
@Deprecated('Use typeKey instead')
139+
Type get type => ref.type;
140+
Key get typeKey => ref.typeKey;
141+
142+
143+
/**
144+
* * [ref]: The components directive ref
145+
* * [_gen]: A function which returns a [BoundComponentFactory]. Called lazily.
146+
*/
147+
BoundComponentData(this.ref, this._gen);
148+
}

lib/core_dom/selector.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ class DirectiveSelector {
5959
ElementBinder matchElement(dom.Node node) {
6060
assert(node is dom.Element);
6161

62-
ElementBinderBuilder builder = _binderFactory.builder(_formatters);
62+
ElementBinderBuilder builder = _binderFactory.builder(_formatters, _directives);
6363
List<_ElementSelector> partialSelection;
6464
final classes = new Set<String>();
6565
final attrs = <String, String>{};
@@ -129,7 +129,7 @@ class DirectiveSelector {
129129
}
130130

131131
ElementBinder matchText(dom.Node node) {
132-
ElementBinderBuilder builder = _binderFactory.builder(_formatters);
132+
ElementBinderBuilder builder = _binderFactory.builder(_formatters, _directives);
133133

134134
var value = node.nodeValue;
135135
for (var k = 0; k < textSelector.length; k++) {
@@ -148,7 +148,7 @@ class DirectiveSelector {
148148
return builder.binder;
149149
}
150150

151-
ElementBinder matchComment(dom.Node node) => _binderFactory.builder(null).binder;
151+
ElementBinder matchComment(dom.Node node) => _binderFactory.builder(null, null).binder;
152152
}
153153

154154
/**

lib/core_dom/transcluding_component_factory.dart

Lines changed: 50 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -70,20 +70,44 @@ class ContentPort {
7070

7171
@Injectable()
7272
class TranscludingComponentFactory implements ComponentFactory {
73-
final Expando _expando;
74-
final CompilerConfig _config;
7573

76-
TranscludingComponentFactory(this._expando, this._config);
74+
final Expando expando;
75+
final ViewCache viewCache;
76+
final CompilerConfig config;
7777

78-
FactoryFn call(dom.Node node, DirectiveRef ref) {
78+
TranscludingComponentFactory(this.expando, this.viewCache, this.config);
79+
80+
bind(DirectiveRef ref, directives) =>
81+
new BoundTranscludingComponentFactory(this, ref, directives);
82+
}
83+
84+
class BoundTranscludingComponentFactory implements BoundComponentFactory {
85+
final TranscludingComponentFactory _f;
86+
final DirectiveRef _ref;
87+
final DirectiveMap _directives;
88+
89+
Component get _component => _ref.annotation as Component;
90+
async.Future<ViewFactory> _viewFuture;
91+
92+
BoundTranscludingComponentFactory(this._f, this._ref, this._directives) {
93+
_viewFuture = BoundComponentFactory._viewFuture(
94+
_component,
95+
_f.viewCache,
96+
_directives);
97+
}
98+
99+
FactoryFn call(dom.Node node) {
79100
// CSS is not supported.
80-
assert((ref.annotation as Component).cssUrls == null ||
81-
(ref.annotation as Component).cssUrls.isEmpty);
101+
assert(_component.cssUrls == null ||
102+
_component.cssUrls.isEmpty);
82103

83104
var element = node as dom.Element;
84105
return (Injector injector) {
106+
85107
var childInjector;
86-
var component = ref.annotation as Component;
108+
var childInjectorCompleter; // Used if the ViewFuture is available before the childInjector.
109+
110+
var component = _component;
87111
Scope scope = injector.getByKey(SCOPE_KEY);
88112
ViewCache viewCache = injector.getByKey(VIEW_CACHE_KEY);
89113
Http http = injector.getByKey(HTTP_KEY);
@@ -94,14 +118,21 @@ class TranscludingComponentFactory implements ComponentFactory {
94118
var contentPort = new ContentPort(element);
95119

96120
// Append the component's template as children
97-
var viewFuture = ComponentFactory._viewFuture(component, viewCache, directives);
98121
var elementFuture;
99122

100-
if (viewFuture != null) {
101-
elementFuture = viewFuture.then((ViewFactory viewFactory) {
123+
if (_viewFuture != null) {
124+
elementFuture = _viewFuture.then((ViewFactory viewFactory) {
102125
contentPort.pullNodes();
103-
element.nodes.addAll(viewFactory(childInjector).nodes);
104-
return element;
126+
if (childInjector != null) {
127+
element.nodes.addAll(viewFactory(childInjector).nodes);
128+
return element;
129+
} else {
130+
childInjectorCompleter = new async.Completer();
131+
return childInjectorCompleter.future.then((childInjector) {
132+
element.nodes.addAll(viewFactory(childInjector).nodes);
133+
return element;
134+
});
135+
}
105136
});
106137
} else {
107138
elementFuture = new async.Future.microtask(() => contentPort.pullNodes());
@@ -112,21 +143,24 @@ class TranscludingComponentFactory implements ComponentFactory {
112143

113144
var probe;
114145
var childModule = new Module()
115-
..bind(ref.type)
146+
..bind(_ref.type)
116147
..bind(NgElement)
117148
..bind(ContentPort, toValue: contentPort)
118149
..bind(Scope, toValue: shadowScope)
119150
..bind(TemplateLoader, toValue: templateLoader)
120151
..bind(dom.ShadowRoot, toValue: new ShadowlessShadowRoot(element));
121152

122-
if (_config.elementProbeEnabled) {
153+
if (_f.config.elementProbeEnabled) {
123154
childModule.bind(ElementProbe, toFactory: (_) => probe);
124155
}
125156
childInjector = injector.createChild([childModule], name: SHADOW_DOM_INJECTOR_NAME);
157+
if (childInjectorCompleter != null) {
158+
childInjectorCompleter.complete(childInjector);
159+
}
126160

127-
var controller = childInjector.get(ref.type);
161+
var controller = childInjector.get(_ref.type);
128162
shadowScope.context[component.publishAs] = controller;
129-
ComponentFactory._setupOnShadowDomAttach(controller, templateLoader, shadowScope);
163+
BoundComponentFactory._setupOnShadowDomAttach(controller, templateLoader, shadowScope);
130164
return controller;
131165
};
132166
}

test/core_dom/element_binder_builder_spec.dart

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ main() => describe('ElementBinderBuilder', () {
3232

3333
beforeEach((DirectiveMap d, ElementBinderFactory f) {
3434
directives = d;
35-
b = f.builder(null);
35+
b = f.builder(null, null);
3636
});
3737

3838
addDirective(selector) {
@@ -49,17 +49,17 @@ main() => describe('ElementBinderBuilder', () {
4949
addDirective('[directive]');
5050

5151
expect(b.decorators.length).toEqual(1);
52-
expect(b.component).toBeNull();
52+
expect(b.componentData).toBeNull();
5353
expect(b.childMode).toEqual(Directive.COMPILE_CHILDREN);
5454

5555
});
5656

57-
it('should add a component', () {
57+
it('should add a component', async(() {
5858
addDirective('component');
5959

6060
expect(b.decorators.length).toEqual(0);
61-
expect(b.component).toBeNotNull();
62-
});
61+
expect(b.componentData).toBeNotNull();
62+
}));
6363

6464
it('should add a template', () {
6565
addDirective('[structural]');
@@ -71,7 +71,7 @@ main() => describe('ElementBinderBuilder', () {
7171
addDirective('[ignore-children]');
7272

7373
expect(b.decorators.length).toEqual(1);
74-
expect(b.component).toBeNull();
74+
expect(b.componentData).toBeNull();
7575
expect(b.childMode).toEqual(Directive.IGNORE_CHILDREN);
7676
});
7777
});

0 commit comments

Comments
 (0)