The state of web components

by Wilson Page

Three years on, web components should be everywhere

Why has this taken so long?!

Vendors couldn't agree

😭

Until now, only a Google effort

Very ambitious proposal

(complexity + highLevel) === vendor.contention

No easy task!

Custom Elements

The least contentious bit

Everyone agrees we need them

Couple of things to iron out...

'Upgrade'

HTMLElement -> MyAwesomeElement

var el = document.querySelector('my-awesome-element');
el.constructor.name; //=> 'HTMLElement';
document.registerElement('my-awesome-element', ...);
el.constructor.name; //=> 'my-awesome-element';

var proto = Object.create(HTMLElement.prototype);
proto.createdCallback = function() { ... };
document.registerElement('x-foo', { prototype: proto });

We can probably do better

There are five new proposals

Two most promising

class MyEl extends HTMLElement {
  constructor() { ... }
}
class MyEl extends HTMLElement {
  createdCallback() { ... }
}
document.registerElement('my-el', MyEl);

'Synchronous Constructor'

class MyEl extends HTMLElement {
  constructor() { ... }
}
  • 👍 created using real constructors
  • 👎 complications with .cloneNode()
  • 👎 registerElement() must run before parse

'Dmitri'

class MyEl extends HTMLElement {
  createdCallback() { ... }
}
  • 👍 elements can be upgraded lazily (more robust)
  • 👎 different to how built-in elements work
  • 👎 'unknown' state is confusing

is

Enhances built-in elements

<input type="text" is="my-text-input">

For

  1. Extend element features that aren't yet exposed.
  2. Progressive enhancement

Against

  1. Ignores the problem of missing primitives
  2. Ignores the problem of extending built-in elements
  3. Limited use-cases
  4. Confusing syntax

Consensus

Google wants it as a stop-gap

Mozilla and Apple want to ship quicker without polluting the platform

Focus on a better solution in a 'V2'

Shadow DOM

(the difficult part)

'Distribution'

The projection of the host's content
into the shadow root

What we have today

<content select="header"></content>

Agreement couldn't be made on declarative API

var shadow = host.createShadowRoot({
  distribute: function(nodes) {
    var slot = shadow.querySelector('content');
    for (var i = 0; i < nodes.length; i++) {
      slot.add(nodes[i]);
    }
  }
});

shadow.innerHTML = '';

// Call initially ...
shadow.distribute();

// then hook up to MutationObserver

Timing, timing, timing

myHost.appendChild(someElement);
someElement.offsetTop; //=> old value

// distribute on mutation observer callback (async)

someElement.offsetTop; //=> new value

Resolution

Work on imperative API until July 2015

If impossible, back to declarative API negotiations

'closed' VS. 'open'

Open: el.shadowRoot.querySelector('.secrets')

Closed: el.shadowRoot === null

element.createShadowRoot({ mode: 'open' });
element.createShadowRoot({ mode: 'closed' });

Apple want it

  • Believe implementation details should
    always be hidden
  • Will be required for 'isolated' custom elements

Google don't

  • Blocks accessibility and tooling libraries
  • JS is open, we should keep it that way
  • Shadow root was designed to be hidden

Resolution

We need the mode feature

Parameter required, so no default value

Shadow piercing
combinators

x-foo /deep/ div { color: red }
x-foo::shadow div { color: red }
x-foo >>> div { color: red }

Designed to style internals
from outside ...

... things got messy!

Prevents unique optimization opportunities

New solutions coming along

Custom properties (variables)



x-foo {
  --background-color: red;
  --something-else: black;
}

          


.internal-thing { background: var(--background-color); }
.other-bit { color: var(--something-else); }

          

Mixins - @extend


.x-foo-part {
  background-color: red;
  border-radius: 4px;
}
          

.internal-part {
  @extend .x-foo-part;
}
          

Custom pseudo elements

input[type=range]::-moz-range-thumb { ... }
x-foo::my-internal-part { ... }

Resolution

Drop all piercing combinators (/deep/ et al)

Multiple shadow roots

😦

Structural inheritance




  

My title

Some details





foo

            


var XDialog = require('x-dialog');
var proto = Object.create(XDialog.prototype);

proto.createdCallback = function() {
  XDialog.prototype.createdCallback.call(this);
  this.createShadowRoot(); // <= 2nd shadow root
  this.shadowRoot.innerHTML = someHTML;
};

document.registerElement('x-dialog-alert', {
  prototype: proto
});

            



  

Alert

Resolution:

Multiple shadow roots not part of 'V1'

Inheritance still possible ...

😯


var proto = Object.create(XDialog.prototype);

proto.createdCallback = function() {
  XDialog.prototype.createdCallback.call(this);
  var inner = this.shadowRoot.querySelector('.inner');

  var h1 = document.createElement('h1');
  h1.textContent = 'Alert';
  inner.insertBefore(h1, inner.children[0]);

  var button = document.createElement('button');
  button.textContent = 'OK';
  inner.appendChild(button);

  ...
};

document.registerElement('x-dialog-alert', {
  prototype: proto
});

          

Downsides

  • Sub-component dependent on the implementation details
  • Not possible if super shadow root is 'closed'
  • Not as elegant

HTML Imports

Maybe useful for isolated elements

Wrap up

  • Web Components are ambitious
  • Standards are hard
  • All vendors are on-board
  • We're near the end

Get ready to componentize the web!

@wilsonpage