2013-03-25

We at SUSE Studio strive to give our users the best experience we can, using modern techniques and technologies.  To that end, I recently spent some time evaluating client-side Javascript MV* frameworks, trying to find a good fit for both how Studio works, and how we develop it.  After a little discussion, we decided we shouldn't be the only ones to benefit from the research, so here's the findings - I hope you find it useful when you have to make the same decision for your web application!

SUSE Studio has evolved a number of methods for performing dynamic client updates; in some places, we use Rails 2.0-style RJS - sending rendered partials along with JS code to be executed by the client. In other places, we're sending JSON data, and handling it via custom-written interactions. Most recently, we've experimented with client-side rendering of Mustache templates. None of these approaches is particularly robust or maintainable, and as a result we've ended up with a huge amount of fragile client-side code.

Luckily, we're not the only developers in this situation - every major web application faces the same challenges. As a result, a number of client-side frameworks have developed, applying common design patterns to build up a logical, maintainable framework for managing client-state and server interactions for web development. The problem is that everyone seems to have a solution, and that means navigating a number of projects to find a good fit.

Scoping

TodoMVC has evolved into a generic sample; implementing the same application (a todo list) in each framework. As an open project ( https://github.com/addyosmani/todomvc ), framework developers are free to add their own impelemntations, ensuring that the frameworks are represented by someone knowledgable of the framework. As a side-effect though, TodoMVC demonstrates how we’re spoiled for choice: 4256 different implementations are currently included.

Additionally, the Throne of JS conference in July 2012 gathered top developers representing a subset of popular frameworks. Videos of many presentations, as well as discussions among the developers, are available on InfoQ.

Based on the quality of coverage via those two primary sources, as well as critical mass of discussion, I narrowed the field to 13 implementations:

agility.js ( https://github.com/arturadib/agility )

AngularJS ( https://github.com/angular/angular.js )

Backbone.js ( http://github.com/documentcloud/backbone )

batman.js ( http://github.com/Shopify/batman )

canJS ( http://github.com/bitovi/canjs )

closure ( http://code.google.com/p/closure-library/source/browse/#git/closure/goog )

dojox/mvc (Dojo) ( http://svn.dojotoolkit.org/src/ )

ember.js ( https://github.com/emberjs/ember.js )

JavascriptMVC ( https://github.com/bitovi/javascriptmvc )

knockback.js ( https://github.com/kmalakoff/knockback )

Knockout ( https://github.com/SteveSanderson/knockout )

Spine ( https://github.com/spine/spine )

YUI/app (YUI) ( https://github.com/yui )

Incompatibility

While each framework has its own merits, and none is without some issue, a number of them were simply incompatible with our project, and were eliminated after a cursory analysis. In order to be considered compatible, a project needed to have a license that is compatible with both our SaaS offering, and our more traditional product. The project must at least tolerate, if not support well our dependency stack; specifically jQuery, server-side HAML or Mustache views, SASS, server-side Coffeescript, and of course Ruby on Rails. From a usability standpoint, a compatible framework is expected to have comprehensive API documentation, some guides and examples, be developed in an open manner where we can effortlessly contribute back, have some example of large public deployments, and be released regularly by and active development community. Beyond that, Studio has a particular style, and we attempt to adhere to a set of good practices; violation of standards or a failure to conform to our existing separation of responsibilities contributes to incompatibility as well.

agility.js

Agility centers around the unique concept of using a single factory to define a container for model, view, and controller objects. In order to accomplish this, all view elements are inlined strings; style is expected to be encapsulated within the view, and the view initialised as a single string of inline HTML, JS, and CSS. Although external view code can be implemented, this is outside the preferred behaviour for the framework.

AngularJS

Angular is a Google-sponsored project, although there is little evidence of its use: Closure, in contrast claims to be used in Google Search, Gmail, Maps, Docs, Sites, Blogger, etc., but this alone is not a reason to abandon the framework. The use of suggestion to use invalid HTML, on the other hand, is. Angular defines its own set of attributes and markup, which are processed by its JS library to provide browser-specific behavior. Fundamentally, though, AngularJS requires suggests you to write invalid HTML; although you can use HTML5 data- attributes instead of the custom attributes, using valid tags requires abandoning some of Angular's functionality.

backbone.js

Backbone, despite its apparent popularity, feels incomplete. Backbone, despite its description as an MVC framework, is generally viewed as muddying the responsibilities of the view and controller, and providing excessive leeway in template rendering and event handlers. This leads to a situation where another large, competing code ecosystem has developed, around building ‘backbone stacks’: collections of cooperating plugins that eventually build a complete MVC-ish framework. Although there is obvious merit in separating the responibilites and creating focus, the consequence is a lack of cohesive code. Being that we want a single solution we can stick with for the long term, and our satisfaction with “Convention over Configuration”, building a maintainable backbone stack seems to be more trouble than its worth. http://ianstormtaylor.com/rendering-views-in-backbonejs-isnt-always-simple/ http://coenraets.org/blog/2012/01/backbone-js-lessons-learned-and-improved-sample-app/ http://dev.hasenj.org/post/35572197519 http://destroytoday.com/blog/reasons-for-spinejs/

Closure

Google’s closure, as noted in AngularJS, powers Google’s top apps. Closure, though, is more of a toolkit than a framework, providing widgets, libraries, and external tools. The heart of closure is the closure compiler a JS to JS compiler designed to optimize code performance for SPI (single page interfaces). Like agility.js, closure centers around writing all JS, leaving only a skeleton of HTML code (which I suppose is better than invalid HTML) but this leaves open the first-load performance gap, and ties the complete application to the compatibility of the JS, which is dependent on the compiler’s output. There is no direct correlation between the code a developer crafts, and the what the user runs in their browser.

The following article, although not entireley relevant due to age, demonstrates some of the issues with this abstraction: http://www.sitepoint.com/google-closure-how-not-to-write-javascript/ .

dojox/mvc

With a first release in 2005, Dojo has demonstated it longevity with ease, but it has failed to demonstrate an ability to adapt to the modern expectations of a library. Before diving into the functionality of this toolkit I ran into two blocking issue: Dojo has its own license terms. Although it appears to be a modified BSD license, the modifications are beyond what I can vouch for on behalf of my company, and would require legal review. On the flip side, Dojo is the only member of this framework that is not developed in a public git repository (they use svn), and requires one to sign and submit another license in order to contribute to the codebase.

JavascriptMVC

Bitovi maintains two projects on this list; they’re also maintaining canJS. And they’re working on doneJS “_the next generation of JavaScriptMVC._” DoneJS doesn’t appear to be usable, with the website completely devoid of documentation. Its existence, though, means that JMVC is approaching EOL.

Knockout

Knockout uses a similar view binding style as batman.js, applying bindings using an HTML5 data attribute. Unfortunately, knockout is another example of an excellent half-solution, covering only the view, the model, and bindings. Examples of persistence rely on JQuery Ajax calls, meaning that substantial code writing would be required just to do CRUD via REST. Additionally, Knockout enables JS functions inside its bindings, leading to some potentially ugly views, with poor separation of responsibility.

Suitability

Once the incompatible solutions are weeded out, it comes time to find the most suitable candidate. This is the most challenging part of the roundup, as it requires a deeper level of technical understanding of the frameworks, as well as an evaluation of the framework’s community, and an estimation of its relative opportunity to thrive.

At this point, I had identified some key differences in the frameworks, and was able to hone in on features that are more desirable:

Community: Is there a competent development core? Is there a larger contributing community, or does the core seem to develop in isolation. Are the project’s maintainers responsive via social channels?

Consistency: Does the framework natively use, or allow configuration of, full URL routing and view templates that are compatible with server- and client-side rendering?

Documentation: Are the APIs documented comprehensively? How are the ‘startup’ materials, such as guides, tutorials, or browsable examples?

Maturity: How long has the project been in development? Does it release regularly? Are the releases stable and well documented?

Publicity: Can the project cite large-scale examples where current releases are in production?

Rails: beyond the core requirement to tolerate Rails, how much philosphical and code compatibility was present? Did the framework favor Convention over Configuration? Was there a storage service that aligned well with Rails’ implementation of RESTful Resources?

Tolerance: Moving to a framework will be a gradual process. Does the framework coexist nicely with our existing code mix, or will we need to make some global changes in order to implement?

For each of these attributes, I assigned a score of 1 to 5; more points are better.

batman.js

Maintained by Shopify, release 0.14.0 of batman.js is in use throughout their user’s sites as part of the optional Shopify 2 Beta release, but no 3rd-party users are listed.
batman.js is unique among the offerings in having an established solution to server-side rendering. Using pushState() where supported (Chrome >= 5, Firefox >= 4, IE >= 10, Opera >= 11.5, Safari >= 5), along with pure valid HTML templating, the client is more likely to be in sync with the browser than with any other framework. View templates can be served via RESTful URLs, or extracted from a server-rendered page.

According to the API docs, and fleshed out by example “Batman’s API is heavily inspired by Rails and designed to make Rails devs feel right at home.” Rails resources work without additional configuration. Source is organized in parallel to the ‘app’ tree.
Community: 4 Consistency: 5 Documentation: 4 Maturity: 4 Publicity: 3 Rails: 5 Tolerance: 4 Overall: 29

canJS

Maintained by Bitovi, a JS-oriented consultancy, canJS 1.1.4 provides a lightweight MVC framework with Mustache or EJS-based templating, and a hashbang-oriented router. PushState() support is planned for version 1.2. CanJS has an emphasis on balancing size and speed with efficiency, with large sections of documentation dedicated to memory management and performance charting. One of canJS’ most interesting features is its flexibility; it is built to leverage either JQuery, Zepto, Dojo, MooTools or YUI, and a host of optional plugins to extend its core functionality. The tradeoff is more code; unlike its more Railsy counterparts, canJS requires defining routes and functions even for CRUD actions via REST. Conceivably the EJS templates could be used for server-side rendering, although there aren’t any concrete examples I could find of someone doing this with respect to this framework.

Bitovi’s developers are active in project forums, responsive to github issues, and seem to be regularly maintaining their examples and documentation, although the overall quality of the documents are not as robust as many of the other projects.

Although Bitovi lists a number of large-scale public projects, including customers such as Huawei and T-Mobile, they don’t specify if any of their open-source frameworks are in use on these projects.
Community: 4 Consistency: 2 Documentation: 3 Maturity: 4 Publicity: 1 Rails: 1 Tolerance: 5 Overall: 20

ember.js

With some big names developing it and a stable release looming after the current 1.0.0-RC1, ember.js clearly has mindshare. Rails core maintainer and JQuery contributor Yehuda Katz is pushing aggressively to make a JS framework worthy of MERB. Out of the box, ember.js is easily as opinionated as Rails and batman.js. Views are handled via 2-way binding against rendered Mustache templates; although the view code is robust and straightforward, server side rendering is only “something we’re working on.” In order to help mitigate the startup lag associated with all that client-side rendering, we would need to roll our own Mustache template compiler, but apps of any complexity should expect the typical SPI issue of a blank page, followed by the browser chugging on JSON and templates before finally rendering the page.

In response to some well-founded early criticism, both API documentation and entry-level guides are comprehensive and of high quality. Community involvement is equally robust, and maintainer presentations seem to always include github issue statistics.

If anything is of concern, it is the core idea that the framework will release ‘early and often’, so there is no guarantee that any release will be of better than beta quality. The reverse is that bugfixes should follow as rapidly, but it may be difficult to pin down a version for Onsite releases.
Community: 5 Consistency: 1 Documentation: 5 Maturity: 3 Publicity: 5 Rails: 5 Tolerance: 4 Overall: 28

knockback.js

Knockback is an example of a more complete backbone framework, depending on both backbone.js and knockout. Knockback is interesting in that it takes the simplest possible approach to satisfying missing components of both frameworks; its largest drawback is simply that it relies on two independent frameworks, in addition to its overlay library, resulting in a large footprint. Ultimately, neither backbone’s routing, nor knockout’s views justify the bloat.

Because of its duplicitous nature, some of the projects attributes, like the community scope, could be attributed to both underlying projects. But, since the core concept is to provide an overlay that joins disparate parts, that’s the part I used for scoping. As a result, knockback has an understandably small audience.
Community: 2 Consistency: 2 Documentation: 2 Maturity: 3 Publicity: 1 Rails: 2 Tolerance: 4 Overall: 16

Spine

Spine builds on backbone.js’ controller API, but provides a leaner implementation of a full MVC stack. Proudly weighing in under 500 lines of CoffeeScript, Spine is the epitome of minimal JS. What is lacking in the framework, Spine’s developers attempt to make up with Rails integration that includes comprehensive code generation and scaffolding; although each function must be defined in a Spine controller to correspond to CRUD via REST, the complete basis can be generated on the command line in a single shot. There maybe some additional maintenance, but the core core patterns are provided simply, while still providing complete flexibility.

Where Spine really diverges from other platforms is the developer’s insistence on a responsive UI, to the complete ignorance of actual data transactions. UI updates always happen immediately, with server transactions queued asyncronously. The assumption is that the server is highly reliable, and that any necessary validation should me mirrored in both the client and server, so that submissions are always valid. The general design pattern for handling an (expectedly rare) server failure, is to prompt the user to refresh. There’s an interesting presentation from Throne of JS diving further into the concept.

Spine has a subtle leaning towards the obscure Eco templates, which would be ideal on a node.js application, but would not be immediately usable via Rails. Additionally, spine is very opinionated about doing all template rendering client-side with no concept of, nor intent to support, server-side rendering. This is only tolerable because of spine’s rediculously small footprint, but a large data payload is still going to impact startup time on an SPI.

The documentation is comprehensive and clear, as would be expected since the framework was developed in parallel to O’Reilly’s Javascript Web Applications.
Community: 2 Consistency: 1 Documentation: 5 Maturity: 4 Publicity: 3 Rails: 3 Tolerance: 5 Overall: 23

YUI/app

YUI/app provides an ‘MVC-like’ framework by layering the Model, Model List, Router, and View YUI components. The framework builds on the wealth of JS knowledge in YUI, with a strong emphasis on compatibility and progressive enhancement. For example, whereas batman.js uses PushState(), but falls back to hashed URLs, YUI/app uses the YUI PJAX library, which falls back to full-page rendering, maintaining the full URL. YUI/app appears to be the only framework with a legacy support mindset; documentation includes comprehensive explanations of different routing techniques, when they will work, and how to gracefully fallback. Core support for loading HTML partials from a server is a nice middle-ground for both progressive enhancement and performance balancing.

All the frameworks fall into two categories with respect to server interaction: either they are Rails-oriented, and include CRUD via REST by default, in a way that is compatible with Rails resources, or every action must be coded from scratch. YUI incorporates both, providing a REST implementation that can be mixed into a Model, or the ability to override the sync() function for complete control.

Although YUI may not have the mindshare of popular frameworks like ember.js, it certainly has proven its maintainability in the long run, with a strong commmunity and exteremely talented maintainers, such as Douglas Crockford.
Community: 4 Consistency: 4 Documentation: 5 Maturity: 5 Publicity: 3 Rails: 3 Tolerance: 3 Overall: 27

Recommendation

Of course the numbers bear it out, I recommend batman.js as the framework best suited to Studio. It is a stable, forward thinking framwork, including all the expected components while maintaining compatibility with existing standards.

Although the best feature maybe one I haven’t mentioned: we’ve already started using it. André, Cassio, and myself were introduced to the framework during our work on Dashio, which is built with batman.js in the client, and Sinatra on the server.
Ember.js is still a serious project to watch, but in general the team hasn’t been particularly receptive to using Mustache templates, and ember is nothing if not opinionated about such matters.
YUI/app stands out as a great option as well, if we want to start taking progressive enhancement seriously. Long-term viability is definitely not a concern in this project.

Show more