Much more than just JavaScript

Modern client-side component development

WilMooreIII
moore-components-teaser1

Wil Moore III explores the best options for packaging and sharing reusable web components.

One of the hotter debates in web development as of late revolves around the notion of packaging and sharing reusable components. You may have noticed that many segments of the web development community have stepped up in order to attempt to solve this problem.

Interestingly, if you hang around in multiple parts of the community, you’ve probably noticed that there are many solutions being proposed and developed. Unfortunately, these solutions have overlapping and incompatible feature sets, leaving us with a manual integration headache. Don’t get me wrong, there are talented developers working on this problem and I commend their efforts; however, virtually all of them miss a critical detail…

A web “component” is much more than just JavaScript

I’m sure there are lots of stories floating around as to which JavaScript package management solution is the best. Perhaps you’ve heard of and even use Twitter’s Bower or James Burke’s Volo or Caolan McMahon’s Jam. As with most of Twitter’s open source projects, Bower happens to be the most popular of the lot. This is no surprise given the impressive list of people behind the project. For example, @fat (Jacob Thornton), @addyosmani (Addy Osmani), and @paulirish (Paul Irish).

About two years ago, I started looking for the “best client-side script loader”. I even entertained the thought, “perhaps I’ll just write one myself” (famous last words). About a year ago, I started digging further into NodeJS…that is when it all came together and I realized that I should be looking for a client-side “module loader”. You know, npm for the browser. Little did I know that there was already Ender and Browserify in existence. Oh wait, what about that Asset Pipeline thing or Assetic? Yes, I frantically tried most of these solutions; and I even wrote about my short list of contenders.

To a large degree, I continued to “miss the point”. Fortunately, a very talented lad by the name of TJ Holowaychuk set me straight. He reminded me “A [web] ‘component’ is much more than just JavaScript… A ‘component’ can be JavaScript, CSS, images, fonts, and more.” The moral of the story is, “Create components, not [only] JavaScript packages”.

Write modular CommonJS components

If you are a PHP developer, you’ve probably heard of and have likely used the esteemed Composer dependency management tool. The idea is that you “declare” your dependencies and it will handle sorting out the gory details. This is very similar to node’s npm [14] and ruby’s bundler. If the concept behind these tools gives your developer senses the warm and fuzzies, you may be able to appreciate the idea behind CommonJS and writing modular components that do one thing well.

This is precisely the philosophy of the new tool aptly named Component. Component is a client-side package manager and module loader. It also ships with a builder tool, which allows you to aggregate the components used in your application into a single package for testing and/or deployment. It can be used to generate new components.

The included “component-builder” is written in JavaScript on top of NodeJS; however, the philosophy behind the project is that other communities may want to write the builder portion in the language of their choice since not everyone uses node. The default “component-builder” is simply one implementation of the “component” specification.

I thought AMD “won” the module loader race already? This sounds nothing like AMD…what gives?

Hold on a second…let’s makes a few things clear before we move on. First, we should establish that the “component” loader is not an AMD module loader. An AMD module loader (i.e. RequireJS, curl.js) loads a module when it is used (sort of like PHP’s Auto Loader). AMD loaders load multiple modules at once asynchronously. In theory, this is very convenient; however, the downsides quickly start to outweigh the benefits once a project moves past trivial. Honestly, if your application is indeed trivial, you are better off punting on the loading debate entirely. At that point, sprinkling script tags and jQuery snippets around your pages is probably “good enough”. I personally don’t like to develop software this way, but hey, who am I to judge you?

“Component” on the other hand, loads modules via a blazingly fast local map. A component module is loaded when the NodeJS/CommonJS style “require” function is applied using a canonical string identifier or an alias (i.e. model, model-timestamps) as depicted below.

// Module dependencies.
var model      = require('model'),
    timestamps = require('model-timestamps');

// Item model.
module.exports = model('Item')
  .attr('id')
  .attr('title')
  .attr('complete')
      .use(timestamps)

If you are building non-trivial client-side applications, the ability to work in multiple domain-specific files (modules) is well worth the investment of executing a build step (BTW, I would recommend using the watch(1) command to automate this). Declaring the modules you are using at the top of your module makes for incredibly transparent source code. This increases readability, testability, and maintainability.

On AMD, compatibility and other module formats

There are many module formats in play today (also referred to as a transport format); however, “component” uses the CommonJS module format, which is not directly compatible with the following:

That being said, the CommonJS module format is well known to be an excellent source module format from which other module formats can be generated. In fact, this “translation” is actually supported by the included “component-build” tool. A “component” is built as an AMD module so you do not have to wrap your modules in a “wrapper” function (which you are forced into with the AMD and YUI formats). With “component” when you are ready to share your module, you can optionally build a “standalone” version that is compatible with AMD (and thus Dojo). For legacy’s sake, a “window” global is also exported.

This “standalone” component or application can be loaded via YUI’s module loader as well or used with manually placed <script> tags (that sounds so barbaric at this point). If you had previously assumed that AMD “won”, please be aware that this is not necessarily the case. It is documented that many very experienced JavaScript developers do not see AMD as the best solution and certainly not a panacea.

Nevertheless, AMD is quite popular; however, that is only because for a long time, there were no compelling alternatives. This is no longer the case. If you have ever developed a non-trivial application on top of an AMD module loader, you may have noticed that it quickly becomes edge-case city. The process is error prone and is ultimately a bad idea given there is a good alternative.

Small, focused modules are better for building applications than monolithic frameworks

The jQuery library takes a fair amount of heat for being monolithic and non-cohesive. You may be wondering if jQuery deserves all of this negative attention. jQuery was originally developed when client-side package/dependency management wasn’t a reality. At the time, a multiple file library just didn’t make sense. jQuery made it easy to drop the library on to an HTML page, select a DOM element, and attach an event handler in an elegant way. jQuery has done well as a DOM abstraction library with a cute API. It protects you from certain cross-browser edge cases and API cruft. On the other hand, that cute DOM library actually sneaks quite a list of extra concerns into our codebases, all attached to the same global $ object (you can optionally give back the $ and make jQuery non-global, but this is not the default).

The following list of things jQuery does other than DOM is quite alarming if you consider the The Single Responsibility Principle to be important:

  • DOM element selection, traversal, creation, and mutation (let’s pretend this is just one thing)
  • Events
  • XMLHttpRequest (AJAX)
  • Browser Sniffing
  • Array / Collection Utilities
  • Object manipulation and iteration (i.e. each, extend)
  • Object type inspection
  • JSON functions
  • XML functions
  • String functions
  • Time functions

I count at least 11 different concerns. This problem is not isolated to jQuery alone. At the time of this writing, the Underscore.js, MooTools, and Prototype libraries all have huge non-cohesive APIs (to be fair, the new MooTools is reported to be cleaning this up). I’ll say it again, this made sense at the time these libraries were introduced; however, the batteries included approach to software development seems nice until you start building non-trivial software.

You may be wondering at this point why this even matters. Perhaps you only use the AJAX portion of jQuery. The fact that there are 100+ methods that you are not using isn’t hurting you…or is it? It is tempting to believe that there is no harm done; however, consider the fact that those unused functions are completely unrelated to the task at hand. The only way for a future developer (in many cases this is yourself) to understand why jQuery or underscore was included is to read the source code…all of it. With a module system in place and small, focused components, this maintenance and complexity problem goes away.

Now, I am not going to get ahead of myself and assume that you’ve been completely persuaded; however, I will assume that you’d at least entertain the question “How do I do X if I give up jQuery (or any other monolithic framework you are using)”? In other words, armed with only the component package manager, the CommonJS module format, and the component.io registry, how might I build a date picker component?

With component(1) installed, creating a re-usable date picker component boils down to the following steps (assuming you’ve already installed Component):

Bootstrapping a new datepicker component

$ npm install -g component
$ component create datepicker

repo (username/project): component/datepicker
description: Datepicker ui component built on component/calendar
does this component have js? y  
does this component have css? y
does this component have html? 

      create : datepicker
      create : datepicker/index.js
      create : datepicker/datepicker.css
      create : datepicker/Makefile
      create : datepicker/Readme.md
      create : datepicker/History.md
      create : datepicker/.gitignore
      create : datepicker/component.json

Adding dependencies

$ cd datepicker
$ component install component/calendar

install : component/calendar@master
    dep : component/range@master
install : component/range@master
    dep : component/jquery@master
install : component/jquery@master
    dep : component/emitter@master
install : component/emitter@master
    sep : component/in-groups-of@master
install : component/in-groups-of@master
  fetch : component/calendar:index.js
  fetch : component/calendar:lib/utils.js
  fetch : component/calendar:lib/template.js
  fetch : component/calendar:lib/calendar.js
  fetch : component/calendar:lib/days.js
  fetch : component/calendar:lib/calendar.css
  fetch : component/range:index.js
  fetch : component/jquery:index.js
  fetch : component/in-groups-of:index.js
  fetch : component/emitter:index.js
complete : component/range
complete : component/in-groups-of
complete : component/emitter
complete : component/jquery
complete : component/calendar

Component Integration

We will create an example.html file that will allow us to integrate and interact with our date picker component.

<!DOCTYPE html>
<html>
  <head>
    <title>Datepicker</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <link rel="stylesheet" href="build/build.css">
    <script src="build/build.js"></script>
  </head>
  <body>
    <input type="text" name="date" placeholder="Choose a date">

    <script>
       var picker = require('datepicker');
       var el = document.querySelector('[name=date]');
       picker(el);
    </script>
  </body>
</html>

We will also install a few more “glue” components:

% component install component/{popover,aurora,event}

Next, we will create an index.js file. This is the demo’s entry point.

var Calendar = require('calendar')
  , Popover = require('popover')
  , event = require('event')

module.exports = Datepicker;

function Datepicker(el) {
  if (!(this instanceof Datepicker)) return new Datepicker(el);
  this.el = el;
  this.cal = new Calendar;
  this.cal.el.addClass('datepicker-calendar');
  event.bind(el, 'click', this.onclick.bind(this));
}

Datepicker.prototype.onclick = function(e){
  this.cal.on('change', this.onchange.bind(this));
  this.popover = new Popover(this.cal.el);
  this.popover.classname = 'datepicker-popover popover';
  this.popover.show(this.el);
};

Datepicker.prototype.onchange = function(date){
  el.value = date.getFullYear()
    + '/'
    + date.getMonth()
    + '/'
    + date.getDate();

  this.popover.hide();
};

Styling

We will create a datepicker.css file that will contain a few overrides so the popover border is not in the way.

.datepicker-calendar {
  font: 10px "Helvetica Neue", Helvetica, Arial, sans-serif;
}
.datepicker-popover .tip-arrow {
  top: auto;
}
.datepicker-popover .tip-inner {
  border: none;
}

Building our component

The only thing left to do is build the component.

% component build

There are a few more minor details that go into polishing off your new component but the above steps will get you a working date picker. For the original Date Picker tutorial check out this article by the “component” author.

My precious plugins

The jQuery plugin ecosystem is arguably a major part of what makes jQuery so popular. There is a plugin for pretty much anything you can think of. Unfortunately, this is part of the fragmentation problem. jQuery isn’t the only library around. There is YUI, Dojo, ExtJS, and many others. With a few minor exceptions, none of these project’s plugins or modules are portable. For example, one can’t simply take a YUI module and plug it into ExtJS. Definitely applaud YUI and Dojo for having real module systems and build tools; however, everyone does it their own way.

With “component” hopefully we will get to a point where we don’t need to worry about compatibility. With enough adoption, developers will be incentivized to build components from plan JavaScript, CSS, HTML, and more. While still very new, the “component” ecosystem is coming along nicely. There are already close to 700 components registered at the component.io registry. For example, suppose you are building an Evernote-like web application and you would like to give your users the ability to tag their notes. This is generally referred to as a “tag-input” component:

% component install component/pillbox

Suppose you miss those CSS selectors, which are extremely popular with the jQuery crowd… in that case, you may like:

% component install component/zest

You may have gotten the impression that components need to include JavaScript. That certainly is not the message I want you to receive. For example, a component could indeed be just CSS (i.e. Twitter Bootstrap).

That being said, there is nothing wrong with a component that has no CSS but is a JavaScript-only utility that works in NodeJS and the browser. There is even a DOM manipulation and traversal component available that will help you ease away from jQuery.

Build awesome things

I hope that you are at least intrigued enough to want to find out more. If so, you’ll definitely want to start by reading the original component announcement then watch the “Web Component Introduction” screencast and then read the best practices wiki. Get inspired to learn JavaScript more intimately by watching Rebecca Murphey’s excellent JSCONF talk The jQuery Divide.

Perhaps you are on the fence, still sprinkling jQuery document.ready calls about your pages and using pseudo-namespaces (i.e. deeply nested but still global objects) for “organization”. If this is the case, you might want to give component a try. If you aren’t sure what to build, there is a wealth of inspiration out there. For example, people are building complex content editors, file management tools, mobile apps, music, games, and even apps that run on your television. Enjoy building awesome things with component.

Wil Moore III (wil.moore@wilmoore.com) is a full-stack software craftsman with a passion for well-crafted software with an aesthetic API. Wil is a Zend Certified Engineer with PHP 5.3+ and is completely in awe of and contributes to many open-source projects. He is not shy about advocating for a development culture of devops, multi-paradigm programming, test-driven development, peer review, and mentorship. Wil primarily develops software in JavaScript (Browser and NodeJS), Ruby, and PHP. Follow Wil on Github or Twitter @wilmoore.

Author
Comments
comments powered by Disqus