Posted by: digdug2k | August 11, 2014

Playing with WebComponents

I’ve been meaning to do some more random blogs lately, so this will hopefully be the start of that. I spent a bit of time lately playing with WebComponents. I always really loved XBL. The idea of shipping small compartmentalized widgets for the web is probably one of the things that made me most interested in the web to begin with. I’ve been pretty excited to see what the follow up was (and frustrated that XBL never got the love it deserved, and even more frustrated to see XBL2 get made fun of while at the same time everyone tried to replicate it).

Polymer

Turns out, its kinda hard to figure out what web components are right now anyway! When I started searching for some examples, right now you’ll be overcome with results pointing to Polymer components. i.e a really basic example looks something like:

<polymer-element name="my-counter" attributes="counter">
  <template>
    <style> /*...*/ </style>
    <div><content></content></div>
  </template>
  <script>
    Polymer({
      counter: 0, // Default value
    });
  </script>
</polymer-element>

Surprised? Probably not, but I was. What is the word polymer doing all over this content? What is this “Polymer” method that’s being called? I thought Polymer was a POLY-fill, hence its name. Turns out its not (anymore?). Now its just some sort of framework on top of a polyfill? A shim?

I was a bit frustrated at this point, as I really just wanted to write something standard and know that it would fallback gracefully as browser support came out. I also wanted to learn the WebComponents spec, not some shim on top of it. So I tossed that aside and glanced at a few other projects I thought were aiming to do the same thing.

X-Tag

First Mozilla’s x-tag. I haven’t heard anything about x-tag in a long time, so i assumed it was dead. The site’s still there though, and, even though the blog posts are a bit old, I didn’t see any “This is deprecated warnings”. Glancing a little harder, I did see that they had given up on writing their own polyfill at some point, and had fallen back to using some of Polymer’s code. Unfortunately, the API STILL looks like its actually a shim on top of that polyfill. Not what I quite wanted:

xtag.register('x-accordion', {
  extends: 'div',
  lifecycle:{
    created: function(){ },
    inserted: function(){ },
    removed: function(){ },
    attributeChanged: function(){ }
  },
  events: { },
  accessors: {
  },
  methods: {
  }
});

At this point I actually gave up. I decided that x-tag was the simplest solution I knew of, and decided to just use it. A single file to import, and I’d be done. The API looks nothing, nothing, NOTHING, like what I thought WebComponents should look like, but neither did Polymer’s. So on I charged. I actually converted a chunk of my project, graph.js, to use it. But I recently stumbled on webcomponents.org and thought it would be fun to upload this stuff there once I have it working using the real standard. With that in mind, I decided to try again to convert over to the real spec.

Brick

The only other work I’ve really heard about in this area is Mozilla’s Brick, which isn’t really a polyfill, but just a set of components you could use if you had a polyfill. A little digging there told me they were actually using the core part of Polymer that I hadn’t found before. A file hidden in the bowels of Polymer named platform.js.

This seemed really promising, I opened some Brick files and started editing a bit trying to get things into the shape I wanted. Brick’s code looks like:

<template>
  <style></style>
  <content></content>
</template>

<script src="something.js"></script>

The old spec

These Brick files are actually very close the spec, but I didn’t know that at the time. You see, the w3c actually hosts a nice set of WebComponent examples already. If you glance at them, they look like:

<element extends="table" name="x-table-chart" constructor="TableChart">
    <script>
        this.lifecycle({
                    created: function(shadowRoot) { },
                    inserted: function() { },
                    removed: function() { }
        });

        TableChart.prototype.draw = function() { }

        this.reflectAttribute('x-chart-type', 'chartType', TableChart.prototype.draw);
        this.reflectAttribute('x-chart-color', 'chartColor', TableChart.prototype.draw);
        this.setupEvent('draw');
    </script>
    <template>
        <canvas></canvas>
    </template>
</element>

Notice the <element> tag wrapping everything, and the use of some special functions inside the element tag (i.e. lifecycle, reflectAttribute, and setupEvent). It also has some attributes to declare the node name and its constructor. This all makes sense to me at a glance. I assumed there’d be some sort of warning at the top if it was seriously deprecated. I was wrong about that.

After trying to hack my code into using platform.js for a bit and then trying to hack the W3C false-example code above into working with it, I was again frustrated. After a long battle, I glanced at the script file in the Brick demos a little harder and noticed things like:

BrickDeckElementPrototype.attachedCallback = function() {
    var importDoc = currentScript.ownerDocument;
    var template = importDoc.querySelector('template');

    // create shadowRoot and append template to it.
    var shadowRoot = this.createShadowRoot();
    shadowRoot.appendChild(template.content.cloneNode(true));
};

It looks like Brick is doing all the work I basically thought WebComponents was supposed to do for me. i.e. Its mirroring the template into its parent manually. Its even creating the ShadowDOM AND ShadowCSS manually! What? What in the world is platform.js good for then?

polyfill.js

I got really frustrated. All these polyfills, and none of them seemed to support the standard in any way. Last night, I got really angry and decided to write my own polyfill for the <element> node above. Turns out, its not that hard. I don’t even need that polyfill to be “good”. I just wanted something general enough to know my components would work. A few hours later I had a file named polyfill.js working pretty well.

At that point I started firming up polyfill.js a little bit. I opened the actual specs and implemented a bunch of easy link[rel=”import”] behaviors (and a native fallback if available). I then started looking for things like the <element> node or the life cycle object, and realized they don’t exist there. Turns out, the example code I had found was old, and drastically out of date.

I had been avoiding simple task until this point, reading some HTML5 Rocks articles on WebComponents. HTML5Rocks is basically a site for Google PR. Up until this point, I had assumed the articles there would basically be “Use Polymer! Its great!” Happily these aren’t.

So one of these articles has, carefully buried at the bottom, a comment about the old <element> syntax, essentially pointing out that its deprecated. The new WebComponents syntax looks a whole lot like whats in the Brick code above. There are a few pieces available to you:

The Shadow DOM
This is the spec I care about LEAST, so I haven’t bothered tor read much of it. I assume its a lot like Anonymous content in XBL. i.e. Nodes that are in the document, but that don’t show up in your normal walking of the tree. From what little I’ve read, there’s been some hard work here to make sure components are encapsulated so that CSS and JS don’t affect the main document. There’s also a strange bit here, where every node can contain a “shadow-root”, which is the start of its internal content (unless you decide to not use the ShadowDOM at all). It seems a bit silly and redundant to me to have that shadow-root. Your markup moves one level deeper with no significant gain. But I assume there was a long debate and people agreed it was necessary at some point.
Custom elements

This is the heart of the WebComponents that I cared about. Its basically a way to “register” a new node “type” with the DOM. i.e. you call

document.registerElement(name, { prototype: something })

Every time a node is created that says it “is” whatever your node name is, your prototype will be applied. Your prototype can also include methods like createdCallback, attachedCallback, detachedCallback, or attributeChangedCallback, which will be hit when those things happen (why they decided to use the terms “attached” and “detached” is confusing to me, since they don’t really appear anywhere else in the DOM).

HTML Imports
HTML Imports it turns out, don’t really do that much. They just pull in some HTML, and if its a script, they execute it in the context of this page’s document. They’re a handy way to encapsulate a bunch of scripts, css, and html in a single document.

I like this separation. I like that this stuff is low level. In fact, it would be neat to see it go evern lower level. It means you can build shims on top of the setup if you want. You don’t have to use imports if you don’t want. You don’t have to use the ShadowDOM if you don’t want. Things like Polymer or x-tags if it was still alive could build abstractions on top if they want. Its neat, clean, and fairly low-level. Its the way I wish web apis were built more often. I tinkered around in the web-screens spec recently this year, and got fairly frustrated with it for NOT being low level.

With some new knowledge, I went back to my polyfill and made the changes to work with this syntax. It actually works pretty well right now. As I said earlier, I don’t intend to support the ShadowDOM much. I don’t need it here (although I do support creating a shadow root node). My polyfill is up on github at polyfill.js. graph.js is completely broken right now, but I think I’ll port it over. I should at this point try to make platform.js work instead, but quickly flipping it on resulted in some strange errors. Debugging is painful enough that I haven’t started yet. Feel free to use/alter/patch/whatever you need. I have a feeling there are much better ways to do some things like attaching a prototype to a node), but this is VooDoo no matter how you do it now.


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Categories

%d bloggers like this: