Web Components (and Polymer)

My Coder’s Next blog is about what is “next” in software development and I cannot think of a better topic to start this blog than Web Components. The emphasis in the blog is what is possible now. With this post and my next one, I hope to show a form of Web Components that can be used in production code now.

I think there is very little doubt that some form of Web Components is the “coming thing“. However, I have serious doubts about Google’s proposed mechanisms to give us Web Components. They are ill-thought out and don’t really accomplish the goals Google says they are trying to reach. Other mechanisms are possible. This post is about the short-comings in the proposed model which Google has recently added to Chrome. My next post will deal with an alternate approach that works now and will likely make it easier to migrate to full Web Components as wide-spread support becomes available.

Why do we need Web Components?

Google actually does a good job presenting this. Often they talk about, “DIV Soup”. The idea is that modern HTML is filled with <div> elements and that it is hard to understand what is going on. This is generally accompanied with a screenshot of the developer console showing the internals of Gmail. (See the image in the article header above.) Yes, this is a mess, but then so is assembly language. The problem is that we are coding the web in the equivalent of assembly language and then complaining that it is verbose and difficult to understand. Further, much of the problem is because the code in question is minified. Minification is necessary to help protect Google’s proprietary interest in its code.

Additionally, they talk about expressivity. Rob Dodson says he would like to replace:

1
2
3
4
5
6
7
8
<!-- Bootstrap Dropdown -->  
<div class="dropdown">
<ul class="dropdown-menu" role="menu" aria-labelledby="dLabel">
<li><a tabindex="-1" href="#">Action</a></li>
<li><a tabindex="-1" href="#">Another action</a></li>
<li><a tabindex="-1" href="#">Something else here</a></li>
</ul>
</div>

with:

1
<fancy-dropdown></fancy-dropdown>

A laudable goal, but Polymer is a very expensive way to do this. It doesn’t begin to solve the “DIV” soup problem… Polymer in fact makes it worse by adding additional layers of Shadow DOMs and DIVs.

Regardless, Google is right, we need better expressivity when writing and debugging HTML.

Google’s proposed solution

Google’s Polymer project is not Web Components per se, but is built upon them and is the only major library anywhere near finished, so I will be using it in my examples.

What Google calls Web Components, consists of 4 parts.
(The ordering below reflects my opinion of the technologies, from best to worst.)

  • Templates
  • HTML Imports
  • Custom Elements
  • Shadow DOM

Templates

Templates for the dynamic creation of elements in HTML are nothing new. Apparently, most of the frameworks do something like this:

1
2
3
4
5
6
7
8
9
10
<script id="temp" type="text/template">
<button>{{dynamicText}}</button>
</script>

<script>
var template = document.getElementById("temp").innerHTML;
/* call templating engine on template to replace {{dynamicText}} */
var d = document.createElement('div');
d.innerHTML = template;
console.dir(d);
</script>

The <template> tag is really just a refinement of that. It mostly avoids the tedious and possibly hazardous mucking about with .innerHTML. I have not studied the spec in detail, but it is likely a good idea in practice as well as in principal.

HTML Imports

HTML imports are born of a desire to store CSS, HTML and Javascript in one file per component. A laudable goal, but terrible in practice. An HTML import is done with <link rel="import"...> tag. To make a menu in Polymer I need to add five of these links for the 5 needed components. Each link is a separate download from the server. Imagine a large app with 100 components… it’s not like images… you have to wait for all the components before it is safe for your program to function. This just doesn’t work. Google’s made a tool that condenses all the html import links in your html file into one file. But surely, that is a strange way to do things. Shouldn’t they have made the program parse the HTML, and automatically find the custom-element files? The HTML import tag could then be used to import ONE file which contains the concatenation of the needed files. It is even reasonably simple to make a way of doing it that does not need the HTML import facility at all.

To be clear, HTML Imports are a nice feature, but it is not a proper way to handle individual Web Components.

Custom Elements

The concept here is that you get to define your own HTML tags (such as <custom-element-name>. In principal, these are a great idea, who wouldn’t want to say:

1
2
3
4
5
6
<menu icon="hamburger"> <!-- Or text="Menu Name" -->
<mi>Action 1</mi>
<mi>Action 2</mi>
<separator>
<mi>A Different Sort of Action</mi>
</menu>

But in practice using Polymer, it comes out looking more like this:

1
2
3
4
5
6
7
8
9
10
11
<core-menu-button relative>
<core-icon-button id="trigger" icon="menu"></core-icon-button>
<core-dropdown class="dropdown" relatedTarget="{{$.trigger}}" layered>k
<core-menu class="menu">
<core-item label="Import..."></core-item>
<core-item label="Export..."></core-item>
<core-separator></core-separator> <!-- Polymer hasn't made this one yet. -->
<core-item label="About..."></core-item>
</core-menu>
</core-dropdown>
</core-menu-button>

Worse it also requires me to add at the top of my HTML:

1
2
3
4
5
<link rel="import" href="bower_components/core-dropdown/core-dropdown.html">
<link rel="import" href="bower_components/core-menu/core-menu.html">
<link rel="import" href="bower_components/core-menu-button/core-menu-button.html">
<link rel="import" href="bower_components/core-icon-button/core-icon-button.html">
<link rel="import" href="bower_components/core-item/core-item.html">

This simply doesn’t constitute progress.

And there are numerous limitations:
(All are understandable, but doesn’t make your job any more pleasant.)

  • Custom element tag names must always have a ‘-‘ in them.
  • Custom attributes must always start with data-. (Google violates this in Polymer).
  • To sub-class a built-in element you have to use a syntax like: <button is="my-custom-element">
  • Custom elements must always have an end tag even if it is not desirable (for example: <core-separator></core-separator>).

Now don’t get me wrong. Because the registerElement call provides a standardized way to let you define new HTML tags, I do believe it will play an important role in the future of web components, but it is not free… Using it as a simple wrapper to get <fancy-dropdown></fancy-dropdown> is a bad idea. At the very least one will generate an extra HTMLElement and a layer of JavaScript invocation overhead. (Fine for one element, but what about 1,000?) If you use Polymer, the overhead as much as triples. And that doesn’t even count the extra overhead of /deep/ (see below) on a DOM tree that can be 3 times bigger than what it would have been without Polymer.

Shadow DOM

This is clearly the worst of the four. So much so that webcomponents.js (the Web Components polyfill used by Polymer and Mozilla Brick) now comes in two forms (one with and one without Shadow DOM). While in principal, one might want a way to encapsulate styles within a component. And initially at least, one thinks one would like to hide the DOM elements within a component. The “cure” here is clearly worse than the disease. It will make browsers even harder to write and will generate many efficiency issue. I have real doubts this proposed standard will be accepted.

Even if it does make it through the standards process, it will be years before it will be in the major browsers and the polyfill is huge and scary. (Mozilla and Apple have both questioned Shadow DOM in its current form.) Polymer’s polyfill for Shadow DOM is simultaneously the most breath-taking and horrifying work of engineering I’ve ever seen. My hat’s off to the people that did that work, but to quote Jurrasic Park:

Yeah, yeah, but your scientists were so preoccupied with whether or
not they could that they didn’t stop to think if they should.

Dr. Ian Malcolm

I won’t cover why it’s a bad idea to use the Polymer (and now Brick) polyfill (at least the one with Shadow DOM) in depth, but you can learn more in this article. I strongly suggest you explore the internals of the polyfills before depending on them in production work. I certainly have no intention of using a Shadow DOM polyfill for my production code.

When you first hear about Shadow DOM… you think of course I want my components to be isolated… but think again… you really probably don’t. You want your buttons to match your inputs to match your fancy animated-cookoo-clock component, right? That’s shadow DOM’s worst problem: theming.

Google has made a whole range of new CSS selectors to “solve” the problem. Previously, when you informally made a component, you name-spaced it, something like class="myc-cookoo-activate-button", which you then used in your CSS rules:

1
2
3
.myc-cookoo-activate-button {
background-color: chartreuse;
}

The styles were all in one file and if things were complicated enough, you’d use LESS or SASS to be able to change that hideous chartreuse you put in all your components to something better by making only one change in the file.

Now with Shadow DOM, you can have all that information separated on a per component basis. You can change them all one by one by hand, or use the sledgehammer approach. In your main CSS file, you would put:

1
2
3
my-cookoo-activate-button::shadow button {
background-color: chartreuse;
}

And we now have an additional place to look for miscreant styling and the CSS is more verbose.

This is not progress.

Worse, they’ve created the /deep/ combinator. A sample:

1
2
html /deep/ [layout][start] {
}

This rule would set styles on every element in the document, including inside Shadow DOMs. The rule would need to be checked every time any style was changed inside the document. Now I’m sure you saying… well it only takes effect if you use it. True… but Polymer uses it 46 times in its layout.html file alone. (It uses it elsewhere as well.)

This isn’t just “not progress”, it’s a step backwards.

Look at this cheatsheet if you want to know the details of the complete set of combinators.

But styling is not the only problem with Shadow DOM. It was supposed to help us with “DIV Soup”… but it really doesn’t, we now get DIV and Shadow DOM Soup. Open the developer tools on any Polymer app… to figure out what is going on, you’ll still need to open all those DIVs, but now you also get to open all those Shadow DOMs which subsection the DIVs. Between the Shadow DOM and the extra HTMLElement that registerElement gives you, you can have as much a 3 times the number of DOM elements.

This is not progress.

Quite simply the whole notion of Shadow DOM needs to be rethought. I’m not even sure it is a good idea at all. If you have a large component that needs to be that isolated, why not use a webview?

Other Polymer Sins

Polymer gives you a large selection of “layout” attributes, such as flex. These seem to be in direct violation of the w3c draft standard for HTML5. I understand that they are convenient. But this isn’t the way to do it. At least Angular namespaces such attributes with “ng-“. This gratuitous use of simple attribute names like “flex” will lead to clashes with other packages later. (Google trying to be the next perpetrator of an IE6-like fiasco?) Adding insult to injury, they use the /deep/ combinator to implement these attributes.

Polymer forces you to use a Shadow DOM on every element. Why? Suppose I want an hbox component:

1
2
3
<div style="display: flex; flex-direction:row">
<!--content goes here-->
</div>

Why would I want a Shadow DOM around that? Is that extra HTMLElement I get from registerElement necessary? But I’d really like to say <hbox>...</hbox>.

Surely, there must be a better way…

Modern programming language debuggers usually have to understand what is going on at the machine code level. What is usually shown to the programmer is the programming language level constructs. Put simply, HTML/CSS is low level, details often have to be dealt with there. How about something higher level, that doesn’t take away any of the power and efficiency of HTML/CSS? That will be the topic of my next post.