AJAX Google API to Minify Javascript using Ruby on Rails
How to optimizing JavaScript performance in Ruby on Rails. This will only focus on one aspect of JS performance optimization, namely, writing a build script to concatenate/minify the JS, and setting up Rails to easily toggle between the compressed and normal files. Also, if your site uses a JavaScript library, we'll explore including it from Google's AJAX Libraries API.
The reason to do these optimizations are for client-side performance. Concatenating many files together into one is especially effective: 10 1-kilobyte files are much slower to download than a single 10k file. You might not think it makes much of a difference, but I estimate that removing 10 additional JS files, for instance, will shave 500-1000ms off latency. Plus, all of the time spent loading the JS will leave the page blank, if you put it in the head.
We'll need to be able to easily toggle between fully commented code and minified code, both for code hosted by us and code from the Google AJAX Libraries API. Since Google offers both minified and normal versions of the code, this should be no problem.
Including JavaScript in Ruby on Rails
First of all, let's look at how JavaScript is included in Ruby on Rails, the javascript_include_tag method. The method quite simply takes any number of source URLs and returns an HTML script tag for each of the sources provided. For instance:
javascript_include_tag("script1.js", "script2.js", "script3.js")
Result:
<script type="text/javascript" src="/javascripts/script1.js"></script>
<script type="text/javascript" src="/javascripts/script2.js"></script>
<script type="text/javascript" src="/javascripts/script3.js"></script>
There's a little bit of magic you can do which is to include all JavaScript files in the /public/javascripts folder automatically, as well as having them concatenated into a single file. It looks like this:
javascript_include_tag :all, :cache => true # when ActionController::Base.perform_caching is true
The resulting code is:
<script type="text/javascript" src="/javascripts/all.js"></script>
If you don't want them concatenated into a single file, e.g. for development purposes, you could do something like this:
# config/environments.rb:
DEBUG_JS = false;
# environments/development.rb:
DEBUG_JS = true;
# app/views/layouts/site.html.erb or wherever you include the JavaScript in your site:
<%= javascript_include_tag(:all, :cache => true) if DEBUG_JS == false || ENV["RAILS_ENV"] != "development"
javascript_include_tag(:all) if DEBUG_JS == true && ENV["RAILS_ENV"] == "development"
%>
I'm not sure how to integrate JSMin, YUICompressor or any other minification into the cached file, though, so I will explore the options for manual building and inclusion next.
Something else to remember is that caching needs ActionController::Base.perform_caching to be true in order to work. This is the default in production, but not development, though of course you can override it in your environments/development.rb file. For more information, view the docs for the javascript_include_tag.
JavaScript Concatenation and Minification
Next, we'll write a custom build script to generate a concatenated, minified version of our files. Ideally, Rails would automatically run it through a minifier, but I'm not sure how to set that up. In the meantime, this is a working solution that is pretty straightforward and requires minimal effort. I wish I were more skilled at shell scripting (and I highly urge readers to contribute their own code), but I'll show my implementation as an example regardless.
I'm on a Windows machine and wrote it as a batch file (.bat), but you can easily do this on linux, just using pipe (the | character) instead of the two right-angle brackets (>>). Here goes:
java -jar yuicompressor-2.3.5.jar "script1.js" -o main.js
java -jar yuicompressor-2.3.5.jar "script2.js" >> main.js
java -jar yuicompressor-2.3.5.jar "script3.js" >> main.js
java -jar yuicompressor-2.3.5.jar "script4.js" >> main.js
Nothing too sophisticated, just a straightforward script that generates main.js using the YUICompressor. To use it, simply run the batch file in the same folder as the JavaScripts and yuicompressor-2.3.5.jar. It will output a main.js file. I chose to run it separately for each individual file so if there are any errors, you can see where they occurred. If you concatenate all of the files into one and then compress it, debugging is difficult as the line number which threw the error is unknown.
I chose to use YUICompressor over JSMin since it seems to have better error messaging, warnings, and be a bit more strict. One of my former colleagues did some tests that determined that JSMin was faster in terms of using less CPU than YUICompressor (and certainly for minifiers which use eval(), such as Dean Edwards' packer). Unfortunately, I can't remember the specifics of the test or which platforms it was on, so that data is pretty much worthless. In any case, I decided on YUICompressor but you can use JSMin or whichever minifier you prefer.
If you are going to use YUICompressor, you must have Java installed and included in your path. If it isn't in your path, you can use the full path to Java, for instance, "C:\Program Files\Java\bin\java.exe" -jar [filename] [options].
Using the Google AJAX Libraries
Depending on your opinion about Google's recent offer to host the world's JavaScript frameworks with its AJAX Libraries API, you may not find this suggestion too useful, but I think it's helpful, especially to those without access to their hosting provider's response header settings, to present here. Without going into too much detail, suffice it to say that besides the caching benefit of using Google's API, their servers are configured "correctly" for best performance (far future expires headers, Gzip, etc).
One thing, though, is that you will want to include a minified version of the JavaScript in production and a full version in development, to ease debugging. It's impossible to debug minified JavaScript (don't even try), so we have to set a toggle like in the above example. Say your site is including jQuery. You may want to include a minified version on the live server but read the full code locally.
Toggling Minified Google JS
Here's how you can include minified jQuery in the production Ruby on Rails code while including the full jQuery library, comments and all, in development:
<%=
javascript_include_tag("http://ajax.googleapis.com/ajax/libs/jquery/1.2.6/jquery.min.js",
:all, :cache => true) if DEBUG_JS == false || ENV["RAILS_ENV"] != "development"
javascript_include_tag("http://ajax.googleapis.com/ajax/libs/jquery/1.2.6/jquery.js",
:all) if DEBUG_JS == true && ENV["RAILS_ENV"] == "development"
%>
<Or, if you didn't want to include everything in the JS folder, and wanted to granularly include only specific files, you might rewrite it like so:
<%= if DEBUG_JS == false || ENV["RAILS_ENV"] != "development"
javascript_include_tag ("http://ajax.googleapis.com/ajax/libs/jquery/1.2.6/jquery.min.js",
"main.js)
elsif DEBUG_JS == true && ENV["RAILS_ENV"] == "development"
javascript_include_tag("http://ajax.googleapis.com/ajax/libs/jquery/1.2.6/jquery.js",
"script1.js",
"script2.js",
"script3.js",
"script4.js")
end %>
That's all there is to it! Just switch the DEBUG_JS flag when you want to test in development mode with/without compression, and don't worry about when it's in production since it will always serve up the minified, concatenated JavaScript. The logic ensures that you won't accidentally start serving up the separate files in production, while giving you the flexibility to store your JavaScript in as many files as you like without negative consequences on the front end, as well as reading fully-commented code./p>
Labels: ajax, google, jquery, ruby on rails
How to optimizing JavaScript performance in Ruby on Rails. This will only focus on one aspect of JS performance optimization, namely, writing a build script to concatenate/minify the JS, and setting up Rails to easily toggle between the compressed and normal files. Also, if your site uses a JavaScript library, we'll explore including it from Google's AJAX Libraries API.
The reason to do these optimizations are for client-side performance. Concatenating many files together into one is especially effective: 10 1-kilobyte files are much slower to download than a single 10k file. You might not think it makes much of a difference, but I estimate that removing 10 additional JS files, for instance, will shave 500-1000ms off latency. Plus, all of the time spent loading the JS will leave the page blank, if you put it in the head.
We'll need to be able to easily toggle between fully commented code and minified code, both for code hosted by us and code from the Google AJAX Libraries API. Since Google offers both minified and normal versions of the code, this should be no problem.
Including JavaScript in Ruby on Rails
First of all, let's look at how JavaScript is included in Ruby on Rails, the javascript_include_tag method. The method quite simply takes any number of source URLs and returns an HTML script tag for each of the sources provided. For instance:
javascript_include_tag("script1.js", "script2.js", "script3.js")
Result:
<script type="text/javascript" src="/javascripts/script1.js"></script> <script type="text/javascript" src="/javascripts/script2.js"></script> <script type="text/javascript" src="/javascripts/script3.js"></script>
There's a little bit of magic you can do which is to include all JavaScript files in the /public/javascripts folder automatically, as well as having them concatenated into a single file. It looks like this:
javascript_include_tag :all, :cache => true # when ActionController::Base.perform_caching is true
The resulting code is:
<script type="text/javascript" src="/javascripts/all.js"></script>
If you don't want them concatenated into a single file, e.g. for development purposes, you could do something like this:
# config/environments.rb: DEBUG_JS = false; # environments/development.rb: DEBUG_JS = true; # app/views/layouts/site.html.erb or wherever you include the JavaScript in your site: <%= javascript_include_tag(:all, :cache => true) if DEBUG_JS == false || ENV["RAILS_ENV"] != "development" javascript_include_tag(:all) if DEBUG_JS == true && ENV["RAILS_ENV"] == "development" %>
I'm not sure how to integrate JSMin, YUICompressor or any other minification into the cached file, though, so I will explore the options for manual building and inclusion next.
Something else to remember is that caching needs ActionController::Base.perform_caching to be true in order to work. This is the default in production, but not development, though of course you can override it in your environments/development.rb file. For more information, view the docs for the javascript_include_tag.
JavaScript Concatenation and Minification
Next, we'll write a custom build script to generate a concatenated, minified version of our files. Ideally, Rails would automatically run it through a minifier, but I'm not sure how to set that up. In the meantime, this is a working solution that is pretty straightforward and requires minimal effort. I wish I were more skilled at shell scripting (and I highly urge readers to contribute their own code), but I'll show my implementation as an example regardless.
I'm on a Windows machine and wrote it as a batch file (.bat), but you can easily do this on linux, just using pipe (the | character) instead of the two right-angle brackets (>>). Here goes:
java -jar yuicompressor-2.3.5.jar "script1.js" -o main.js java -jar yuicompressor-2.3.5.jar "script2.js" >> main.js java -jar yuicompressor-2.3.5.jar "script3.js" >> main.js java -jar yuicompressor-2.3.5.jar "script4.js" >> main.js
Nothing too sophisticated, just a straightforward script that generates main.js using the YUICompressor. To use it, simply run the batch file in the same folder as the JavaScripts and yuicompressor-2.3.5.jar. It will output a main.js file. I chose to run it separately for each individual file so if there are any errors, you can see where they occurred. If you concatenate all of the files into one and then compress it, debugging is difficult as the line number which threw the error is unknown.
I chose to use YUICompressor over JSMin since it seems to have better error messaging, warnings, and be a bit more strict. One of my former colleagues did some tests that determined that JSMin was faster in terms of using less CPU than YUICompressor (and certainly for minifiers which use eval(), such as Dean Edwards' packer). Unfortunately, I can't remember the specifics of the test or which platforms it was on, so that data is pretty much worthless. In any case, I decided on YUICompressor but you can use JSMin or whichever minifier you prefer.
If you are going to use YUICompressor, you must have Java installed and included in your path. If it isn't in your path, you can use the full path to Java, for instance, "C:\Program Files\Java\bin\java.exe" -jar [filename] [options].
Using the Google AJAX Libraries

Depending on your opinion about Google's recent offer to host the world's JavaScript frameworks with its AJAX Libraries API, you may not find this suggestion too useful, but I think it's helpful, especially to those without access to their hosting provider's response header settings, to present here. Without going into too much detail, suffice it to say that besides the caching benefit of using Google's API, their servers are configured "correctly" for best performance (far future expires headers, Gzip, etc).
One thing, though, is that you will want to include a minified version of the JavaScript in production and a full version in development, to ease debugging. It's impossible to debug minified JavaScript (don't even try), so we have to set a toggle like in the above example. Say your site is including jQuery. You may want to include a minified version on the live server but read the full code locally.
Toggling Minified Google JS
Here's how you can include minified jQuery in the production Ruby on Rails code while including the full jQuery library, comments and all, in development:
<%= javascript_include_tag("http://ajax.googleapis.com/ajax/libs/jquery/1.2.6/jquery.min.js", :all, :cache => true) if DEBUG_JS == false || ENV["RAILS_ENV"] != "development" javascript_include_tag("http://ajax.googleapis.com/ajax/libs/jquery/1.2.6/jquery.js", :all) if DEBUG_JS == true && ENV["RAILS_ENV"] == "development" %><
Or, if you didn't want to include everything in the JS folder, and wanted to granularly include only specific files, you might rewrite it like so:
<%= if DEBUG_JS == false || ENV["RAILS_ENV"] != "development" javascript_include_tag ("http://ajax.googleapis.com/ajax/libs/jquery/1.2.6/jquery.min.js", "main.js) elsif DEBUG_JS == true && ENV["RAILS_ENV"] == "development" javascript_include_tag("http://ajax.googleapis.com/ajax/libs/jquery/1.2.6/jquery.js", "script1.js", "script2.js", "script3.js", "script4.js") end %>
That's all there is to it! Just switch the DEBUG_JS flag when you want to test in development mode with/without compression, and don't worry about when it's in production since it will always serve up the minified, concatenated JavaScript. The logic ensures that you won't accidentally start serving up the separate files in production, while giving you the flexibility to store your JavaScript in as many files as you like without negative consequences on the front end, as well as reading fully-commented code./p>
Labels: ajax, google, jquery, ruby on rails
5 Comments:
Your formatting is looking messed up in the examples
Thanks, Lowell! Much to my dismay, I noticed that there were some formatting errors in this post as well. It's no excuse, but here are some of the factors contributing to the messiness:
1.) Blogger makes it really difficult to format, for instance, BR tags are completely stripped.
2.) We are in the process of switching over from Blogger to Joomla so I figured I would reformat all the articles then anyway.
3.) I don't have the Ruby extension for dp.Highlighter so the Ruby code doesn't have any highlighting/code coloring.
Bear with me for now, this blog is still only 2 months old! We're a young pup and working out all the quirks.
In the meantime, any budding Joomla developers who want to contribute to the site are welcome! And similarly, any readers of this blog who feel they have something to contribute should contact me and we can set something up. (I'm looking at you Lowell!)
One of the big gotchas with using the :cache flag is that changes to the original css/js files are not reflected until the cached file is deleted and mongrel is restarted. Due to this, it might be better to write a rake task to concatenate the files in advance and avoid the unnecessary risk.
I just wrote my first blog post ever, which happens to be about using the google ajax library api without needing to change existing calls to javascript_include_tag in your views. (See http://www.strictlyuntyped.com/2008/06/using-google-ajax-libraries-api-with.html) You *might* be interested in what I suggest.
@matthew: Thanks for posting! I am very interested and thanks for the info about :cache as well. I've only just started working on a Ruby on Rails site in production and I know I have a lot to learn.
I'm just about to head out the door but I'll check out your article soon. Great idea on the rake task as well.
Do you have any experience with capistrano? I was wondering if it would be a good task for capistrano since it is a deployment type thing -- though capistrano can call rake tasks of course so it may be preferable to put it in rake so you can generate the JS file at will.
@jonah: I am not a Capistrano expert, but you will definitely want to add it to your deployment task list. It is best to stop mongrel, reset memcache, then start mongrel.
I have found the :cache option to be quite problematic. I currently 'build' my javascript files, check those into source, and reference them directly by name. Building the files is very similar to the operation performed automagically inside javascript_include_tag.
Post a Comment
Subscribe to Post Comments [Atom]
<< Home