Web Components
Soledad Penadés
devangeneering at Mozilla
- @supersole
- #devrel / #devtools in irc.mozilla.org
- Based in London, so ~11-20h GMT
Web Components?
- "A tectonic shift in web development"
- "The declarative simplicity of using web components will be unparalelled"
No, but really, please?
A series of technologies to make it easier to write modular, reusable front-end code
Why?
Web 1.0
- simple documents. Homepages.
Web 1.5
- Web rings! Java Applets! Flash!
Web 2.0
- Modern JS! AJAX! JS Frameworks! JSON! JSONP!
- Code reuse. Mash-ups!
- Lots of jQuery plugins! "Designers can code"
Web 2.0 also brought us
- Side effects
- CSS bleeding all over the page
- Bloated code
- Terrible mark-up
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
- HTML templates
- Shadow DOM
- HTML imports
Custom elements
Teach new elements to the browser
document.registerElement(
'web-bell',
WebBellPrototype
);
A custom element prototype
- at least extends HTMLElement.prototype
- lifecycle callbacks
- methods
- getters, setters
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
- Firefox
- Chrome
- Opera
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
- No two way binding
- No string interpolation
- "Does what it says on the tin"
HTML Templates support
- Firefox
- Chrome
- Opera
- Safari
- and in development in Edge
Shadow DOM
Now you see it, now you don’t
- Multiple DOM trees in a hierarchy
- Creates a boundary around your element
- Reset and isolate CSS styles, hide mark-up
- Superuseful for things such as players, calendars
- It is still in the same doc context (not an iframe)
- It’s complicated
node.innerHTML = 'This is the light DOM';
var shadow = node.createShadowRoot({
mode: 'closed'
});
shadowRoot.innerHTML =
'The Shadow DOM has taken over';
Shadow DOM support
- Chrome
- Opera
- Firefox: in development, behind a pref
- Edge: under consideration
*Very unstable*.
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
- Chrome
- Opera
- Firefox: sort of available behind a flag, but will be removed
Why will it be removed from Firefox?
Concerns regarding duplication of similar features
- Service Workers
- ES6 modules
- Fetch API
Support recap
- Chrome, Opera: everything
- Firefox: Behind a flag, everything except HTML imports
- Safari: Templates
- Edge: they're going to implement Templates and Shadow DOM
Also, the spec is not finalised
“Nothing is
a standard
until there’s
several browsers
commited to it”
–Anne van Kesteren.
but this is JavaScriptlandia
we have
polyfills!
webcomponents.js
- polyfills custom elements, HTML imports, Shadow DOM
- also WeakMap and Mutation Observers
webcomponents-lite.js
- like the full polyfill, but it doesn't polyfill Shadow DOM
A note of warning
Polyfills are NOT FREE
- They come at a cost (bandwidth, processing)
- The Shadow DOM polyfill is a huge beast
- You might run into issues with Shadow DOM scoped selectors
- Potential inconsistencies with the HTML imports polyfill
What about Polymer? X-Tag? Bosonic?
Syntactic sugar to make vanilla web components less sour (I ❤️ BAD PUNS)
- Built on top of the Web Components APIs
- ... or on the polyfills
- Not shipped with the browser!
X-Tag example
xtag.register('web-bell', {
extends: 'div',
lifecycle: {
created: function() {
this.innerHTML = '🔔';
}
}
});
Polymer example
Polymer('web-bell', {
extends: 'div',
created: function() {
this.innerHTML = '🔔';
}
});
Very nice. Shall I use them?
Very nice indeed, but...
- we're back to the jQuery/Moo situation
- you need the library the component was built with
How do web components compare to JS frameworks?
How well do they interoperate (or not)?
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:
- Inheriting from HTMLElement prototype
- Not using certain attributes
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?
- innerHTML = '';
- or wait until attachedCallback to append the DOM
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!
If you can’t control the environment
Err on the safest side:
use custom elements only
Fail-safe custom elements recipe
- Use the smallest polyfill
- Don’t use the is="" syntax
- Have a .js and .css per component
- Use existing tooling to minimise all your .js/.css
- Be aware of React/Ember/Angular/... weirdnesses
- Don’t take anything for granted. Set defaults everywhere!
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>
Real life examples
- Firefox OS refactored to use web components for UI elements
- The Guardian's dashboard using Polymer + Material design comps
- Chrome's Platform Status is built on Polymer
- GitHub <time is=""> custom element for timestamps