Introducing Polymer - Testing, Building, and Deploying Components with Polymer - Developing Web Components (2015)

Developing Web Components (2015)

Part IV. Testing, Building, and Deploying Components with Polymer

In Part III we went over the core of web components and their constituent technologies, gaining an understanding of the complexity that they present. In Part IV we will be porting our web component to take advantage of the Polymer framework, incorporating it into a Grunt build process, packaging it with Vulcanize, and publishing it with Bower. We’ll also take a look at some of the currently available options for testing web components and show the end-to-end process of writing and running unit tests using Karma.

The Polymer Project is more than just a pleasing API for web components. It provides many, many shims that bring the future of web development to most relatively modern browsers today. Technologies like the shadow DOM aren’t the easiest to polyfill, but the community has rallied around this suite of shims in order to provide consistent behavior across all modern browsers and in all competing frameworks. This means that you can feel relatively confident investing time in understanding Polymer, what its developers are trying to do, and how they are doing it even if you have not fully bought into the API.

As your projects grow and your usage of Polymerized web components increases, you’ll soon find that managing web component source files requires a different level of concern than traditional development. You’ll be managing multiple JavaScript, HTML, and CSS resources that can’t be concatenated blindly with others of their kind. Gone are the days of treating JavaScript as an afterthought, and it is important to consider your source code as first class, having its own build process, pipeline, unit tests, linters, and analytics. Committing to a build tool like Grunt or Gulp early will allow you to manage these disparate tools, creating a task chain with a smooth flow and resulting in fewer headaches and less maintenance frustration. Vulcanize, a project created and maintained by the Polymer group, will help you package your web components for deployment or distribution. Finally, we’ll cover publishing with Bower, an excellent tool for development that also provides an intuitive and standard way for users of your libraries to install them and get started quickly.

Chapter 14. Introducing Polymer

Jarrod Overson

There are a number of caveats to the use of web components as they stand today, but there are lots of people from lots of companies doing their best to help get you started as quickly as possible. Polymer is one such project that has gained traction quickly and has proven itself to be invaluable in the web component revolution. Polymer is a BSD-licensed suite of libraries that enable you to be productive with web components immediately.

POLYMER MISSION STATEMENT

The expressed focus of Polymer is to “embrace HTML as much as possible by encouraging the use of custom elements wherever possible.”

To help speed the adoption of future web technology, Polymer’s authors have created shims for all related aspects of web components and tangential technologies. These shims are entirely usable outside of Polymer and are accepted as standard shims by companies like Mozilla and Microsoft, who use them with their own web component frameworks.

Considering that it is such a bleeding-edge technology, the Polymer group has done excellent work easing the transition to web component adoption. Polymer (outside of the Polymer elements) consists of two distinct layers of code. In this respect it is similar to jQuery, which is made up of jQuery itself and Sizzle. Sizzle is the independent selector engine that normalizes DOM selection for browsers that don’t support getQuerySelector and similar methods. Most people aren’t even aware of this abstraction because it’s largely invisible to the end user.

Polymer proper is the opinionated sugar on top of standard APIs that provides a satisfying framework for creating and managing web components. The second, lower layer of code, known as platform.js (soon to be known as webcomponents.js), is an extensive series of shims that iron out APIs and inconsistencies across evergreen browsers.

EVERGREENS

“Evergreen” is the term adopted for AAA browsers that keep themselves up to date without user interaction. This includes every version of Chrome, modern Firefox, modern Opera, and IE 10+.

This is not a term used to denote browsers that support any particular technology and, as such, can be misleading when used to describe support. Unless browsers start deviating dramatically from specifications, it can be thought of as a term that means “a browser that will probably support emerging technologies in a reasonable time frame.”

Figure 14-1 shows the Polymer group’s explanation of its component pieces. You can see the distinct layers that make up the Polymer framework and the foundational pieces that make up the platform.

The long-term goal of Polymer, with respect to its platform shims, is to eventually reduce and eliminate the need for them completely. In its heyday, jQuery (like many other libraries) was required to support very old browsers, due to the time between version upgrades. Over the years this added up to a substantial amount of code that has been and will be around for a long time. Polymer’s commitment to only supporting evergreen browsers ensures that the gradual removal of shim code is likely to be a reality, albeit at the substantial cost of limited browser support. Internet Explorer’s support starts at version 10, and Safari’s support doesn’t extend to versions before Safari 6. There are other community shims that provide limited support to earlier browsers, but that is beyond the scope of Polymer and this book.

Polymer's explanation of its internal layers

Figure 14-1. Polymer components (source: https://www.polymer-project.org)

POLYMER VERSIONS

This book was conceived during the Polymer 0.1 era (the “alpha” years), started taking form while Polymer 0.2 was in use, and matured to completion as Polymer evolved to version 0.8. Polymer 0.8 is still in preview on a branch on GitHub at the time of this book’s publication. The jump from 0.5 to 0.8 saw some major changes to the Polymer philosophy, but the majority of those choices don’t affect the common usage of Polymer. Polymer 0.8 focuses on performance and cutting out features that, ultimately, people weren’t using. The functionality still exists in add-on libraries but has been removed from the core Polymer builds.

Since the changes in 0.8 (at the time of publication) don’t substantially affect the ideas in this book, and given that the Polymer team is still making decisions as to what Polymer 0.8 (and Polymer 1.0) will look like, the book’s code is tailored around the 0.5 release.

Polymer’s syntax was inspired by the <element> element, which is currently in spec limbo. This element was conceived as a way to declaratively define elements within an HTML document. It was removed due to complexities in its implementation but the fact that it can live on spiritually within Polymer is a testament to this shift in how the Web is evolving. As developers, we are being provided with the tools necessary to build pieces of the Web at a very low level.

Polymer Elements

A basic Polymer element definition looks like this:

<polymer-element name="hello-world" noscript>

<template>

<h1>Hello World</h1>

</template>

</polymer-element>

The syntax should look reasonably intuitive, and if you’ve just read Part III, the simplicity is probably welcome. This small chunk of code defines a custom element called hello-world that, when instantiated, will clone the template’s content and append it to the element’s shadow root. Thenoscript attribute on polymer-element just tells Polymer that we’re not doing any scripting inside the element and that the element can be created automatically with the default options.

Now contrast this to the steps you needed to take with bare web component APIs in Part II, and you might get that “Ohhh, neat” feeling you had when you first started working with jQuery. There’s no doubt that the web component APIs can be a little cumbersome and unintuitive, especiallywhen combined with each other, so the benefit of this sugar is immediately apparent.

One of the substantial gains Polymer provides beyond the syntactic sugar is its two-way data binding integrated into templates and the JavaScript API. This allows you to declaratively define template replacements that are bound to attributes and other changing properties. For example, the following code:

<polymer-element name="x-greeting" attributes="target">

<template>

<h1>Hello {{target}}</h1>

</template>

<script>

Polymer('x-greeting', {

target: "World"

});

</script>

</polymer-element>

creates the x-greeting element, whose target attribute can be used to specify who should be greeted (defaulting to World):

<x-greeting target=Mom></x-greeting>

NOTE

Given the requirement that a custom element must have a hyphen in its name (see the sidebar “Naming Conventions”), a common practice is to namespace custom elements, as in polymer-tabs or myapp-button. In the case of reusable elements that don’t exist in a logical namespace, a growing standard is to use the x- or sometimes ui- prefix for the element names.

For some elements, such as this one, it may be more intuitive to make use of the content of a custom element instead of an attribute. This can easily be done with the <content> tag:

<polymer-element name="x-greeting" noscript>

<template>

<h1>Hello <content>World</content></h1>

</template>

</polymer-element>

Here we default our content to World again, but we can change who is greeted by specifying different content in the custom element instead of using an attribute:

<x-greeting>World!</x-greeting>

Notice we went back to using noscript in the preceding code sample; this isn’t required to use <content>, but it’s a good exercise to see just how complex we can make a custom element without requiring any scripting whatsoever. This is more important than it may seem—this is not an exercise for curiosity’s sake. When you’re playing with web components you are implicitly defining your environment as being close to or beyond the leading edge. This provides you with a wide array of CSS styles, animations, and techniques that most people probably aren’t familiar with, due to common browser compatibility requirements. It’s important to reset your baseline in order to make the most out of web components.

RESETTING YOUR BASELINE

It’s not only CSS that you may need to brush up on. Technologies like SPDY now become something you can reliably invest in, and SPDY itself is more than just a “faster Web.” SPDY dramatically reduces the cost of serving many small files from the same server. This will have substantial implications for how web sources are structured, maintained, and built in the future.

AMD or ES6 modules will not need to be concatenated together. HTML imports will not only be a viable solution, they will be the preferred abstraction layer, and spritesheets may be a thing of the past.

In this weird, alien future, even using a content delivery network may adversely affect perceived performance. Crazy, I know.

Adding Style

To start adding CSS rules to your elements, you simply add a <style> block to the <template>. We can use Polymer templates in CSS styles as well, allowing us to create dynamic styles with little extra effort.

Since we’re in control of our own elements now, let’s resurrect an old favorite, the <font> tag. Deprecation be damned!

<polymer-element name="x-font" attributes="color" noscript>

<template>

<style>

span {

color:{{color}};

}

</style>

<span><content></content></span>

</template>

</polymer-element>

<x-font color=red>Hello World!</x-font>

There’s an important subtlety in the preceding code; did you spot it? If you don’t specify a color attribute, what happens? It’s probably an easy guess that the color of the resulting element is the inherited text color of the context the element is used in, but why? We actually get a “broken” CSS rule specifying that the color is "null". This, conveniently, isn’t a useful color, so the CSS engine just ignores it, but it’s important to call out because web components should act like normal elements—and that means silent errors and obvious fallbacks.

There’s a second important thing to note about our element. What do you think would happen if you did this?

<x-font color="red;font-style:italic">Hello World!</x-font>

Congratulations if you guessed that you’d get a red, italic “Hello World!” This isn’t an artifact of web components—it’s due to Polymer itself—but regardless of what you use to make web components, you’re going to need to account for them being used in ways you don’t expect. Maybe now we can all sit back and start to appreciate the jobs spec writers have been doing for the past few decades.

External Resources

As your web components grow in size, you’ll need a way to better manage larger styles. Unfortunately, as we went over in Part III, you can’t easily add external stylesheets via <link> tags to a shadow DOM. Fortunately, though, many smart people have traversed this path before us and have simplified some common use cases like this one. A template like the following wouldn’t include any external styles when added to the shadow DOM manually:

<template>

<link rel="stylesheet" href="styles.css">

<span>Hello World</span>

</template>

span {

font-style:italic;

}

In Polymer, though, that template would work perfectly fine and the styles would be applied to the elements in the shadow root (and the shadow root alone). How does that work? Polymer simply finds the <link> elements, asynchronously grabs the referenced CSS via anXMLHttpRequest, deletes the original <link> element, and inlines the CSS into a <script> tag at the location of the original link element. Simple, right? The resulting element in the DOM would look very similar to this:

<template>

<style>

span {

font-style:italic;

}

</style>

<link rel="stylesheet" href="styles.css">

<span>Hello World</span>

</template>

Filtering Expressions

Polymer’s templating additions also provide the ability to filter expressions through simple JavaScript functions. This provides a layer of abstraction between any advanced logic and the template itself, somewhat similar to the features provided by a lot of limited-logic templating solutions available for JavaScript right now. Let’s add some classiness to our x-font tag by way of a type attribute where we can specify a predefined set of styles to apply:

<polymer-element name="x-font" attributes="type color" noscript>

<template>

<style>

span {

color:{{color}};

}

.shadow {

text-shadow: 2px 2px 2px grey;

}

</style>

<span class="{{ { shadow: type == 'fancy' } | tokenList }}">

<content></content>

</span>

</template>

</polymer-element>

The block class="{{ {shadow: type == 'fancy'} | tokenList }}" probably looks like it’s doing a lot more than it really is. To isolate the important parts, we can get rid of the attribute and wrapping quotes, class="", and the Polymer template delimiters, {{ and }}. We’re then left with { shadow: type == 'fancy' } | tokenList, which we can further separate into two parts. The first part is a plain old JavaScript object with one key, shadow, the value of which is the return value of the comparison type == 'fancy'. The second part, | tokenList, is Polymer syntax that pipes the lefthand side into the filter specified on the righthand side. tokenList is just a plain JavaScript function located on the PolymerExpressions.prototype:

PolymerExpressions.prototype = {

tokenList: function(value) {

var tokens = [];

for (var key in value) {

if (value[key])

tokens.push(key);

}

return tokens.join(' ');

},

[...]

}

Here we can see that the tokenList function takes in a JavaScript object and, for every key, tests the truthiness of the key’s value; it then adds the key name to an array if it es. That array’s values are then joined together, separated by a space (' '). We’re going through filters in depth here because they are very powerful and often overlooked, and when you see them in a context like this one they are regularly seen as more complicated than they really are. The filter in our Polymer template can be seen as being equivalent to the following code:

var originalValue = {

shadow: type == 'fancy' // 'type' is a defined attribute on our element

// e.g. <x-font type='fancy'></x-font>

};

var filteredValue = tokenList(originalValue);

// filteredValue === 'shadow'

Filters and data binding provide you with a substantial amount of control, without exposing much actual JavaScript. This is a great step toward creating maintainable applications and client code.

Template Syntax

We’ve already seen several examples of Polymer’s template syntax, and there’s a lot more going on with templates than we’ll deal with here. More advanced expressions make use of attributes on nested <template> elements that provide common template functions like block repetition and conditional blocks.

Data Binding

Polymer offers intuitive data binding for anyone who has used Handlebars-style templates in other frameworks/templating solutions (i.e., where you bind a property of the context to a portion of a template by surrounding the identifier with double curly braces):

<polymer-element name="x-simple">

<template>

{{value}}

</template>

<script>

Polymer('x-simple',{

ready : function () {

this.value = "Hello World!";

}

})

</script>

</polymer-element>

Block Repetition

This approach combines the double curly brace style with HTML attributes in order to specify the behavior of subtree nodes, very similar to AngularJS-style template loops:

<polymer-element name="x-repeat">

<template>

<ul>

<template repeat="{{ value in values }}">

<li>{{value}}</li>

</template>

</ul>

</template>

<script>

Polymer('x-repeat',{

ready : function () {

this.values = [1,2,3,4];

}

})

</script>

</polymer-element>

Bound Scopes

Bound scopes are the template equivalent of JavaScript’s with statement: they extend the scope chain of a subtree. They allow you to simplify references to extended properties without specifying redundant identifiers repeatedly:

<polymer-element name="x-bind">

<template>

<span>

{{outerObject.outerValue}}

</span>

<template bind="{{outerObject.innerObject}}">

<span>{{innerValue}}</span>

</template>

</template>

<script>

Polymer('x-bind',{

ready : function () {

this.outerObject = {

outerValue : "Hello",

innerObject : {

innerValue : "World"

}

};

}

})

</script>

</polymer-element>

Conditional Blocks

Polymer’s conditional blocks follow the same style and are immediately intuitive:

<polymer-element name="x-editable">

<template>

<span>{{value}}</span>

<template if="{{ !readonly }}">

<input value="{{value}}">

</template>

</template>

<script>

Polymer('x-editable',{

value : 'default',

readonly : true

})

</script>

</polymer-element>

Multiple Template Directives at Once

An often underused aspect of template directives is the ability to combine items like repeat and if. Rather than nesting templates with their own directives, you can combine them in one template for a terser syntax:

<polymer-element name="x-combo">

<template>

<h1>A list of values</h1>

<ul>

<template repeat="{{ value in values }}" if="{{ open }}">

<li>{{ value }}</li>

</template>

</ul>

</template>

<script>

Polymer('x-combo',{

values : [1,2,3,4],

open : true

})

</script>

</polymer-element>

Attributes and Properties—Your Element’s API

There is a lot to consider when determining how to construct and expose your custom element’s API. When should something be an attribute? When should it be a property? Should it be both? How should it be bound? Should it be read-only? What should happen if it changes after instantiation? Should your element emit events? If so, what events? Should you model your element’s API after an existing element or toss existing best practices aside?

There are other frameworks out there that do things strikingly similarly to what web components and Polymer provide, but web components exist on an isolated island far away from any application framework. The responsibility to create consistent, intuitive, and decidedly “HTML-like” APIs is important.

With Polymer you have the power to create a variety of API touchpoints that all have their own benefits and drawbacks.

Naked Attributes

As you’d probably expect, you can add any attribute you want to your custom element and access it via the standard getAttribute method that exists on instances of elements. You don’t need to explicitly code this support, but it isn’t bindable within templates, and Polymer doesn’t offer you much sugar on top of it. You can add an attribute to your element as follows:

<polymer-element name="x-example" noscript>

</polymer-element>

<x-example attr="my attribute"></x-example>

<script>

var myElement = document.querySelector('x-example');

myElement.getAttribute('attr') == "my attribute"; // true

</script>

Published Properties

Polymer’s “published properties” are probably what is going to get the most use in your custom elements. These properties are exposed versions of element attributes and are bindable within templates; when the attribute changes or the property changes, the templates will reflect this. Here’s how to add a published property to your custom element:

<polymer-element name="x-example" noscript>

<template>

{{myAttribute}}

</template>

</polymer-element>

<x-example myAttribute="hello world"></x-example>

<script>

var myElement = document.querySelector('x-example');

myElement.getAttribute('myAttribute'); // "hello world"

myElement.myAttribute; // "hello world"

myElement.setAttribute('myAttribute', 'til next time');

myElement.myAttribute = 'later!';

</script>

Published properties are popular because they offer a lot of functionality in a simple package, and also because they are intuitive to users and are discoverable via JavaScript introspection and browser devtools.

Instance Methods

All methods applied to the prototype of the element within the Polymer definition are exposed as instance methods of the element within JavaScript:

<polymer-element name="x-example">

<template>

{{myAttribute}}

</template>

<script>

Polymer('x-example', {

myMethod : function(value) {

this.myAttribute = value;

}

})

</script>

</polymer-element>

<x-example myAttribute="hello world"></x-example>

<script>

var myElement = document.querySelector('x-example');

myElement.myMethod('howdy world');

</script>

This can be useful, but side effects should be kept to a minimum. Always keep in mind that you are notified when attributes have changed, so it may make more sense at an API level to have certain functionality triggered upon attribute change instead of via an instance method.

Polymer’s JavaScript APIs

This chapter is nearly over, and we’re only now touching on Polymer’s JavaScript APIs. This is by design, because the beauty of web components, and the philosophy of Polymer, is to embrace HTML with all its modern power. HTML is no longer something you need to treat gently and twist and tweak to get where you want. You are defining the power of HTML. You’ll be doing this with JavaScript, of course, but HTML is now sharing the front seat again.

Life Cycle Methods

The life cycle methods you are used to with custom elements are supported and easy to use within Polymer. For convenience, though, they are shorter than their spec equivalents. Polymer also adds other methods that align with important events in the Polymer life cycle. Table 14-1 lists these methods, and their spec equivalents (if any).

Polymer

Spec equivalent

Called when

created

createdCallback

The element has been created.

ready

None

Polymer has completed its initialization of the element.

attached

attachedCallback

The element has been attached to the DOM.

domReady

None

The initial set of children is guaranteed to exist.

detached

detachedCallback

The element has been detached from the page.

attributeChanged

attributeChangedCallback

An attribute has been added, removed, or changed.

Table 14-1. Polymer life cycle methods

NOTE

Note and memorize the difference between created and ready. created simply marks that the element has been created and is an analog to createdCallback on naked custom elements; ready is called when Polymer has done its initialization work, injected the shadow DOM, set up listeners, and is largely ready to interact with. In a Polymer mindset, you’ll likely always want to deal with the ready callback.

Events

You can emit events easily from your web component yourself, but for consistency in creation and usage, Polymer provides the fire method that automates this for you. The method’s signature is intutive and expected, but it abstracts away common event boilerplate:

fire: function(type, detail, onNode, bubbles, cancelable) {

var node = onNode || this;

var detail = detail === null || detail === undefined ? {} : detail;

var event = new CustomEvent(type, {

bubbles: bubbles !== undefined ? bubbles : true,

cancelable: cancelable !== undefined ? cancelable : true,

detail: detail

});

node.dispatchEvent(event);

return event;

}

Usage is straightforward, and you’ll commonly only use the first one or two parameters (the event name and the data payload). The following example issues an open event with the target being the current context—which, in this example, would be the element itself:

this.fire('open', { target: this });

Managing Delayed Work

When you start encapsulating all work in components, you may find yourself commonly dealing with timeouts and the management thereof to ensure work is done only once and after a certain period of time. This could be for animations, notifications, event throttling… anything. It’s so common that Polymer provides a common API for it, .job:

this.job('openDialog', function () {

this.fire('open');

}, 100);

Summary

In this chapter we caught a glimpse of the future of the World Wide Web. It is important to note how the somewhat disparate technologies we looked at in Part III have been joined together by the community to offer a fairly pleasing API. This is a core philosophy touched on by the “Extensible Web Manifesto,” a series of principles that seek to influence how web technology is standardized in the future.

EXTENSIBLE WEB MANIFESTO

§ Focus on adding new low-level capabilities to the web platform that are secure and efficient.

§ Expose low-level capabilities that explain existing features, such as HTML and CSS, allowing authors to understand and replicate them.

§ Develop, describe, and test new high-level features in JavaScript, and allow web developers to iterate on them before they become standardized. This creates a virtuous cycle between standards and developers.

§ Prioritize efforts that follow these recommendations and deprioritize and refocus those which do not.

This chapter outlined a large slice of what Polymer can do, and after reading it you should be able to create your own highly functional web components with ease. In the coming chapters we’ll explore how to port our existing dialog to Polymer, test it, and build a distributable, installable package.