Skip to content

Commit 31702ff

Browse files
author
Steven Orvell
committed
Lazy register features we can be deferred until first instance. This is an optimization which can speed up page load time when elements are registered but not needed at time of first paint/interaction
1 parent edb59eb commit 31702ff

7 files changed

Lines changed: 147 additions & 38 deletions

File tree

‎lazy-register.html‎

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
<!doctype html>
2+
<html>
3+
<head>
4+
5+
<title>lazy-register</title>
6+
7+
<meta charset="utf-8">
8+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
9+
<script>
10+
Polymer = {lazyRegistration: true};
11+
</script>
12+
<script src="../../../webcomponentsjs/webcomponents-lite.js"></script>
13+
<link rel="import" href="../../polymer.html">
14+
15+
</head>
16+
<body>
17+
18+
<script>
19+
(function() {
20+
var lr = Polymer.Base._ensureRegistered;
21+
Polymer.Base._ensureRegistered = function(proto) {
22+
console.log('_ensureRegistered', proto.is);
23+
lr.call(this, proto);
24+
}
25+
26+
})();
27+
</script>
28+
29+
<dom-module id="x-base">
30+
<script>
31+
Polymer({
32+
33+
is: 'x-base',
34+
35+
properties: {
36+
state: {value: 'live!'}
37+
}
38+
39+
});
40+
</script>
41+
</dom-module>
42+
43+
<dom-module id="x-foo">
44+
<template>I {{state}}</template>
45+
<script>
46+
XFoo = Polymer({
47+
48+
is: 'x-foo',
49+
extends: 'x-base'
50+
51+
});
52+
</script>
53+
</dom-module>
54+
55+
<dom-module id="x-bar">
56+
<script>
57+
Polymer({
58+
59+
is: 'x-bar',
60+
extends: 'x-foo'
61+
62+
});
63+
</script>
64+
</dom-module>
65+
66+
67+
<script>
68+
// console.log('XFoo has performed lazy registration tasks', Boolean(XFoo.prototype._template));
69+
// console.log('Create XFoo', new XFoo());
70+
// console.log('XFoo has performed lazy registration tasks', Boolean(XFoo.prototype._template));
71+
</script>
72+
73+
<!-- <x-bar></x-bar> -->
74+
75+
</body>
76+
</html>

‎polymer.html‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,12 @@
3232
this._prepConstructor();
3333
// template
3434
this._prepTemplate();
35-
// styles and style properties
35+
// styles
3636
this._prepStyles();
37+
},
38+
39+
_registerLazyFeatures: function() {
40+
this._prepShimStyles();
3741
// template markup
3842
this._prepAnnotations();
3943
// accessors

‎src/lib/base.html‎

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,36 @@
2828
this._desugarBehaviors(); // abstract
2929
this._doBehavior('beforeRegister'); // abstract
3030
this._registerFeatures(); // abstract
31-
this._doBehavior('registered'); // abstract
3231
},
3332

3433
createdCallback: function() {
34+
this._ensureRegistered(this.__proto__);
3535
Polymer.telemetry.instanceCount++;
3636
this.root = this;
3737
this._doBehavior('created'); // abstract
3838
this._initFeatures(); // abstract
3939
},
4040

41+
/**
42+
* When called from the element's prototype, ensures that the element has
43+
* fully registered. By default registration tasks are defered until the
44+
* first instance of an element is created.
45+
*/
46+
ensureRegistered: function() {
47+
this._ensureRegistered(this);
48+
},
49+
50+
_ensureRegistered: function(proto) {
51+
if (proto.__hasRegistered !== proto.is) {
52+
proto.__hasRegistered = proto.is;
53+
if (proto._registerLazyFeatures) {
54+
proto._registerLazyFeatures();
55+
}
56+
// registration extension point
57+
this._doBehavior('registered');
58+
}
59+
},
60+
4161
// reserved for canonical behavior
4262
attachedCallback: function() {
4363
// NOTE: workaround for:

‎src/lib/style-util.html‎

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -77,22 +77,38 @@
7777
},
7878

7979
// add a string of cssText to the document.
80-
applyCss: function(cssText, moniker, target, afterNode) {
80+
applyCss: function(cssText, moniker, target, contextNode) {
81+
var style = this.createScopeStyle(cssText, moniker);
82+
target = target || document.head;
83+
var after = (contextNode && contextNode.nextSibling) ||
84+
target.firstChild;
85+
this.__lastHeadApplyNode = style;
86+
return target.insertBefore(style, after);
87+
},
88+
89+
createScopeStyle: function(cssText, moniker) {
8190
var style = document.createElement('style');
8291
if (moniker) {
8392
style.setAttribute('scope', moniker);
8493
}
8594
style.textContent = cssText;
86-
target = target || document.head;
87-
if (!afterNode) {
88-
var n$ = target.querySelectorAll('style[scope]');
89-
afterNode = n$[n$.length-1];
90-
}
91-
target.insertBefore(style,
92-
(afterNode && afterNode.nextSibling) || target.firstChild);
9395
return style;
9496
},
9597

98+
__lastHeadApplyNode: null,
99+
100+
// insert a comment node as a styling position placeholder.
101+
applyStylePlaceHolder: function(moniker) {
102+
var placeHolder = document.createComment(' polymer element ' +
103+
moniker + ' ');
104+
var after = this.__lastHeadApplyNode ?
105+
this.__lastHeadApplyNode.nextSibling : null;
106+
var scope = document.head;
107+
scope.insertBefore(placeHolder, after || scope.firstChild);
108+
this.__lastHeadApplyNode = placeHolder;
109+
return placeHolder;
110+
},
111+
96112
cssFromModules: function(moduleIds, warnIfNotFound) {
97113
var modules = moduleIds.trim().split(' ');
98114
var cssText = '';

‎src/standard/styling.html‎

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,18 @@
4242
}
4343
if (this._template) {
4444
this._styles = this._collectStyles();
45+
// under shady dom we always output a shimmed style (which may be
46+
// empty) so that other dynamic stylesheets can always be placed
47+
// after the element's main stylesheet.
48+
// This helps ensure element styles are always in registration order.
49+
if (this._styles.length && !nativeShadow) {
50+
this._scopeStyle = styleUtil.applyStylePlaceHolder(this.is);
51+
}
52+
}
53+
},
54+
55+
_prepShimStyles: function() {
56+
if (this._template) {
4557
// calculate shimmed styles (we must always do this as it
4658
// stores shimmed style data in the css rules for later use)
4759
var cssText = styleTransformer.elementStyles(this);
@@ -50,21 +62,18 @@
5062
// do we really need to output static shimmed styles?
5163
// only if no custom properties are used since otherwise
5264
// styles are applied via property shimming.
53-
var needsStatic = this._styles.length &&
54-
!this._needsStyleProperties();
55-
// under shady dom we always output a shimmed style (which may be
56-
// empty) so that other dynamic stylesheets can always be placed
57-
// after the element's main stylesheet.
58-
// This helps ensure element styles are always in registration order.
59-
if (needsStatic || !nativeShadow) {
65+
if (!this._needsStyleProperties() && this._styles.length) {
6066
// NOTE: IE has css style ordering issues unless there's at least a
6167
// space in the stylesheet.
62-
cssText = needsStatic ? cssText : ' ';
6368
var style = styleUtil.applyCss(cssText, this.is,
64-
nativeShadow ? this._template.content : null);
69+
nativeShadow ? this._template.content : null, this._scopeStyle);
6570
// keep track of style when in document scope (polyfill) so we can
6671
// attach property styles after it.
6772
if (!nativeShadow) {
73+
// remove old scope style (comment node) when it's not needed.
74+
if (this._scopeStyle) {
75+
this._scopeStyle.parentNode.removeChild(this._scopeStyle);
76+
}
6877
this._scopeStyle = style;
6978
}
7079
}

‎test/unit/base.html‎

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@
2929

3030
setup(function() {
3131
// Ensure a clean environment for each test.
32-
/* global Base */
3332
window.Base = Polymer.Base;
3433
window.Child = Object.create(Base);
35-
Child._registerFeatures = function() {};
34+
Child._registerFeatures = function() {
35+
};
3636
Child._initFeatures = function() {};
3737
Child._setAttributeToProperty = function() {};
3838
Child._desugarBehaviors = function() {};
@@ -54,22 +54,6 @@
5454

5555
});
5656

57-
suite('registerCallback', function() {
58-
59-
test('calls registered() after registerFeatures()', function() {
60-
var called = [];
61-
Child._registerFeatures = function() {
62-
called.push('1');
63-
};
64-
Child.registered = function() {
65-
called.push('2');
66-
};
67-
assert.deepEqual(called, []);
68-
Child.registerCallback();
69-
assert.includeMembers(called, ['1', '2']);
70-
});
71-
72-
});
7357

7458
suite('createdCallback', function() {
7559

‎test/unit/notify-path.html‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -950,7 +950,7 @@
950950
Polymer({
951951
is: 'x-broken',
952952
observers: ['foo(missingParenthesis']
953-
});
953+
}).prototype.ensureRegistered();
954954
} catch (e) {
955955
assert.equal(e.message, "Malformed observer expression 'foo(missingParenthesis'");
956956
thrown = true;

0 commit comments

Comments
 (0)