A dissection of Polymer 3.0

Blue Harvest
Blue Harvest Tech Blog
6 min readNov 6, 2018

by Kalle Karamell, our Senior Software Engineer at Blue Harvest

Two weeks fresh into Blue Harvest I was put in a head-first dive to deliver our official web site. My colleague set up a repository with a Polymer 3.0 boilerplate, but was then too busy to continue with it, so had to do the work myself. Never used Polymer before, I accepted the challenge. At least I could try to prove that I’m entitled to the senior engineering position that I’m hired to be.

The short Polymer 3.0 developer guide left me with a lot of questions unanswered. In this article, I will try to answer some of these. At the time of writing, Polymer 3.0 is very new and lacking a lot of resources. Coming from a background of framework development myself, I generally approach learning while focusing on the why rather than on the how.

From version 2.x to 3.x, Polymer moved from HTML to pure JavaScript. According to Polymer it’s a “move toward the mainstream of JavaScript development, making it easier to use Polymer (and Polymer-based elements) with other popular libraries, frameworks and tools”. Apparently there is quite some opposition to this idea among in Polymer’s own user-base, and a heated discussion in this issue also points out that another reason is the uncertainty whether HTML imports will actually be implemented by certain browsers.

Makes sense! Let’s try it out.

In Polymer 3.0, the first thing that strikes me is that the view definitions are declared with strings. My initial response is confusion, followed by the question: Why?

class Greeter extends PolymerElement {  static get template() {    return html`
<h1> Hello world </h1>
`; }}window.customElements.define("template-greeter", Greeter);

There are immediate problems arising from this decision, the most obvious one being the lack of syntax highlighting inside the template strings inside my IDE (Visual Studio Code):

Booo, no syntax highlighting :(

Turns out that I had to switch to Atom for this one:

Phew, looks better

Polymer is based around standards. Rather than having a custom templating language like JSX, I can write all my code in .js files. I was positively surprised when I saw that the file is running, in my browser, exactly as I wrote it.

Source maps are turned off, but everything is still super-native. Cool!

Okay, but how is performance ensured? Vue can pre-compile .vue files, and Babel transpiles JSX into plain Javascript, so how can parsing HTML on the fly seem like a good idea, especially considering data binding and reactive updates? It turns out Polymer is one step ahead of the game by utilizing a new-ish (non-IE) feature called HTMLTemplate. A HTMLTemplate is a portion of reusable markup that is parsed but not rendered, implemented by the browser.

Cool, so parsing HTML is potentially lightning fast. But how about data binding?

class Greeter extends PolymerElement {  static get template() {    return html`      <h1 on-click="onClick"> Hello [[name]] </h1>    `;  }  onClick() {    this.name = "Karl";  }}window.customElements.define("template-greeter", Greeter);

I debugged the above code to investigate how [[name]] is bound to this.name. It turns out that a RegExp search is being executed on the string. No magic here, bummer. This is what I found in polymer/lib/mixins/property-effects.js:

const IDENT = '(?:' + '[a-zA-Z_$][\\w.:$\\-*]*' + ')';
const NUMBER = '(?:' + '[-+]?[0-9]*\\.?[0-9]+(?:[eE][-+]?[0-9]+)?' + ')';
const SQUOTE_STRING = '(?:' + '\'(?:[^\'\\\\]|\\\\.)*\'' + ')';
const DQUOTE_STRING = '(?:' + '"(?:[^"\\\\]|\\\\.)*"' + ')';
const STRING = '(?:' + SQUOTE_STRING + '|' + DQUOTE_STRING + ')';
const ARGUMENT = '(?:(' + IDENT + '|' + NUMBER + '|' + STRING + ')\\s*' + ')';
const ARGUMENTS = '(?:' + ARGUMENT + '(?:,\\s*' + ARGUMENT + ')*' + ')';
const ARGUMENT_LIST = '(?:' + '\\(\\s*' + '(?:' + ARGUMENTS + '?' + ')' + '\\)\\s*' + ')';
const BINDING = '(' + IDENT + '\\s*' + ARGUMENT_LIST + '?' + ')'; // Group 3
const OPEN_BRACKET = '(\\[\\[|{{)' + '\\s*';
const CLOSE_BRACKET = '(?:]]|}})';
const NEGATE = '(?:(!)\\s*)?'; // Group 2
const EXPRESSION = OPEN_BRACKET + NEGATE + BINDING + CLOSE_BRACKET;
const bindingRegex = new RegExp(EXPRESSION, "g");

When a binding is found, a getter/setter property accessor is set on the this scope. The neat thing is that I didn’t have to declare this.name to be a special data property. Pretty slim syntax.

Polymer takes note of where that variable was used and manages to keep all subsequent changes limited to the place where the variable was used. That means no tree walking for detecting DOM updates (a.k.a React-style reconciliation). This also means that the template is only parsed once. I’ve yet to see any performance benchmarking on Polymer 3.0, but I can imagine that it might be browser dependent.

Another consequence from the one-time parsing of the template is that just like in Polymer 2, we have to resort to elements like <dom-if>or <dom-repeat> when we want to do any type of control flow, so just because Polymer moved to JavaScript doesn’t grant us the JavaScript control flow for loops, conditionals, etc.

Routing

Whether Polymer counts as a framework or not is disputable, but it turns out that routing is now in stable beta. Developers with the framework mindset probably tend to put Polymer in the framework bucket and expects it to live up to features such as routing. Routing is a very framework-like feature and I would imagine that Polymer would feel inclined to provide it.

This is how it can look like:

<!-- app-location binds to the app's URL -->
<app-location route="{{route}}"></app-location>
<!-- this app-route manages the top-level routes -->
<app-route
route="{{route}}"
pattern="/:page"
data="{{data}}"
tail="{{tail}}">
</app-route>

The {{route}} is a bi-directional binding that makes sure that the route from app-location propagates to app-route. It’s a bit hard to see where all this is coming from, and why there are two separate HTML elements for doing one task. Polymer explained their rationale behind this in a blog post, referring to it as encapsulated routing. In this post, the Polymer team even dares to take a stab at other routing solutions that allegedly do things poorly:

Most client side routers are monolithic black boxes that require a complete and centralized route configuration with perfect knowledge of every routable location in the app. It’s also common for routing to be conflated with other concerns, like loading data or transitioning between views.

Framework competition is always interesting when they start bashing each other, right? Short summary is that app-location is responsible for reading the URL, whereas app-route is a utility class to bind a certain pattern (like /users/:userName) to determine what the rendered page should be.

The resulting web page looks like this. I’m not entirely sure whether I would like to use Polymer for a larger scale project. The clear advantage is that it’s very lightweight, but that comes with downsides.

Not having to bundle any resources means that I can miss syntax errors which would have normally surfaced during bundling or transpiling. The browser has a tendency to silently self-repair unaligned tags and syntax errors in scripts which will only show up when (dynamically) loaded. This puts a greater burden on the linter. Because Polymer is using native ES6 modules, there’s interoperability problems with the UMD design, which was patched in October last year. However, packages that were published prior to that simply can’t be used.

It’s also harder to find out what best practices to use when there’s not a big framework to hold your hand. On top of that there’s a new major version that much in use yet. There’s great freedom, which also means much more responsibility. Kind of like not using a framework at all.

But the intention of Polymer isn’t even to address these issues. Polymer wants to expose the capabilities that browsers are building up each day without many people even noticing. Polymer wants to future-proof our developer skills. They go under the hashtag #UseThePlatform, which doesn’t necessarily coincide with #OptimizeDevelopmentRobustness, a hashtag invented by the author.

--

--