molily Navigation

Standards-compliant and Sustainable JavaScript Libraries

[DE] Deutsche Version lesen

Currently, the company I work for is developing several JavaScript web applications using jQuery, Backbone and Underscore. We’re writing these apps mostly in CoffeeScript, a language which compiles to plain JavaScript.

In this blog post, I will first discuss this software toolchain and its pros and cons. I will then explain why these libraries conform to standards only to a limited extend. I will end up advocating future-proof libraries that promote standards and best practices.

jQuery

jQuery is the de facto standard for DOM scripting, animations and HTTP communication (“Ajax”). jQuery allows you to solve common DOM tasks using a short and meaningful syntax.

From the very start, jQuery served merely as a single-purpose helper library. Apart from the “DOM element wrapper” pattern, it did not offer patterns for organizing your application at the top level. jQuery does not force you to adhere a specific programming style, its purpose rather is to simplify DOM node list operations. Other JavaScript application framework like YUI, Dojo and Ext.JS always had a much broader scope.

As a market leader, jQuery has left competitors like Prototype and Mootools far behind. Although the library only covers the core of client-side scripting, it’s quite a huge piece of code. Many of its features aren’t used by simple DOM scripting. Especially advocates of micro-frameworks criticize jQuery as monolithic. jQuery’s defenders argue that jQuery is superior with regard to test-driven development and browser compatibility.

Underscore

Underscore is a small self-contained library which is often used in conjunction with jQuery. It offers several helpers that jQuey leaves out and addresses JavaScript programming at its core. The Underscore methods enable convenient handling of objects/hashes, lists and functions like Python, Ruby and functional languages do. From my experience, these features are essential for advanced JavaScript development.

Prototype and Mootools offer quite similar helper methods, but they augment JavaScript’s core prototypes. On the contrary, Underscore follows jQuery’s approach by encapsulating all methods in a single namespace which is called _ or underscore. That is, you have to write _.method(…) or _(…).method(…) to use Underscore’s functionality.

Backbone

Backbone seeks to be a simple, light-weight framework for client-side web applications inspired by the Model-View-Controller pattern. It consists of models, model collections, views and routers (formerly known as controllers). Backbone is maintained by the same author as Underscore, therefore it relies on Underscore. The threesome jQuery, Underscore and Backbone is a common team.

Most of Backbone’s ideas are well-proven and tested in software development, for example in server-side frameworks like Ruby on Rails. While Ruby on Rails relies heavily on conventions, which is considered as one of its strengths, Backbone still lacks best practices and a clear direction. In my opinion, Backbone’s MVC concept is vague and not yet mature, as the lack of controllers and the recent renaming of controllers to routers demonstrate. The examples like the famous Todo application are rather misleading when it comes to large-scale applications.

For this reason, the Backbone applications we have developed differ greatly in their architecture. We had to establish key conventions like Separation of Concerns over and over again. In order to avoid repetition, you have to create reusable base modules yourself. We ended up with a sizeable class hierarchy with our own view and model flavours as well as self-made controllers. I will publish these base classes on Github sometime in the future.

CoffeeScript

CoffeeScript is an alternative syntax for JavaScript which eases frequent tasks. The CoffeeScript notation avoids common pitfalls, offers plenty of syntactic sugar and uses significant whitespace in order to be more readable and compact. Despite its readability, the compiled JavaScript code provides an optimized performance. Essential features of advanced JavaScript programming like function binding are integrated right into CoffeeScript’s vocabulary. In addition, CoffeeScript offers the declaration of “classes”. These do not try to apply foreign concepts to JavaScript, they are rather a simplified application of core JavaScript features: constructors, prototypes and prototypal delegation (“inheritance”).

We experienced CoffeeScript to speed up our JavaScript development. However, the flexible syntax turns out to be quite fragile and therefore error-prone. You have to read the compiled JavaScript code frequently to check if the generated code does what you have intended. CoffeeScript is opinionated by design: It encourages a specific coding style based on Douglas Crockford’s JavaScript: The Good Parts. Some problems are easier to solve using CoffeeScript syntax, others are harder to express in CoffeeScript. Even CoffeeScript supporters argue that you should master JavaScript before tasting CoffeeScript’s strong brew.

CoffeeScript + jQuery + Underscore = Chaos

The objective of this blog post is not only to introduce the components of our software toolchain. The point is that I’m quite unhappy with this toolchain because their components do not adhere to a common concept. They do not gear into each other, but duplicate functionality. Some examples:

Iterate over an array, call a method for each element passing its index
ECMAScript 3
for (var index = 0, l = array.length, item; index < l; index++) {
   item = array[index];
   item.foo(index)
}
CoffeeScript
item.foo(index) for item, index in array

which compiles to:

var index, item, _len;
for (index = 0, _len = array.length; index < _len; index++) {
  item = array[index];
  item.foo(index);
}
Underscore in CoffeeScript
_.each array, (item, index) -> item.foo(index)
jQuery in CoffeeScript
$.each array, (index, item) -> item.foo(index)
ECMAScript 5 in CoffeeScript
array.forEach (item, index) -> item.foo(index)
Binding a method to an instance object during event handling
ECMAScript 3 (the simplest way how to create a closure)
var instance = this;
$('#elem').click(function () {
  instance.method();
})
CoffeeScript
$('#elem').click => @method()

which compiles to:

var __bind = function(fn, me){ return function(){
  return fn.apply(me, arguments); }; };
$('#elem').click(__bind(function() {
  return this.method();
}, this));
Underscore in CoffeeScript
$('#elem').click _.bind(@method, @)
jQuery in CoffeeScript
$('#elem').click $.proxy(@method, @)
ECMAScript 5 in CoffeeScript
$('#elem').click @method.bind(@)

When using CoffeeScript, jQuery and Underscore simultaneously, there aren’t any standard solutions for common tasks because all libraries are self-contained. Instead of one preferable standard approach, there are several equivalents you may choose from. In short, this toolchain lacks overall coherence which brings the conflicting approaches together.

Which one of the solutions described above should we prefer? I would vote for CoffeeScript since this is the language you’re writing in the first place. However, binding functions with CoffeeScript creates a redundant helper method which does not delegate to the native ECMAScript 5 bind method yet.

Prolegomena to Any Future JavaScript (cf.)

Back in spring 2010 (German blog post) I asked: When can we expect groundbreaking framework architectures? Unfortunately, nothing has happened since. A revolutionary approach which brings a robust and extensible foundation has not been invented yet. In fact, solutions for special problems like asynchronous script loading, modularisation and polyfills for HTML5 APIs have emerged. Admittedly, this valuable work moves the JavaScript development forward.

In contrast to Prototype and Mootools, the trio jQuery, Underscore and CoffeeScript leave the built-in prototypes untouched. There are plenty good reasons to do so, especially to refrain from extending the DOM element prototypes. But in the long term, essential functionality should not be encapsulated in separate libraries over and over again. The JavaScript programming community should come to an agreement and find common answers to key questions. This would simplify and professionalize JavaScript development.

So, which approach will persist and may outlive the libraries which are fashionable right now?

The ECMAScript standard will remain

ECMAScript 5 is the current web standard behind JavaScript. It was approved one and a half years ago and its browser support increases continuously.

The new methods of Object and Array as well as property descriptors, getters/setters and the Strict Mode form a superb foundation for high-level JavaScript programming. Many functions of Underscore are standardized by ECMAScript 5. Underscore just delegates to them given the browser already supports them.

This is why I ask for ECMAScript 5 to be the standard library of all future JavaScripts. We should follow the standard in the first place and reimplement standard behavior for older browsers. This can be achieved by augmenting the core prototypes. There is nothing dangerous with it as long as it is used to implement standardized methods. Future-proof libraries should not reinvent the wheel again and again but use ECMAScript 5 syntax consistently to create APIs which fit into the ECMAScript environment.

Several polyfills for ECMAScript 5 features already exist:

There aren’t any risks when using those ES5 features which can be polyfilled for older browsers.

For the time being, CoffeeScript will stay and influence ECMAScript

In many aspects, CoffeeScript is quite conservative and allows you to write plain JavaScript with a pleasant syntax. CoffeeScript pseudo-classes are surprisingly simple and fast compared to those in Prototype, Mootools and other “class” libraries. The inventor of JavaScript proposed to the ECMAScript working group to incorporate ideas from CoffeeScript into the next ECMAScript standard. Jeremy Askenas, the influential maintainer of CoffeeScript, Underscore und Backbone, even gave a talk together with Brendan Eich.

Nevertheless, we should not forget that CoffeeScript is not the ultimate answer to all JavaScript problems, which can mostly be explained by misconceptions of JavaScript itself. CoffeeScript as a language inherits many strengths and weaknesses from JavaScript and introduces many advantages but also pitfalls.

Of course, there still remain some conflicts between pure ECMAScript and CoffeeScript’s syntactic sugar. When iterating an array, you may chose between the standard ECMAScript 5 forEach() approach or the CoffeeScript for item, index in array syntax which translates to a plain, old-school for loop. This decision is left to the programmer, but the choice should follow well-defined conventions.