What's New in Custom Elements
This document is public. Everyone on the internet can see what you write here.
dominicc, 2016-11-15
Disclaimer
Audience:
Custom elements? What's that? This deck is probably not for you. See Google Developers > Web Fundamentals > API Primers > Using custom elements.
I'm assuming you have a working understanding of "v0" and just want the diffs to "v1". This is a pretty deep dive. Ask questions!
Disclaimer:
Things in this presentation could be wrong. It's complicated. The implementation is slightly behind what you see here.
Contents
Specs
Custom elements are just part of DOM and HTML now.�Don't be fooled: Anything other spec is obsolete.
Cheat deep links into the specs:�https://github.com/whatwg/dom/wiki#custom-elements �https://github.com/whatwg/html/wiki#custom-elements
Changed vocabulary
Old
Type extension�"Not type extensions"�Callbacks
New
Customized built-in element�Autonomous custom element�Reactions (the steps) + callbacks
Contents
document.registerElement ⇒ customElements.define
�customElements.define(name, cls, opt_options)
cls must be a function with a prototype property that's an Object. cls must not be reused or reuse a built-in. No restrictions on the prototype chain.
You may pass random things and have the call succeed, but find creating elements doesn't work. Stick to ES6 classes which ultimately extend HTMLElement.
createdCallback ⇒ constructor
class MyElement extends HTMLElement {� constructor(...args) {� super();� // callbacks happen from now on� }� …�}��window.customElements.define(� 'my-element', MyElement);��// does not work before define�let e = new MyElement();�document.body.appendChild(e);
You must call super. You must not return a different object. Throwing is fine.
You should call super first. Carefully violating this will work, but if you fail your reward is seemingly random exceptions at other times.
this may be quite dirty. Because of upgrade, it may have existed for a long time already.
Using a function instead of a class
function OldSchool(...args) {� let self = Reflect.construct(� HTMLElement, [], OldSchool);� …� return self;�}��// define, etc. is the same
This Reflect.construct call is the equivalent of super(). Same rules apply.
Note this example uses an Object prototype.
Reflect.construct is reportedly measurably slower than super() in V8 at the moment. Use classes if you can. Classes are the future.
:unresolved ⇒ :defined
:defined will match built-in elements.
:defined is for applying styles, so:
When working with custom element definitions consider using whenDefined instead of selectors.
With specific elements, consider testing prototype properties.
CSS�:not(:defined) {� opacity: 0;�}��JS�let app = document.querySelector(� 'my-app:defined');�
Upgrade
When you call define, in-document elements get upgraded in document order. Other existing elements are upgraded as they're inserted into the document.
This is probably more expensive than before because we have to sort them.
New: get, whenDefined
window.customElements.get(name)�Retrieves the constructor if there's a definition. Only for custom elements; you can't retrieve button, etc.
window.customElements.whenDefined(name)�A Promise that resolves when an element is defined. Resolves with undefined; use get to retrieve the constructor.
You can't tell whether the definition is an autonomous element or a customized built-in element without trying it.
Contents
attached ⇒ connected
detached ⇒ disconnected
Simple renames.
Attributes
class MyElement extends HTMLElement {� static get observedAttributes() {� return ['src', 'my-prop'];� }� attributeChangedCallback(� name, old, value, nsuri) {� … � }
You must list which attributes you want callbacks for. This list is static—cached at definition time.
Only listen to what you need to. Some attributes, like style, are expensive to serialize.
Obviously everyone will ignore nsuri. It's JavaScript; you can write a shorter parameter list.
Exceptions are inconsistent
Some errors are reported via window.onerror. Watch the console/onerror for errors during development/automation.
let e=document.createElement(� 'button',� {is: 'doesnt-exist'});�// throws an exception,�// definition doesn’t exist
class T extends HTMLElement {� super() {� throw 'boogers';� }�}�customElements.define(� 'x-t', T);��let e=document.createElement(� 'x-t');�// onerror gets boogers�// e is HTMLUnknownElement
Contents
Fewer documents are active
In the past, definitions were shared between the document, its clones, and DOMImplementation documents. Plus, the template document could have definitions.
Now, only a document with a window can have definitions.
Detached documents
Elements hang onto their definition forever.
If you move a node into another frame and close the first one, the callbacks stop. (v0 was also this way, for example Issue 282477.)
haraken says he will complete bindings work to run script in detached documents in 2016Q4 but this remains controversial. If it happens we will keep running these callbacks for v1 in M58-ish time frame.
👎
:(
Use the importNode trick
To avoid the detached document problem, use importNode to copy nodes into a new document instead of reusing them.
👍
Adoption
There is now an adoptedCallback(). Use this.document to discover the new document.
You need to keep track of the old document yourself if you need it.
Multiple re-adoptions may lose information. https://github.com/whatwg/dom/issues/292
Imports
Notes about how custom elements work in HTML Imports:
https://gist.github.com/TakayoshiKochi/93a46a10c056d2a3d70495c6fc025310
Contents
is= Syntax
Chrome 56+ with --enable-custom-elements-builtin flag. Under active development, expect sawdust.
Use createElement('div', {is: 'x-foo'})�instead of createElement('div', 'x-foo')
When upgrading, switch document.register calls to customElements.define and createElement arguments at the same time. One may not work with the other.
Contents
Implementation Status
Everything* works (*see open bugs):�define(name, cls)�- extends: button behind a flag�:defined�get(name)�parsing�new X(...)�upgrade�callbacks
We welcome bug reports, especially for these features.
Please turn on crash reporting and file bugs with crash IDs from chrome://crashes.��
Autonomous custom elements shipped in Chrome 53.�Customized built-in elements under active development.
Moving from old to new
document.registerElement is the "old" API.
You can use document.registerElement and customElements.define in the same page. But you can't use the same name simultaneously because what would that mean?
All old things happen together. All new things happen together. The exact order depends on implementation minutiae and we may shift things around.
Deprecating the old
When define is in stable, we will add a deprecation message.
When use is below 0.03% we will remove registerElement.
If we have evidence callers are feature detecting correctly we may remove it sooner.
When removal reaches stable, we will delete the code.
~oOo~