1 of 29

What's New in Custom Elements

This document is public. Everyone on the internet can see what you write here.

dominicc, 2016-11-15

2 of 29

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.

3 of 29

Contents

  1. Specs
  2. Defining elements
  3. Callbacks
  4. Minutiae about documents
  5. is= Syntax
  6. Plans for Blink

4 of 29

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-elementshttps://github.com/whatwg/html/wiki#custom-elements

5 of 29

Changed vocabulary

Old

Type extension�"Not type extensions"�Callbacks

New

Customized built-in element�Autonomous custom element�Reactions (the steps) + callbacks

6 of 29

Contents

  • Specs
  • Defining elements
  • Callbacks
  • Minutiae about documents
  • is= Syntax
  • Plans for Blink

7 of 29

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.

8 of 29

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.

9 of 29

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.

10 of 29

: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;�}��JSlet app = document.querySelector(� 'my-app:defined');�

11 of 29

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.

12 of 29

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.

13 of 29

Contents

  • Specs
  • Defining elements
  • Callbacks
  • Minutiae about documents
  • is= Syntax
  • Plans for Blink

14 of 29

attached ⇒ connected

detached ⇒ disconnected

Simple renames.

15 of 29

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.

16 of 29

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

17 of 29

Contents

  • Specs
  • Defining elements
  • Callbacks
  • Minutiae about documents
  • is= Syntax
  • Plans for Blink

18 of 29

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.

19 of 29

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.

👎

:(

20 of 29

Use the importNode trick

To avoid the detached document problem, use importNode to copy nodes into a new document instead of reusing them.

👍

21 of 29

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

22 of 29

Imports

Notes about how custom elements work in HTML Imports:

https://gist.github.com/TakayoshiKochi/93a46a10c056d2a3d70495c6fc025310

23 of 29

Contents

  • Specs
  • Defining elements
  • Callbacks
  • Minutiae about documents
  • is= Syntax
  • Plans for Blink

24 of 29

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.

25 of 29

Contents

  • Specs
  • Defining elements
  • Callbacks
  • Minutiae about documents
  • is= Syntax
  • Plans for Blink

26 of 29

Implementation Status

Everything* works (*see open bugs):�define(name, cls)�- extends: button behind a flag�:definedget(name)�parsingnew 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.

27 of 29

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.

28 of 29

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.

29 of 29

~oOo~