Disclaimer: I talk a lot about React here, but you can substitute your favorite library: Inferno, Preact, Vue, snabbdom, virtual-dom (or non-js libraries and frameworks like Elm, Om, etc.). Similarly, replace Polymer with Vaadin, or X-Tag, or…
Also, read Rob Dodson’s excellent response to this article: https://robdodson.me/regarding-the-broken-promise-of-web-components/
Brief, incomplete, and mostly incorrect history of Web Components
Ancient times
By 2011 Internet had grown. It had Facebooks and Gmails, and Twitters, and Asanas, and Google Docs, and countless other online things that could no longer be called sites, or Single Page Applications. They were, for all intents and puposes, applications. As simple as that.
And woe was to the devs who were developing them.
State of the art for Web GUIs at the time was probably a templating language glued together by some serverside logic and/or a client-side library. Backbone if you were lucky. jQuery UI or Sencha/ExtJs if you were enterprise enough.
This was cumbersome. This was limiting. You could not prototype easily and quickly. You could not easily escape the limitations of UI libraries. etc. etc. etc.
And you were limited to the same set of HTML elements as ever: div
s, p
s, forms
s…
In 2011 Alex Russel proposed a vision of the future (emphasis mine):
And then he goes on to introduce and demo Web Components, which at the time were three different things: Scoped CSS, Shadow DOM and Web Components. (I also highly recommend his talk in general)
But then W3C happened. In true w3c fashion it went ahead and spent another 5 years defining the Platonic ideal and never feeding the progress as a result.
Cue in Facebook
Facebook application is complex. It might not look like it, but it is. Those little boxes everywhere on the page have surprisingly complex layouts which have to be repeated, and/or customized, and/or adjusted in various contexts. A developer would naturally want to do the following: take this box
element, and put it here, and apply these random styles to it without disturbing anything on the page.
And it has to be reasonably fast. Because, well, DOM updates are notoriously slow, and there are countless articles detailing how you absolutely must reduce the number of times you access the DOM.
It’s so bad that innerHTML
, a sign of horrible smelly code, is on par with or faster than DOM manipulation.
So, what did Facebook do? Oh, simple, they basically wrote their own implementation of Web Components entirely in Javascript. With an XML-based DSL to boot. They called it React and unleashed it unto the world in 2013.
React provides you with following:
- a way to define your own custom elements
- place them on the page with HTML-like syntax
- provides a fast virtual DOM implementation that minimizes changes to the actual DOM
- has very few limitations on what you can do with or within components because it’s Javascript all the way down (the DSL is a thin wrapper on top of a small number of functions)
No wonder React took the world by storm. Those who weren’t writing it were surely talking about it. It spawned several competitiors that are aiming for the same feature set (Inferno, Preact) or various subsets, most notably Virtual DOM (Snabbdom, virtual-dom etc.).
2017
In 2017 React fulfills all the promises of Web Components: it lets you write performant reusable self-contained components. It can run on almost any Javascript-enabled browser (React doesn’t support IE < 9).
As the ecosystem blossomed, it went far beyond the scope of Web Components. If I’m not mistaken, CSS Modules proposal appeared because it was first implemented in, and for, React.
In 2017 Web Components are in development despite already spawning two versions each of two of underlying standards.
At the time of this writing the situation for Web Components looks like this:
So what’s this broken promise I hear about?
Well, the main failure is obvious: they are nowhere to be seen. The promise of “feeding the process of progress” is unfulfilled. By their 6th year they spawned a total of 6 standards. Two of them are already deprecated. Only one major browser is commited to supporting them (sorry, Opera, you’re no longer a major browser, and you run on top of Chrome these days).
The other broken promise is the one bandied about in the Internets these days: interoperable custom components without vendor lock-ins.
And this is the one that got me writing this overly long piece of thinking out loud.
Because there’s Polymer.
Polymer is Google’s attempt at creating a Web Components-compliant implementation:
Polymer shows the main problem with Web Components: they are DOM.
The DOM
Consider the following code in React:
<MyComponent style={{border: '1px solid gray'}}>
{
['Hello', 'world'].map((text) => <p>{text}</p>)
}
</MyComponent>
This is a custom component. Its style is defined by a JavaScript object. Its children are defined by map
ing over an array of values and producing another component. In this case it’s a <p>
, but it could be anything. This component’s children is the current array value.
This XML-like DSL is directly translated into JavaScript:
React.createElement(
MyComponent,
{ style: { border: '1px solid gray' } },
['Hello', 'world'].map(text => React.createElement(
'p',
null,
text
))
);
A similar feat in WebComponents would be … well…
If you go for HTML as a counterexample for React’s DSL, it’s impossible:
<my-component style="only strings are allowed in attributes">
... nothing here ...
only other components or text allowed here
</my-component>
What about JS APIs? Remember I told you Web Components is DOM?
const MyComponent = document.createElement('my-component')
MyComponent.style.border = '1px solid gray'
['Hello', 'world'].forEach(('text') => {
const p = document.createElement('p')
p.textContent(text)
MyComponent.appendChild(p)
})
This gets progressively worse as your components grow in complexity. Imagine adding a span
around the text
in p
?
React? Easy-peasy. Just add it:
['Hello', 'world'].map((text) => {
return <p><span>{text}</span></p>
})
Web Components? Well, it’s DOM:
['Hello', 'world'].forEach(('text') => {
const p = document.createElement('p')
const span = document.createElement('span')
span.textContent(text)
p.appendChild(span)
MyComponent.appendChild(p)
})
Ad infinitum.
Let’s break compatibility
I assume the limitations described above were the immediate problem that Polymer faced. How do you work around this? Well, you invent your own not-really-JavaScript-but-kinda-Javascript kinda-templating-kinda-scripting kinda-language. That can only exist in strings.
Work your way through Polymer’s data system for a full overview. Below are just some examples.
Note: none of these [[]]
, {{}}
, $=
etc. are in the Web Component spec
<template>
<div>[[name.first]] [[name.last]]</div>
</template>
<my-input value="{{name}}"></my-input>
static get properties() {
return {
active: {
type: Boolean,
observer: 'userListChanged(users.*, filter)'
}
}
}
<div>[[_formatName(first, last, title)]]</div>
<a href$="{{hostProperty}}">
etc. etc. etc.
And my favorite one:
I mean, wat.
Seriously, y’all
In all seriousness though. Web Components ended up delivering hardly anything from their original promises (or have hardly answered any of the originally raised questions):
- Their specs depend on JavaScript to work:
- Custom Elements are a part of scripting
- HTML Templates exist only to be manipulated by scripts
- I don’t even know if Shadow DOM can be used without JavaScript
- And only HTML imports don’t need JavaScript
- They are DOM. So:
- attributes are strings
- content models are strange (we’ll have to wait and see how they end up interacting in deeply nested structures)
- To work around limitations (such as string attributes) libraries will (and have) come up with incompatible ways to pass data
- is Polymer’s
attr$='{{user.name}}'
better than Vaadin’sitem-label-path="name.first"
or Angular’s<div *ngFor="let hero of heroes">{{hero.name}}</div>
or more compatible with others? - What, where, and how should I import the entirety of a library X to deal with their weird ways of dealing with DOM limitations if I deal with multiple nested components?
- DOM APIs are horrible, cumbersome, awkward and clunky. Polymer and others are bravely trying to use DOM APIs only, but even they resort to
innerHTML
anywhere they don’t have to put on a show (tests, for example). When Web Components take root, the web will be flooded with less performantinnerHTML
s and possibly re-implementations of snabbdoms and virtual-doms (obviously incompatible) - how will this help with interoperability and vendor lock-ins if everyone will chose their own ways of dealing with this?
- is Polymer’s
- Scoped CSS…
- CSS Modules. Need I say more?
These are just a few warts I could come up wth off the top of my head. I haven’t seen them really truly discussed anywhere.
React team went as far as to say
Nowadays React lets you have them as the leaf nodes in the component tree because React assumes any component name that starts with a lowercase to be a DOM element.
To quote Pete Hunt:
There are very very few discussions about these issues except for comments on Twitter or on various articles. The consensus seems to be “Web Components is the glorious interoparable fast performant future”.
Is it?
Rob Dodson posted excellent response to this article: https://robdodson.me/regarding-the-broken-promise-of-web-components/. I highly recommend it.
Credits
- Image: Nobody Home Yet by Rich Herrmann, CC BY-NC-ND 2.0
- Links to some of the material were found in this blog post by Dr. Axel Rauschmayer