UNPKG

9.59 kBMarkdownView Raw
1# Custom Elements (v1) Polyfill [![Build Status](https://travis-ci.org/webcomponents/custom-elements.svg?branch=master)](https://travis-ci.org/webcomponents/custom-elements)
2
3A polyfill for the [custom elements](https://html.spec.whatwg.org/multipage/scripting.html#custom-elements)
4v1 spec.
5
6## Using
7
8Include `custom-elements.min.js` at the beginning of your page, *before* any code that
9manipulates the DOM:
10```html
11<script src="custom-elements.min.js"></script>
12```
13
14## Developing
15
161. Install and build
17
18 ```
19 npm install
20 npm run build
21 ```
22 (Or, `npm i && gulp`, if [gulp](https://github.com/gulpjs/gulp) is installed globally.)
23
241. Test
25
26 ```
27 npm run test
28 ```
29 (Or, [`wct`](https://github.com/Polymer/web-component-tester), if installed
30 globally.)
31
32## Custom element reactions in the DOM and HTML specs
33
34API which might trigger custom element reactions in the [DOM](https://dom.spec.whatwg.org/)
35and [HTML](https://html.spec.whatwg.org/) specifications are marked with the
36[`CEReactions` extended attribute](https://html.spec.whatwg.org/multipage/scripting.html#cereactions).
37
38## Known Bugs and Limitations
39
40- `adoptedCallback` is not supported.
41- Changing an attribute of a customizable (but uncustomized) element will not
42 cause that element to upgrade.
43- Only DOM API is patched. Notably, this excludes API from the HTML spec marked
44 with the `CEReactions` extended attribute.
45 - Unpatched API from the DOM spec:
46 - Setters on `Element` for `id`, `className`, and `slot`.
47 - `DOMTokenList` (`element.classList`)
48 - `NamedNodeMap` (`element.attributes`)
49 - `Attr` (`element.attributes.getNamedItem('attr-name')`)
50- The [custom element reactions stack](https://html.spec.whatwg.org/multipage/scripting.html#custom-element-reactions-stack)
51 is not implemented.
52 - Typically, DOM operations patched in this polyfill gather the list of
53 elements to which a given callback would apply and then iterate that list,
54 calling the callback on each element. This mechanism breaks down if an
55 element's callback performs another DOM operation that manipulates an area
56 of the tree that was captured in the outer operation's list of elements.
57 When this happens, the callbacks from the inner DOM operation will be called
58 *before* those of the outer DOM operation (typically, depending on the patch
59 implementation), as opposed to a spec-compliant implementation where the
60 callbacks are always run in the order they were inserted into each
61 particular element's reaction queue.
62- Custom elements created by the UA's parser are customized as if they were
63 upgraded, rather than constructed.
64 - These elements are only learned about *after* they have been constructed,
65 and typically after their descendants have been constructed. When these
66 elements are constructed, their children are visible and editable *even
67 though they would not yet exist and manipulating them would throw in a
68 spec-compliant implementation of custom elements!*
69- The [requirements for custom element constructors](https://html.spec.whatwg.org/multipage/scripting.html#custom-element-conformance)
70 are not enforced.
71 - These requirements are not generally enforcable in user script because of
72 the ability to use the `new` operator on a custom element constructor. This
73 means there is no way to know when a call to a constructor has begun or
74 finished.
75- Methods of the `ParentNode` and `ChildNode` interfaces do not support
76 `DocumentFragment`s as arguments.
77- Your custom element constructor's prototype *must* have a property named
78 `constructor` which is that constructor.
79 - By default, for every constructable function `F`, `F.prototype.constructor === F`.
80 If you replace the prototype of your constructor `F`, you must make sure
81 that `F.prototype.constructor === F` remains true. Otherwise, the polyfill
82 will not be able to create or upgrade your custom elements.
83- The [`:defined` CSS pseudo-class](https://html.spec.whatwg.org/multipage/semantics-other.html#pseudo-classes)
84 is not supported.
85
86### ES5 vs ES2015
87
88The custom elements v1 spec is not compatible with ES5 style classes. This means
89ES2015 code compiled to ES5 will not work with a native implementation of Custom
90Elements.[0] While it's possible to force the custom elements polyfill to be
91used to workaround this issue (by setting (`customElements.forcePolyfill = true;`
92before loading the polyfill), you will not be using the UA's native
93implementation in that case.
94
95Since this is not ideal, we've provided an alternative:
96[native-shim.js](https://github.com/webcomponents/custom-elements/blob/master/src/native-shim.js).
97Loading this shim minimally augments the native implementation to be compatible
98with ES5 code. We are also working on some future refinements to this approach
99that will improve the implementation and automatically detect if it's needed.
100
101[0] The spec requires that an element call the `HTMLElement` constructor.
102Typically an ES5 style class would do something like `HTMLElement.call(this)` to
103emulate `super()`. However, `HTMLElement` *must* be called as a constructor and
104not as a plain function, i.e. with `Reflect.construct(HTMLElement, [], MyCEConstructor)`,
105or it will throw.
106
107### Parser-created elements in the main document
108
109By default, the polyfill uses a `MutationObserver` to learn about and upgrade
110elements in the main document as they are parsed. This `MutationObserver` is
111attached to `document` synchronously when the script is run.
112- If you attach a `MutationObserver` earlier before loading the polyfill, that
113 mutation observer will not see upgraded custom elements.
114- If you move a node with descendants that have not yet been inserted by the
115 parser out of the main document, those nodes will not be noticed or upgraded
116 (until another action would trigger an upgrade).
117
118Note: Using `polyfillWrapFlushCallback` disconnects this `MutationObserver`.
119
120### `customElements.polyfillWrapFlushCallback`
121
122tl;dr: The polyfill gets slower as the size of your page and number of custom
123element definitons increases. You can use `polyfillWrapFlushCallback` to prevent
124redundant work.
125
126To avoid a potential memory leak, the polyfill does not maintain a list of upgrade
127candidates. This means that calling `customElements.define` causes a synchronous,
128full-document walk to search for elements with `localName`s matching the new
129definition. Given that this operation is potentially expensive and, if your page
130loads many custom element definitions before using any of them, highly redundant,
131an extra method is added to the `CustomElementRegistry` prototype -
132`polyfillWrapFlushCallback`.
133
134`polyfillWrapFlushCallback` allows you to block the synchronous, full-document
135upgrade attempts made when calling `define` and perform them later. Call
136`polyfillWrapFlushCallback` with a function; the next time `customElements.define`
137is called and a full-document upgrade would happen, your function will be called
138instead. The only argument to your function is *another* function which, when
139called, will run the full-document upgrade attempt.
140
141For example, if you wanted to delay upgrades until the document's ready state
142was `'complete'`, you could use the following:
143
144```javascript
145customElements.polyfillWrapFlushCallback(function(flush) {
146 if (document.readyState === 'complete') {
147 // If the document is already complete, flush synchronously.
148 flush();
149 } else {
150 // Otherwise, wait until it is complete.
151 document.addEventListener('readystatechange', function() {
152 if (document.readyState === 'complete') {
153 flush();
154 }
155 });
156 }
157});
158```
159
160Once your wrapper function is called (because the polyfill wants to upgrade the
161document), it will not be called again until you have triggered the
162full-document upgrade attempt. If multiple definitions are registered before you
163trigger upgrades, all of those definitions will apply when you trigger upgrades -
164don't call the provided function multiple times.
165
166Promises returned by `customElements.whenDefined` will not resolve until a
167full-document upgrade attempt has been performed *after* the given local name
168has been defined.
169
170```javascript
171let flush;
172customElements.polyfillWrapFlushCallback(f => flush = f);
173
174const p = customElements.whenDefined('c-e', () => console.log('c-e defined'));
175
176customElements.define('c-e', class extends HTMLElement {});
177// `p` is not yet resolved; `flush` is now a function.
178
179flush(); // Resolves `p`.
180```
181
182You can't remove a callback given to `polyfillWrapFlushCallback`. If the
183condition your callback was intended to wait on is no longer important, your
184callback should call the given function synchronously. (See the
185`document.readyState` example above.)
186
187**Calling `polyfillWrapFlushCallback` disconnects the `MutationObserver` watching
188the main document.** This means that you must delay until at least
189`document.readyState !== 'loading'` to be sure that all elements in the main
190document are found (subject to exceptions mentioned in the section above).
191
192You can call `polyfillWrapFlushCallback` multiple times, each function given
193will automatically wrap and delay any previous wrappers:
194
195```javascript
196customElements.polyfillWrapFlushCallback(function(flush) {
197 console.log('added first');
198 flush();
199});
200
201customElements.polyfillWrapFlushCallback(function(flush) {
202 console.log('added second');
203 setTimeout(() => flush(), 1000);
204});
205
206customElements.define('c-e', class extends HTMLElement {});
207// 'added second'
208// ~1s delay
209// 'added first'
210// The document is walked to attempt upgrades.
211```