12 April 2008

With rich internet applications being all the buzz these days people are finding themselves pouring more and more interactivity onto their pages. Typically, this is if nothing else through a slew of external third party libraries, or as with Dojo and Scriptaculous and YUI, entire frameworks! Taken one at a time these various scripts – used sparingly and perhaps compressed – don't cause too much a burden on your server or the client during download. Right...  

So say you build up a complex layout / widget set for your site. Say that you're also consistently using prototype.js and rico.js and a few of the scriptaculous libraries and that you've also got a few other 'commodity' scripts that are used everywhere (company libraries, a widget built on prototype, etc, etc).  Anything. Now you've got 6 different .js files to include on every page, and you haven't even STARTED loading your code – you remember that code, right? The code that actually does the line of business you set out to help with this application in the first place? All these HTTP requests are bad for the server, but they're also really bad for the client. Load enough scripts and eventually things to start to deteriorate, even crash, on the client.

So now you see the problem. Different half-solutions exist. The Dojo framework has done extensive work to provide a compressor that runs as an ant task and that even obfuscates your code. One version – thanks to a donation by AOL - even does dead code path elimination! However this is a Dojo-only solution and, frankly, I found it a little fragile if you do anything cute in your JavaScript.  Another solution is the GWT route from Google. GWT takes lava code and "compiles" into JavaScript. The advantage of this is that the JavaScript is super-lean / optimized to work across browsers and mechanically, and utterly concise. The disadvantage of it is that – like the Dojo solutions – is inextricably tied to the GWT framework.

There are JavaScript compressors. http://www.brainjar.com/js/crunch/ has been around for the better part of a decade! There are others- - and the idea is the same: take JavaScript and regex your way into a more compact JavaScript file. Some of these solutions become unusable if you neglect to terminate your statements with a semicolon as they blindly wrap all the lines together, yielding non-runnable JavaScript code. Two other problems with this solution – as with the others previously mentioned-- is that it's not automated. Each time you update your code, you need to re run it all through that program on the site. Similarly, it's only compressing the code itself, not the format in which its delivered to the client. G-Zip compression works great for JavaScript content, so long as it it's not served to browsers that don't support it.

My definition of an ideal solution would be something that knew how to combine multiple libraries into one, compact that code except where the resulting source code would be made un runnable – and that conditionally served gzip content.

Enter JAWR, a library freely available from https://jawr.dev.java.net/ that does all of the above. It even can work its magic on .css files! It provides tag libraries (accessible from both JSP and now –with the 2.0 release – from Facelets). I'm sure if you were so inclined you could reverse engineer the tag library handlers and wrap it in a Tapestry component or a Wicket widget.

Below, I walk through setting up a simple example:

For my example, I'm using JSF and Maven. Set up the web application however you like. If you're using Maven, here is the dependency I'm using:


Ensure that you have the following repository added to Maven:

<name>Java.net Repository for Maven</name>

I configured the JAWR servlet to handle my JavaScript files in my web.xml as follows:

  <param-value>js</param-value> <!-- this could be css -->
  <!-- Location in classpath of the config file -->
  </servlet>  <servlet-mapping>

The above configuration requires the presence of a file named jawr.properties on the classpath.

Here's my jawr.properties.

# Common properties
jawr.charset.name=UTF-8 # Javascript properties and mappings
jawr.js.bundle.names=appcore,railsjs# All files within /js/lib will be together in a bundle.
# The remaining scripts will be served sepparately.jawr.js.bundle.effects.id=/bundles/effects.js



The configuration format is somewhat akin to editing a log4j.properties property file.

In the example above, I'm creating two "bundles" of JavaScript files. All JS in a set is compressed together and made available as one file. It also condenses the JavaScript within the file, condensing new lines. However, if you have JavaScript on two lines and didn't use a semi-colon to terminate the first one, it will leave the second line of JavaScript on a new line.

var a = 1 
var b = 2  ;
window.alert( 'a = ' + a + ' & b = ' + b ) ;

roughly becomes:

var a = 1 
var b = 2  ; window.alert( 'a = ' + a + ' & b = ' + b ) ; 

Pretty cool, eh?

So, in the example above, I tell JAWR to create two "bundles":

One bundle called "effects" takes together all the JavaScript files in the /lib/js folder and yields one single compressed JavaScript file. Accessing the JavaScript file is simple thanks to the tag libraries shipped with the framework. It's also smart: if you've enabled gzip compression and the client support its, an appropriate URL will be rendered, otherwise a non GZIP'ed but still "minified" JavaScript URL will be rendered. Nice!

Compiling the files at these URLs is time intensive, so by default when your application starts up JAWR caches the permutations of the files it'll deliver, so theres no overhead in request processing except for the tag library to render. If you are debugging in, say, Jetty and would like to make changes to your JavaScript and still benefit from immediate changes, you can enable debug mode and you can enable a periodic "refresh" of the cached bundles. This way, JAWR will write out the individual JavaScrpt files in the same way you were doing before the switch to JAWR. Flip the debug flag off and now it'll render the URL for the single cached / compressed JavaScript file.