Web Content Optimization: Introduction

The tools for making content performant have improved considerably in the last few years. As designers we can now influence the size and the type of resources that the end users will see when they view our content.

In this series we’ll discuss strategies and tools to minimize the size of our web content: CSS, Javascript, HTML, images, video and audio, along with best practices in serving the content.

Good overview of the topic are:

A final note: This covers my experience and research. It is by no means an exhaustive guide. For more and deeper research into tooling and performance check Addy Osmani’s blog and Totally Tooling Tips on Youtube. Ilya Grigorik’s blog is a good source of information on the subject

Web Components Conclusion: Should we use web components now?

It depends on your target platforms. The only desktop browser to fully implement web components is Chrome (and by extension Opera) so the webcomponent.js polyfill must be used for any kind of meaningful support. It’s also important to note that the polyfills don’t work particularly well on mobile devices and I haven’t tested them in any e-book reader (where I assume they will not work.)

It depends on the the scope of your planned components. If your planning to progressively upgrade elements on your page then it makes sense to take the plunge and use them now. However, if you’re planning full blown applications based on components you may be better off waiting until the specifications finalilze and browsers add native support.

I had initial concerns about the technologies and their support but after the Polymer Summit the concerns were calmed and it became intriguing to play with Polymer again.

I have a few apps and elements that have been updated to 1.0

Web Components: Where to use web components?

I’m mostly familiar with using Web Components (specifically Polymer) as the front end for either JSON or Firebase backends. My research has turned to how do we make web components play nice with other frameworks, MVC and otherwise.

Sole Penadés talks about Web Components and also what to expect when using web components with other frameworks 🙂

We can take two approaches to Web Components: Whole cloth or progressive enhancement.

Whole cloth is building a custom element hierarchy with the associated elements needed to achieve your application goals. It may require multiple elements like the ones below where each element is defined and imported in a separate document with its own CSS and Javascript associated with each element.

<my-app>
  <my-app-header>
    <iron-icon></iron-icon>
    <my-title></my-title>
    <my-app-menu></my-app-menu>
  </my-app-header>

  <my-app-body>
    <content></content>
  </my-app-body>
</my-app>

A progressive enhancement approach is what Github did. Instead of going full bore with defining new components, they enhanced portions of their application application by extending what is already there as we discussed above. Taking the my-avatar element we created earlier we’ll revisit the code to make it work as a component.

var MyAvatarPrototype = Object.create(HTMLElement.prototype);
MyAvatarPrototype.createdCallback = function() {
  var username = this.getAttribute('username');
  var service = this.getAttribute('service');
  var url = 'http://avatars.io/' + service + '/' + username;
  var img = document.createElement( 'img' );
  img.setAttribute('src', url);
  this.appendChild(img);
};

document.registerElement('my-avatar', {
  prototype: MyAvatarPrototype
});

And then call it as we normally would:

<my-avatar username='myname' service='twitter'></my-avatar>

A third alternative when using Polymer (and only Polymer) is to use auto binding templates. With these templates we can use Polymer tools outside polymer custom elements. You create the auto-binding template by using a template with the is=dom-template attribute.

Once you create the template you can use all Polymer techniques and tricks inside your main document without having to create a custom element for it as show in the example below.

<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <script src="components/webcomponentsjs/webcomponents-lite.js"></script>
  <link rel="import" href="components/polymer/polymer.html">
  <link rel="import" href="components/iron-ajax/iron-ajax.html">

</head>
<body>

  <!-- Wrap elements with auto-binding template to -->
  <!-- allow use of Polymer bindings in main document -->
  <template id="t" is="dom-bind">

    <iron-ajax url="http://..." last-response="{{data}}" auto></iron-ajax>

    <template is="dom-repeat" items="{{data}}">
        <div><span>{{item.first}}</span> <span>{{item.last}}</span></div>
    </template>

  </template>

</body>
<script>
  var t = document.querySelector('#t');

  // The dom-change event signifies when the template has stamped its DOM.
  t.addEventListener('dom-change', function() {
    // auto-binding template is ready.
  });
</script>
</html>

So which one should we use?

As with everything on the web the answer is, it depends.

Type extension elements have the draw that, if they don’t work (either because Javascript is disabled, polyfills are not available or custom elements are not supported), content will still be displayed to the user.

For larger projects where type extension elements are not enough, full blown custom elemets are necessary with all the additional weight and performance penalties they carry if you’re not careful when implementing them.

Paul Lewis’ blog post Polymer for the performance obsessed outlines some of the requirements to improve a Polymer application’s performance. It would be interesting to see how much of the post (given where Paul works) is applicable to vanilla components and x-tag built elements

In the end the type of component you use depends on your project and your needs.

Web Components: How to use web components?

When I first started working with Web Components I looked at the biggest way to use them. In creating my own components I own all of the component, the scripts, the encapsulated CSS and the responsibility of making sure that they worked and worked well with other components and other elements in the page.

In learning how to use Web Components we’ll look at both the big and the small picture: Creating full custom components and createying type extension custom elements.

Custom Components

The ability to create fully customized and reusable elements is what attracted me to Polymer and the concept of web components. The elements we create can be as simple or as complex as we need them to be…. We can also add other components to enhance the functionality of our components.
The example below (taken from the Polymer Project’s home page) shows what a custom element built with Polymer 1.0 looks like.

<dom-module id="contact-card">       
  <link rel="import" type="css" href="contact-card.css">
  <template>         
    <content></content>      
    <iron-icon icon="star" hidden$="{{!starred}}"></iron-icon>       
  </template>        
  <script>       
  Polymer({      
    is: 'contact-card',      
    properties: {
      starred: Boolean       
    }        
 });         
 </script>       
</dom-module>        

And how we use the component on our document.

<contact-card starred>       
 <img src="profile.jpg" alt="Eric's photo">      
 <span>Eric Bidelman</span>      
</contact-card>      

Extending existing elements (Type Extension Custom Elements)

There are times when a custom element is too much. We might need a smaller chunk of functionality or we may need to enhance an already-existing element instead of creating a whole new element. You can create a custom element that extends a native HTML element and its features. This is called a Type Extension Custom Element. To use the element, use the original tag and specify the custom tag name using the is attribute.

<input is="x-component"></div>       

To define a type extension:

  • Create the base prototype object using the prototype of the extended element, instead of HTMLElement.
  • Add an extends key in the second argument to document.registerElement(), specifying the tag name of the extended element.

Following is an example code when extending the input element:

var XComponent = document.registerElement('x-component', {       
 extends: 'input',       
 prototype: Object.create(HTMLInputElement.prototype)        
});      

Notice that it extends: ‘input’ and its prototype is based on HTMLInputElement instead of HTMLElement. Now you can use &lt;input is="x-component"> inside your document. By doing so, you can have extended APIs on top of basic input element’s features.

Github’s example

Github Relative Time on display
Github Relative Time on display

GitHub has a component that displays date and time as shown above. Notice they are not absolute dates/times but relative to the browser’s current time. GitHub uses a Type Extension Custom Element accomplish this. The HTML code looks like this:

HTML source for time type extension custom element
HTML source for time type extension custom element

There some things to notice:

  • time tag is used as a base element
  • datetime attribute indicates an absolute date/time
  • is='time-ago' specifies a type extension
  • The tag’s content indicates a relative date/time
  • This is done on the fly as a type extension.

Even if web components are not supported or Javascript is disabled we will still be able to see when the file was last changed. If you disable Javascript from your browser’s

For more details about time-elements, check webcomponents.org’s How GitHub is using Web Components in production.

Web Components: How to build web components?

To illustrate how to create web components we’ll use the same element for all three methods. The end result will look like the example below:

  <my-avatar service="twitter" username="caraya"></my-avatar>

We’ll look at the differences between a script based approach used in x-tags and a declarative take using Polymer.

Using vanilla Javascript

var MyAvatarPrototype = Object.create(HTMLElement.prototype);
MyAvatarPrototype.createdCallback = function() {
  var username = this.getAttribute('username');
  var service = this.getAttribute('service');
  var url = 'http://avatars.io/' + service + '/' + username;
  var img = document.createElement( 'img' );
  img.setAttribute('src', url);
  this.appendChild(img);
};
document.registerElement('my-avatar', {
  prototype: MyAvatarPrototype
});

or with tags and scripts:

<template id="myAvatar">
    <img src=" ">
</template>
<script>
function addAvatar(service, username) {
  var t = document.querySelector("#myAvatar");
  // This will stamp out the element into our DOM
  var comment = t.content.cloneNode(true);
  // Build the URL
  var serviceURL = '"http://avatar.io/" + service + "/" + username'
  // Populate content.
  comment.querySelector('img').src = serviceUrl;
  comment.querySelector('.comment-text').textContent = text;
  document.body.appendChild(comment);
}
</script>

X-Tags

X-tags implementation of Web Components lacks the capability to import elements; their justification is that they are waiting to see if Javascript modules will work to avoid standards and technologies that duplicate each other.

//<script type='text/javascript' src='scripts/x-tag-core.min.js'></script>    
//<script type='text/javascript'>
xtag.register('my-avatar', {
  // The 'content' property generates templated content
  // and automatically adds it to each new element you create
  content: '<div class="avatar"></div><img class="avatar" />',
    lifecycle: {
      created: function() { 
        this.xtag.img = this.lastElementChild;
        // In the next release we're adding default attribute values
        // so you won't have to do these if () checks
        if (!this.service) this.service = 'twitter';
        if (!this.username) this.username = 'name';
      }
  },
  accessors: {
    service: {
      attribute: {},
      set: function(val){
        this.xtag.img.src = 'http://avatars.io/' + val + '/' + this.username;
      }
    },
    username: {
      attribute: {},
      set: function(val){
        this.xtag.img.src = 'http://avatars.io/' + this.service + '/' + val;
      }
    }
  }
});

Polymer

Polymer has gotten more complicated since I last played with it in the 0.5 but it’s still fairly easy to implement and modify. We’ll explore some of the changes from 0.5 to 1.0 and some of the syntactic sugar that makes working with web components fun.

The example below is the Polymer version of our my-avatar tag.

<dom-module id="my-avatar">
  <style>
    .avatar {
      border: 0;
    }
  </style>
  <template>
    <img class='avatar' src='http://avatars.io/{{service}}/{{username}}'>
  </template>
</dom-module>
<script>
  'use strict';
  Polymer({
    is: 'my-avatar',
    properties: {
      service: {
        type: String,
        value: '',
        notify: true
      },
      value: {
        type: String,
        value: '',
        notify: true
      }
    }
  });
</script>

There is a very interesting presentation from ForwardsJS 3 that covers why you should be using web components now.