How I Do Javascript in Ruby on Rails

I have been asked recently how I do my javascript in a javascript-intensive production application. There are a number of things to consider when you start rolling out huge amounts of javascript in your application. If you aren't careful, it can become an unwieldy beast very quickly. It comes down to two major things to keep in mind: organize and namespace. To level set, I would like to point out that I am a purist when it comes to javascript. I find that completely separating the html, css, javascript, and server side helps to keep things clean and well designed. I always blast the prototype and script.aculo.us libraries from my projects and I never use rjs templates. I write javascript such that it keys off the metadata in the DOM to operate, so no javascript is ever generated by the server. This makes the javascript easy to test and debug, and forces you to think hard about the design of your client layer. I believe that it has helped me to produce a higher quality of code and I continue to use and refine my process.

Organize Your Scripts

It is very important to come up with a solid organizational scheme. Having a clean and consistent way of storing the code will make it easy to lookup and easy to know exactly what everything is used for. In my applications, I follow two main tenets:
  • Application specific plugins should be noted. I always name jQuery plugins that are specific to my product as jquery-productname-plugin-name.js. That has been working out pretty well to clearly identify them against the plugins you downloaded.
  • Javascript file names should match the controller action they correspond to and be nested in a folder matching the controller name. So your /products/new action's javascript would be in /public/javascripts/products/new.js.
I also arrange my css the same way. Doing that makes it really easy to know what is being used and where. When you blast a controller, you can safely blast the javascript and css directories matching it if you strictly followed the pattern.

Be Smart About Includes

Another thing I do is always have 2 javascript and 2 css files per page. One is the global template group and the other is the page specific group. In my application template I have this: [ruby]<%= javascript_include_tag "global.js", "jquery-tablesorter-min.js", ..., :cache => true %> <%= yield :tail %>[/ruby] Similarly, I have the same tags for my css but with yield :head instead. Then in my view, I do [ruby]<%- content_for :tail do -%> <%= javascript_include_tag "controller/action.js" %> <%- end -%>[/ruby]

Namespace!

There are three different types of javascript that will be attached to your page: in the document ready, as a plugin, or as static namespaced functions. The code in your document ready is fine. It should be pretty lean, but if there are lots of unique event handlers or whatnot, it can get pretty big. Your custom plugins should always have cool and unique names. So what do you do with the rest of the javascript? Namespace it as static functions. I don't typically write a lot of this, but sometimes it makes sense to separate code into some logical parts. Mine would look something like this: [js]var ControllerAction = { someFunction: function() { ... } }[/js] Sticking with the controller and action naming will always let you know where the functions you are calling came from. If something happens down the road and it gets included in Controller2's Action2, I can look at the function calls and know exactly where ControllerAction.someFunction() came from. Obviously, it isn't part of the Controller2Action2 namespace, so I need to look in /public/javascripts/controller/action.js to find it. So that is the organization of my Rails application javascript in a nutshell. Trying to stay as close to the Rails naming conventions as possible has helped me to keep a handle on the huge amounts of javascript I have written. Always remember, consistency is key to maintainability.

Performance Evaluation: Remember the Milk

If you aren't familiar with Remember the Milk, it is a totally awesome web-based to-do list manager. I use it religiously and recommend it to all my friends. The site is beautifully designed and very heavy with javascript. It uses ajax for everything and their server is extremely fast. It is very well implemented and they did almost everything right with their implementation. Going down the list of major things they did right...
  1. Gzipping the resources: They enabled gzipping. That is probably the easiest thing to do with the biggest bang for the buck. If you are using apache, which you probably are, then it is a very simple configuration change to enable mod_deflate.
  2. Minify the javascript: They compile their site's javascript files into a single file called rtm.{version}.js and minify it. That is a great practice and I highly recommend it. Adding the version number into the name is an excellent practice as well. That is critical to using far-future expires headers.
  3. Far-future expires headers: They added them to all the site's javascript, which makes up a significant portion of the total download. Doesn't help the first page load, but all subsequent access are all cached. Awesome.
Although there are a few other minor things that could be improved, the biggest performance issue with their site is the large number of image downloads. They could combine about 40 icons into a single (or maybe 2) jpg or crushed png and use css sprites to display the various icons. On my internet connection, a refresh of the page takes almost 4 seconds to download the images and that would likely be reduced to about 1/10th of the time by using a single image. Overall, they are an excellent example of how to implement a highly interactive UI in a very efficient manner. They clearly took some care in crafting it and it pays off in the load time of the page. It is a great effort and I wish more sites on the net would do the same. If you have a site that you would like me to have a look at and post my analysis and recommendations for, please leave a comment or drop a line to paul at codingfrontier.com.