Web components are the new hotness. And now that a complete web components implementation landed in Chrome 36, we finally have stable, unprefixed, unflagged version to try out. But, although web components are certainly something to be excited about, and a technology worth experimenting with, that doesn't mean that they're ready to use in your production applications — because for most applications, they're not.
In this article you'll see why that is. We'll discuss the current issues with using web components in production, and what needs to be done to solve them.
The obvious reason to avoid web components is browser support. Although web components landed in Chrome 36, they only have partial support in Firefox, and they are not present in Safari or IE. Because cross-browser support won't be possible for a very long time, if it happens at all, a polyfill is a long-term necessity for developers that want to use web components outside of Chrome.
And there is seemingly good news on this front: the Polymer team maintains a complete set of polyfills collectively known as "The Platform". But although Polymer's official documentation makes it seem like getting cross-browser web components support is as easy as including platform.js, things aren't quite that simple.
All polyfills aren't created equal
Although developers tend to think of a polyfill as a polyfill, the complexity of the implementations can vary widely depending on the technology being polyfilled. APIs that are syntactic sugar for existing APIs tend to be the easiest to implement. For instance you can write a
Function.prototype.bind() polyfill in a handful of lines of code, and a
classList polyfill polyfill in about 70 lines of code.
APIs that add entirely new behavior, or that include CSS syntax changes, tend to be harder to write. For example consider a pointer events polyfill. Because pointer events work with the
touch-action CSS property, and because browsers ignore CSS rules for properties they do not understand, pointer event polyfills have to get creative. Polymer's pointer events polyfill requires you to use a
touch-action attribute on elements — e.g.
<div touch-action="none"></div> — and forego the CSS declaration entirely. Microsoft's HandJS polyfill does a text-based search of all
<link rel="stylesheet"> tags for the
The point here being, some technologies are harder to polyfill than others. Why is this relevant? Well, as it turns out, polyfilling web components is the king of all polyfilling challenges. Simply put, writing a web components polyfill is absurdly difficult, and the inherit complexity has non-trivial implications on the viability of using web components polyfills in production. Let's look at some examples to demonstrate this.
The insanity that is polyfilling web components
To start, consider the encapsulation that the shadow DOM specification allows. When HTML elements live within a shadow root they are hidden from functions such as
querySelectorAll(). For example, in the following screenshot
querySelectorAll() does not find the
<h1> element because it's within a shadow root:
Think for a moment how you would polyfill this behavior. Crazy, right? But, if you add Polymer's platform.js to the same example, it unbelievably works. That is,
querySelectorAll() does not find elements within a shadow root — in browsers that have no concept of a shadow root. The following screenshot shows this behavior in Safari:
I had to run this a few times to verify my own sanity, as I had no idea how this was even possible. How does a polyfill do this?
The answer is, Polymer's shadow DOM polyfill wraps a large number of DOM methods — at least 25 of them — with a series of customized shims that exclude elements that reside within its internal list of shadow roots, and it does this for over 30 HTML element interfaces. No, I'm not kidding.
And we're just getting started with the crazy polyfilling challenges that web components present. The shadow DOM specification also defines a number of new CSS concepts for exposing and selecting elements that reside within shadow roots. So, because browsers discard CSS selectors and rules they don't understand, this means that polyfills must resort to text-based searches of
<link rel="stylesheet"> tags. This 38-line block of regular expressions defined in platform.js' source should give you an idea of the difficulty of doing this:
Why this stuff matters?
But, given that Google has already done the hard work, why should you care about how difficult it was? Because the complexity has several adverse effects on the feasibility of using these polyfills in production. Let's look at each of these issues in turn.
Not everything is supported
The first issue is, even with some of the code examples shown above, Polymer's polyfills do not support all the functionality that web components offer (most notably shadow DOM), or as the source says:
"The intention here is to support only the styling features which can be relatively simply implemented. The goal is to allow users to avoid the most obvious pitfalls and do so without compromising performance significantly."
The problem is, from a developer's perspective it's hard to know what will work and what won't. The only documentation of this I can find is hidden, such as this 113-line comment in a source file discussing which portions of shadow DOM's CSS is supported.
Remember the example above of
querySelectorAll() not finding an
<h1>? That same example has different behavior when styling that
<h1> in a
<style> tag. The following shows the difference in Chrome and Safari:
The polyfills are also big
Polymer does do a good job of separating the polyfills into separate modules so you can pick and choose the ones you need, but in practice almost no one actually does that (the one exception being Mozilla's X-Tags library). All of Polymer's elements, and most (all?) of the elements listed on http://customelements.io/ and http://component.kitchen/ depend on Polymer, which depends on the platform in its entirety.
In browsers that support HTML imports natively — i.e. in Chrome — the browser can resolve these resources using parallel connections, usually ~6–8 per hostname, as well as leverage speculative parsing algorithms to optimize the loading of these resources. This works great in Chrome. But in all other browsers, when HTML imports aren't natively supported, polyfills must resort to queued up XHRs, which can be painfully slow.
This isn't very noticeable on a demo of one web component on a high-end development machine, but it shows on pages with multiple components — especially on slow networks and mobile devices. The best example of this is actually Polymer's site itself, which uses Polymer to build everything you see. Visit http://www.polymer-project.org/ in any browser that isn't Chrome 36+ and see the performance issues for yourselves (iOS Safari and IE Mobile are particularly bad).
Now, there is some good news on this front, as some tooling is emerging to aide with the performance issues. Vulcanize is a build tool from the Polymer team specifically for HTML imports. In a nutshell, you pass Vulcanize an HTML file and it outputs that HTML file with all HTML imports inlined, including deep dependencies. For example, the following inlines all imports in an index.html file, and places the output in a built.html file:
$ vulcanize -o built.html index.html
That being said, build tools such as Vulcanize are still in their infancy. A lot more research needs to be done to show how to incorporate a tool such as this in to existing development processes and workflows.
So what does this all mean?
Web components and Polymer are exciting technologies that may fundamentally change the way we develop web applications, but because of the large performance gap between browsers that support the technologies natively (aka Chrome 36+) and those that don't (aka every other browser), it will be difficult for most developers to use web components until they're implemented everywhere, and there's no way of knowing how long that will be.
Here are a few things I think would help the adoption of web components:
- Browser support—Duh. But seriously, web components without any polyfills perform great in Chrome 36+; it's the polyfilling that causes the issues.
- Performance benchmarks—If the Polymer team expects people to use web components in real apps, then they should start benchmarking the polyfills in the browsers developers need to support, especially mobile ones.
Bear in mind that none of this is especially surprising given that Polymer is still officially designated as a "developer preview". I'm confident that better tools, better modularity, and better performance will come in time, but Polymer and web components are still really new in the web development world.
How This Affects Kendo UI
At Telerik we get a lot of questions about when Kendo UI will support web components, as UI widgets are seen as an ideal use case for the web components model. But, although we certainly look forward to the day we can provide
<kendo-calendar> as an API, we have to weigh that against the need to provide high quality and performant components for our users in the browsers we support.
We're continuously researching Polymer and the web components specification so that we can leverage the benefits they provide when appropriate, and the research presented above is a direct result of that. Simply put, Kendo UI does not support web components today because we cannot provide high quality and performant components using them.
We are, however, concentrating on making our widgets as easy as possible to use with production-ready technologies; therefore, although you can't write
<kendo-calendar> yet, you can write
<div data-role="calendar"> using our declarative data bindings, as well as
<div kendo-calendar="..." k-ng-model="..."> using our comprehensive set of Angular directives. We're also constantly listening to what our users want. If you're interested in seeing web components and/or Polymer in Kendo UI, let us know in our feedback portal.
This article purposely focused on the feasibility of using web components in production today, and did not touch on whether web components are actually worth using — that discussion is coming in a future article.