Skip to content
This repository was archived by the owner on Feb 22, 2018. It is now read-only.

Commit db65f77

Browse files
committed
perf(compiler): 31%. Cache CSS in Style elements.
In a benchmark, I saw a 31% speedup while creating components which include unminified Bootstrap (125K) as a cssUrl.
1 parent f2eb941 commit db65f77

File tree

2 files changed

+59
-17
lines changed

2 files changed

+59
-17
lines changed

lib/core_dom/shadow_dom_component_factory.dart

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ class ShadowDomComponentFactory implements ComponentFactory {
3030

3131
ShadowDomComponentFactory(this._expando);
3232

33+
final Map<String, async.Future<dom.StyleElement>> _styleElementCache = {};
34+
3335
FactoryFn call(dom.Node node, DirectiveRef ref) {
3436
return (Injector injector) {
3537
var component = ref.annotation as Component;
@@ -41,7 +43,7 @@ class ShadowDomComponentFactory implements ComponentFactory {
4143
NgBaseCss baseCss = injector.get(NgBaseCss);
4244
// This is a bit of a hack since we are returning different type then we are.
4345
var componentFactory = new _ComponentFactory(node, ref.type, component,
44-
injector.get(dom.NodeTreeSanitizer), _expando, baseCss);
46+
injector.get(dom.NodeTreeSanitizer), _expando, baseCss, _styleElementCache);
4547
var controller = componentFactory.call(injector, scope, viewCache, http, templateCache,
4648
directives);
4749

@@ -65,14 +67,15 @@ class _ComponentFactory implements Function {
6567
final dom.NodeTreeSanitizer treeSanitizer;
6668
final Expando _expando;
6769
final NgBaseCss _baseCss;
70+
final Map<String, async.Future<dom.StyleElement>> _styleElementCache;
6871

6972
dom.ShadowRoot shadowDom;
7073
Scope shadowScope;
7174
Injector shadowInjector;
7275
var controller;
7376

7477
_ComponentFactory(this.element, this.type, this.component, this.treeSanitizer,
75-
this._expando, this._baseCss);
78+
this._expando, this._baseCss, this._styleElementCache);
7679

7780
dynamic call(Injector injector, Scope scope,
7881
ViewCache viewCache, Http http, TemplateCache templateCache,
@@ -87,28 +90,24 @@ class _ComponentFactory implements Function {
8790
// styles all over the page. We shouldn't be doing browsers work,
8891
// so change back to using @import once Chrome bug is fixed or a
8992
// better work around is found.
90-
List<async.Future<String>> cssFutures = new List();
93+
Iterable<async.Future<dom.StyleElement>> cssFutures;
9194
var cssUrls = []..addAll(_baseCss.urls)..addAll(component.cssUrls);
9295
if (cssUrls.isNotEmpty) {
93-
cssUrls.forEach((css) => cssFutures.add(http
94-
.get(css, cache: templateCache).then(
95-
(resp) => resp.responseText,
96-
onError: (e) => '/*\n$e\n*/\n')
97-
));
96+
cssFutures = cssUrls.map((cssUrl) => _styleElementCache.putIfAbsent(cssUrl, () =>
97+
http.get(cssUrl, cache: templateCache)
98+
.then((resp) => resp.responseText,
99+
onError: (e) => '/*\n$e\n*/\n')
100+
.then((styleContent) => new dom.StyleElement()..appendText(styleContent))
101+
)).toList();
98102
} else {
99-
cssFutures.add(new async.Future.value(null));
103+
cssFutures = [new async.Future.value(null)];
100104
}
101105
var viewFuture = ComponentFactory._viewFuture(component, viewCache, directives);
102106
TemplateLoader templateLoader = new TemplateLoader(
103107
async.Future.wait(cssFutures).then((Iterable<String> cssList) {
104-
if (cssList != null) {
105-
shadowDom.setInnerHtml(
106-
cssList
107-
.where((css) => css != null)
108-
.map((css) => '<style>$css</style>')
109-
.join(''),
110-
treeSanitizer: treeSanitizer);
111-
}
108+
cssList
109+
.where((styleElement) => styleElement != null)
110+
.forEach((styleElement) => shadowDom.append(styleElement.clone(true)));
112111
if (viewFuture != null) {
113112
return viewFuture.then((ViewFactory viewFactory) {
114113
return (!shadowScope.isAttached) ?

test/core/templateurl_spec.dart

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ class PrefixedUrlRewriter extends UrlRewriter {
4242
void main() {
4343
describe('template url', () {
4444
afterEach((MockHttpBackend backend) {
45+
backend.verifyNoOutstandingExpectation();
4546
backend.verifyNoOutstandingRequest();
4647
});
4748

@@ -242,5 +243,47 @@ void main() {
242243
expect(log.result()).toEqual('LOG; SIMPLE');
243244
})));
244245
});
246+
247+
describe('style cache', () {
248+
beforeEachModule((Module module) {
249+
module
250+
..type(HtmlAndCssComponent)
251+
..value(TemplateCache, new TemplateCache(capacity: 0));
252+
});
253+
254+
it('should load css from the style cache for the second component', async(inject(
255+
(Http http, Compiler compile, MockHttpBackend backend,
256+
DirectiveMap directives, Injector injector) {
257+
backend
258+
..expectGET('simple.css').respond(200, '.hello{}')
259+
..expectGET('simple.html').respond(200, '<div log="SIMPLE">Simple!</div>');
260+
261+
var element = e('<div><html-and-css>ignore</html-and-css><div>');
262+
compile([element], directives)(injector, [element]);
263+
264+
microLeap();
265+
backend.flush();
266+
microLeap();
267+
268+
expect(element.children[0].shadowRoot).toHaveHtml(
269+
'<style>.hello{}</style><div log="SIMPLE">Simple!</div>'
270+
);
271+
272+
// Since the template cache is disabled, we expect a 'simple.html' call.
273+
backend
274+
..expectGET('simple.html').respond(200, '<div log="SIMPLE">Simple!</div>');
275+
276+
var element2 = e('<div><html-and-css>ignore</html-and-css><div>');
277+
compile([element2], directives)(injector, [element2]);
278+
279+
microLeap();
280+
backend.flush();
281+
microLeap();
282+
283+
expect(element2.children[0].shadowRoot).toHaveHtml(
284+
'<style>.hello{}</style><div log="SIMPLE">Simple!</div>'
285+
);
286+
})));
287+
});
245288
});
246289
}

0 commit comments

Comments
 (0)