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.
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 <style>
and <link rel="stylesheet">
tags for the touch-action
property, and then replicates that functionality in JavaScript — which obviously has non-trivial performance implications.
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.
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 querySelector()
and 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 <style>
and <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:
I want to make it clear that the code examples I show above are not meant to be a reflection on the engineering effort put forth by Google, but rather an attempt to show the complexity inherent to reimplementing web components in JavaScript. The web components specification aims to give hooks into browser internals, so it makes sense that it's nearly impossible to replicate that functionality in JavaScript. Given this, the job the Google did with the platform polyfills is extraordinary.
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.
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."
Although I disagree with Google's interpretation of "relatively simply", I understand the sentiment here, as truly polyfilling all of shadow DOM would require rewriting CSS in JavaScript, and even Google agrees that goes too far.
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 next issue is sheer file size. As of version 0.3.3, the platform polyfills comprise 151K of JavaScript, 44K gzipped. If you choose to use Polymer on top of the polyfills, you can add another 66K, 20K gzipped. For comparison, people complain incessantly about the size of jQuery, which is 29K gzipped.
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.
Beyond the overhead of shipping the polyfills across the network, there's also the time it takes for the browser to parse and interpret the JavaScript code, which is a known performance bottleneck on mobile devices. Polymer's documentation states that they do not yet do performance benchmarking, which is unfortunate considering that many of the things Polymer's polyfills do are inherently slow.
One performance issue worth specifically discussing is the numerous requests generated by HTML imports. The idea behind HTML imports is great: a single .html file that contains everything you need — templates, CSS, JavaScript, other HTML, and so forth. As such, it's relatively common to see an HTML import that depends on several other resources, each of which must be resolved over HTTP.
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.
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:
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.
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.
TJ VanToll is a frontend developer, author, and a former principal developer advocate for Progress.