Saturday, May 21, 2011

Compressing JavaScript

As part of this blog post one of the recommendations I made for obtaining the best performance for downloading your JavaScript is to compress it before returning it to the requesting Web Client. There are a number of options available and I'm going to compare a variety of them to demonstrate their differences.

Before I get into the comparison result here are details on the environment used to run the tests.
  • The HTML page simply contained a <div> tag with a dojo dijit.Calendar widget attached.
  • All JavaScript is loaded via the Zazl AMD dynamic optimizer. It is delivered in one single response connected to a single script tag in the page.
  • When a JavaScript compressor was applied it was on an individual module basis, not on the whole JavaScript response that is returned.
  • Dojo 1.6 in AMD mode was used for providing the dijit.Calendar Widget.
  • RequireJS was used for the AMD loader. 
  • Google Chrome was used to load the page and its Developer Tools used to show the size of the JavaScript downloaded
  • The list of modules returned were as follows 
dojo/_base/_loader/bootstrap.js
dojo/lib/backCompat.js
dojo/_base/_loader/hostenv_browser.js
dojo/lib/kernel.js
dojo/_base/lang.js
dojo/_base/array.js
dojo/_base/declare.js
dojo/_base/connect.js
dojo/_base/Deferred.js
dojo/_base/json.js
dojo/_base/Color.js
dojo/_base/window.js
dojo/_base/event.js
dojo/_base/html.js
dojo/_base/NodeList.js
dojo/_base/query.js
dojo/_base/xhr.js
dojo/_base/fx.js
dojo/lib/main-browser.js
dijit/lib/main.js
dojo/i18n.js
dojo/cldr/supplemental.js
dojo/date.js
dojo/regexp.js
dojo/string.js
dojo/date/locale.js
dijit/_base/manager.js
dojo/Stateful.js
dijit/_WidgetBase.js
dojo/window.js
dijit/_base/focus.js
dojo/AdapterRegistry.js
dijit/_base/place.js
dijit/_base/window.js
dijit/_base/popup.js
dijit/_base/scroll.js
dojo/uacss.js
dijit/_base/sniff.js
dijit/_base/typematic.js
dijit/_base/wai.js
dijit/_base.js
dijit/_Widget.js
dojo/date/stamp.js
dojo/parser.js
dojo/cache.js
dijit/_Templated.js
dijit/_CssStateMixin.js
dijit/form/_FormWidget.js
dijit/_Container.js
dijit/_HasDropDown.js
dijit/form/Button.js
dijit/form/DropDownButton.js
dijit/Calendar.js
app/Calendar.js

The types of compression used were :
No Compression






With no compression at all, that is gzip is turned off and the modules are written into the response "as is" results in a transfer of around 755kb. This is quite a sizable chunk considering that all the page contains is a single widget.

Gzip
  




As you can see just by turning on Gzip results in a 218kb download vs 755kb.
 
Gzip + Simple Comment and Whitespace removal





Once again quite a signification size reduction (85kb vs 218kb) just by removing comments and whitespace. I should note that I used simple home grown code to remove comments and whitespace. Writing code that reliably removes comments is actually not as straight forward as it would first appear.  Ideally using a real JS parser is the best solution but that can increase the compression time. Google Closure does support a "comment and whitespace" removal mode and this will do a thorough job at the potential expense of increased time to compress.

Gzip + Dojo Shrinksafe





Shrinksafe renames locally scoped variable names in addition to comment and whitespace removal. We have now gone from 85kb to 68kb.

Gzip + Google Closure





This result is from using Google Closure with its SIMPLE_OPTIMIZATIONS mode turned on. We see a better results than Shrinksafe going from 68kb to 58kb. Closure also support more advanced optimizations but typically you have to modify your code to be closure friendly. If you plan to exclusively use Closure this might be something to consider.

Gzip + Uglify-JS



 

This result is from using Uglify-JS in its default mode. As can be seen Uglify-JS compresses almost as well as Closure. The following code was used to execute it.

var jsp = require("uglify-js").parser;
var pro = require("uglify-js").uglify;

var ast = jsp.parse(src);
ast = pro.ast_mangle(ast);
ast = pro.ast_squeeze(ast);
var compressedSrc = pro.gen_code(ast);

Conclusion
It's pretty obvious that compression can provide substantial reduction in the size of the JavaScript code delivered to WebClients. Certainly the best bang for the buck is simply to turn on Gzip, however adding any of the 3 compression engines used here, or even just removing whitespace and comments, will result in much smaller downloads.