Back from Backbone

So I refactored that Raspberry Pi project this weekend. It was a small Backbone.js app - a pretty simple JSON consumer where users can log in, feeds are fetched from a third party and then the app mixes some of its own data in with that.

Why?

Speed! Two kinds of speed, actually: partly the user’s perceived download speed but mostly my own development speed. I quite like Backbone, it’s a rock-solid way of setting up models and reacting to the events they dispatch. But if you want to render anything more complex than a simple Todo application you’re going to have to wrestle it. And I don’t particularly enjoy wrestling my frameworks. Since the app is hooked up to a JSON pump (and I’m not in control of that JSON) Backbone seemed like a natural fit, and getting to a prototype level was a breeze! Trouble is, you’re very rarely satisfied with the layout and flow of a prototype - and once the time comes to iterate and tinker with the user interface, a non-prescriptive client side framework like Backbone really starts to step on your toes. EJS templates mitigate the damage somewhat but far too often you’re finding yourself fiddling around with view state models, adding and removing functions and shuffling .js- class names around just to move a box to another place or giving a component a new responsibility. I wanted to simplify my user interface and I was also pretty interested in what the performance difference would be between a Pi-powered Rails app and a client-side Backbone app.

Hi ho, back to Rails we go

I started by commenting out all the JS and moving the HTML from EJS to ERB templates. This was sort of a painstaking process since Backbone (or underscore templates, to be more precise) has no notion of partials - a view is comprised of both a HTML template part and a JS part and one can’t live without the other. Since I had been smart about using .js- as a prefix for everything Backbone hooked into I at least knew where to look, but I had to take things like className and tagName into account as well. I had an open tab with the rendered HTML I was shooting for, and used another tab for comparing it with my new Rails output with some mock objects thrown in. Some hours of legwork later and I had the same markup running on Rails. Replacing the router was trivial since I had been using a resourceful layout for my routes.

Rendering JSON in ERB views

If you use ActiveRecord models, Rails does a lot of nice things for you - you can use partial paths and put your things in views/things/_thing.html.erb and then you can just do <%= render @things %>. With JSON, you’re a lot more on your own. The JSON I was receiving from the remote server didn’t really map to my local objects so I ended up wrapping the JSON as OpenStruct objects and rendering a little more manually (using <%= render collection: @things, partial: 'things/thing' %> instead) which made the JSON behave a little more like the ActiveRecord models I’m used to. It’s not perfect but it’s fast and easy and at least it keeps me from having to do a bunch of presence checks in my views. I’m sure there’s a better way and if I improve it I might post about that, too.

Is it faster?

Yes. Emphatically yes! Moving things around and changing the UI is so much easier. With Backbone, if I wanted to render a collection of things I would have to create a thingCollection.js, a thingCollectionView.js, make a container in an existing view for them to render in, add renderThings() to the collection’s render() function, make a renderThing() helper function for that function, then set up a bunch of listeners for the stuff I wanted to happen. Now, I just slap a <%= render collection: @things %> statement in there and they show up. If I want some JS hooking in to them, I add a plain old jQuery listener. No more having to go between JS and EJS, no more massaging model.toJSON(), no more having to show and hide spinners to deal with a slow response from the third-party server. It’s very freeing. I made a CLOC of the app/ directory, pre- and post-refactoring:

Before refactoring:

-------------------------------------------------------------------------------
Language                     files          blank        comment           code
-------------------------------------------------------------------------------
Javascript                      28            170             46           2214
SASS                             7             54              4            641
HTML                            30             53              0            534
Ruby                            20             57             11            335
JSON                            61              0              0             61
CSS                              2              3             19             28
-------------------------------------------------------------------------------
SUM:                           148            337             80           3813
-------------------------------------------------------------------------------

After refactoring:

-------------------------------------------------------------------------------
Language                     files          blank        comment           code
-------------------------------------------------------------------------------
Javascript                       9             22             19           1458
HTML                            19             45              0            410
Ruby                            18             62              4            378
SASS                             5             40             17            360
JSON                            61              0              0             61
CSS                              1              0             18              0
-------------------------------------------------------------------------------
SUM:                           113            169             58           2667
-------------------------------------------------------------------------------

Suddenly there’s 35 less files to worry about, and a 30% reduction in lines of code. It’s a small project but still, that’s massive! Let’s compare the loading speeds (both benchmarked with a cold cache):

Before refactoring:

With Backbone

Initial load time is extremely quick, but the JSON load hurts performance. The UI is shown pretty much immediately but it takes roughly two and a half seconds before the actual content reaches the user. There’s a spinner in place but the data fetching makes the application feel sluggish.

After refactoring:

Without Backbone

I was surprised to find that the refactored version took less than a second to render. DOMContentLoaded fires at around 800ms and everything is parsed and ready for interaction. My assumption was that the JSON round trip would be almost as bothersome as before and now the Raspberry Pi has to handle the rendering duties, too. But it does a sterling job of quickly returning the rendered page to us! Stripping Backbone, Underscore, and all the client-side rendering code shaved some 15KB from the JS that has to go over the wire but since I’m using both async and defer on the JavaScript that’s not really noticable.

The moral of the story

If you’re married to realtime updates, optimistic UI, or have a ton of complicated view state that needs to live on the client side, you should definitely consider using a client-side framework. It’s not a silver bullet but sometimes it’s the right answer. But rendering server side is going to get you a lot of things for free. You get predictability, device independence, instrumentation, less boilerplate code, and - surprisingly - sometimes speed, too. Backbone might not be state of the art anymore but I think this is true even if you pick a more modern framework. Server side rendering with a smattering of jQuery will get you very far, and if your JavaScript is evolving into a big ball of mud there are many ways of untangling it without having to take over rendering wholesale from the server. YMMV of course - it all depends on your use case, your skills and expertise, the complexity of your site, etc. But before jumping in and making your project a fully isomorphic JS app because that’s how everyone seems to be doing it these days, consider making your server work for you instead. You might be surprised at how fast and easy it is.