The cure for your Web Components hangover

Soledad Penadés

evangeneering at Mozilla

Some projects I've worked on

Web Components?

No, but really, please?

A series of technologies to make it easier to write modular, reusable front-end code

Why?

Web 1.0

Web 1.5

Web 2.0

Web 2.0 also brought us

Terrible mark-up, 1

<div class="widget calendar ui theme-winter">
  <div class="component-wrapper">
    <div class="inner-content">
      (ad nauseam)
    </div>
  </div>
</div>

Terrible mark-up, 2

$("#datepicker")
  .datepicker($.datepicker.regional["fr"]);
$("#locale").change(function() {
  $("#datepicker").datepicker("option",
    $.datepicker.regional[ $(this).val() ] );
});

What if the browser helped us write
better code?

What if we could
teach new elements
to the browser?

<x-calendar></x-calendar>
var calendar =
  document.createElement('x-calendar');
  
var calendar =
  document.querySelector('x-calendar');
calendar.nextMonth();
calendar.setLocale('fr');

How do we make this happen?

With Web Components

Custom elements

Teach new elements to the browser

document.register('web-bell', WebBellPrototype);

A custom element prototype

WebBell Prototype:

var WebBellPrototype =
  Object.create(HTMLElement.prototype);
  
WebBellPrototype.createdCallback = function() {
  this.innerHTML = '🔔';
};
var bell =
  document.createElement('web-bell');
<web-bell></web-bell>

Style custom elements like any other element

web-bell {
  border: 3px solid green;
}

Custom elements can
extend existing elements

by extending their prototype

var DingButtonProto =
  Object.create(HTMLButtonElement.prototype);
DingButtonProto.createdCallback = function() {
  this.innerHTML = 'ding!';
};
document.registerElement('ding-button', {
  prototype: DingButtonProto,
  extends: 'button'
});
var ding = document.createElement(
  'button', 'ding-button'
);
<button is="ding-button"></button>

This is amazing for accessibility and
progressive enhancement

It might also be deprecated soon

Custom elements support

HTML Templates

Inert HTML chunks

not live until you say so

<template id="row-template">
  <tr>
    <td><input .../></td>
    <td><button .../></td>
  </tr>
</template>

Creating rows on the fly

var tpl =
  document.getElementById('row-template');
var table =
  document.getElementById('form-table');
  
table.appendChild(tpl.content.cloneNode());

HTML templates are very simple

HTML Templates support

Shadow DOM

Now you see it, now you don’t

node.innerHTML = 'This is the light DOM';
var shadow = node.createShadowRoot();
shadowRoot.innerHTML = 
  'The Shadow DOM has taken over';

Shadow DOM support

HTML imports

Include HTML documents from other HTML documents

require() for the web

<link rel="import" href="my-component.html">

my-component.html's <head>

<script src="my-component.js"></script>
<link rel="stylesheet" href="my-component.css">

HTML Imports support

Why will it be removed from Firefox?

Concerns regarding duplication of similar features

Mozilla Hacks article

Support recap

Also, the spec is not finalised

Are we componentized yet?

but this is JavaScriptlandia

we have

polyfills!

webcomponents.js

webcomponents-lite.js

A note of warning

Polyfills are NOT FREE

What about Polymer? X-Tag? Bosonic?

Syntactic sugar to make vanilla web components less sour (I ❤️ BAD PUNS)

X-Tag example

xtag.register('web-bell', {
  extends: 'div',
  lifecycle: {
    created: function() {
      this.innerHTML = 'BELL';
    }
  }
});

Polymer example

Polymer('web-bell', {
  extends: 'div',
  created: function() {
    this.innerHTML = 'BELL';
  }
});

Very nice. Shall I use them?

Very nice indeed, but...

How do web components play with your MVC framework?

The beauty of components is that they’re just DOM elements

We should be able to use them across frameworks

😏

jQuery

Works quite well!

jQuery

Creating instances w/attributes and properties works as expected

$('<web-bell loud></web-bell>');

jQuery

Careful when setting properties

$('random-square').width = 15 ; // no
$('random-square')[0].width = 15; // yes

React

Works better the simpler your elements are:

React

You can't use custom elements by name in JSX 0.12 (fixed in 0.13)

var RandomSquareReact = React.createClass({
  render: function() {
    return <random-square></random-square>
  }
  

React

Using is="" with JSX doesn't work

var ComponentWithBellButton = React.createClass({
render: function() {
  return (
    <div>Look at that button 
      <button is="bell-button"></button>
    </div>
  );
}

React

Some attributes sanitised out

React.createElement('random-square', {
  width: 150, // OK
  height: 25, // OK
  colour: '#f0f' // XXX
})

React

I couldn't figure out how to access the actual DOM

so I'm not sure how to call methods or set properties

¯\_(ツ)_/¯

Ember and Angular

Pretty similar features
(and issues)

Ember

You can use custom elements in Handlebars templates

<script type="text/x-handlebars">
  <random-square></random-square>
</script>

Ember

Remember to unescape variable values set in JS

<script type="text/x-handlebars">
  {{{variableName}}}
</script>

Ember

Two way binding works

{{input type="range" value=squareWidth
  step="1" min="1" max="200" }}
<random-square {{bind-attr width=squareWidth}}>
</random-square>

Ember

They have Ember components

<script type="text/x-handlebars"
  id="components/random-square">
  <random-square></random-square>
</script>

Ember

Components, 2

App.RandomSquare =
  Ember.Component.extend({
    tagName: 'span'
  });
  

Ember

Components, 3

{{random-square}}

Ember

Using is="" in Ember components makes them use the wrong prototype

<script type="text/x-handlebars"
  id="components/bell-button">
  <button is="bell-button"></button>
</script>

Ember

Don't use id properties in top level tags; Ember will overwrite them

Angular

Code is escaped as in Ember

<span ng-repeat="bell in bellbuttons">
  {{bell}}
</span>
$scope.bellbuttons =
  [ '<button is="bell-button"/>' ];
  

Angular

Create elements in directives instead

.directive('my-thing', function() {
  return {
    //...
    template: '<my-thing></my-thing>'
    }
  });
  

Angular

Two-way binding works

<input type="text" ng-model="width" value="50">
<random-square width="{{width}}"></random-square>

Angular

Unexpected instantiation

<span ng-repeat="bell in bellbuttons">
  <random-square></random-square>
</span>

The joint Ember and Angular weirdness

Extraneous elements with custom elements in templates

Ember weirdness example

<script type="text/x-handlebars"
  id="components/random-square">
  <random-square></random-square>
</script>
{{random-square}}
<random-square>
  <canvas></canvas>
  <canvas></canvas>
</random-square>

Angular weirdness example

<span ng-repeat="square in squares">
  <random-square></random-square>
</span>
<span>
  <random-square>
    <canvas></canvas>
    <canvas></canvas>
  </random-square>
  <random-square>
    <canvas></canvas>
    <canvas></canvas>
  </random-square>
</span>

Why?

Cloning non-inert content

Solutions?

Are you sure Web Components are
a good idea?

Modularising and isolating your code is a good idea

I call this
defensive design

Even if platform support is not there yet

You can think in terms of components already

Don’t tie your code to an specific framework

Easier to share/reuse

If you can afford to experiment

Go full in, use the edgiest features and provide feedback!

Example: Firefox OS refactored to use web components for UI elements

Example: the Guardian's dashboard using Polymer + Material design comps

If you can’t control the environment

Err on the safest side:
use custom elements only

Fail-safe custom elements recipe

As you start refactoring,

your controllers
will get leaner and more expressive

As you start refactoring,

your controllers
will get leaner and more expressive

As you start refactoring,

your code
will get leaner and beautiful

<div class="widget calendar ui theme-winter">
  <div class="component-wrapper">
    <div class="inner-content">
      (ad nauseam)
    </div>
  </div>
</div>
<x-calendar></x-calendar>

Thanks!

@supersole

soledadpenades.com