I recently came across Google’s closure compiler, which I really like for its advanced optimizations feature. It bothered me, since I’m writing a webapp, that most compilers take too much care in respecting that all of the attributes of my methods be available in the global scope, at the expense of a lot of kilobytes.
After fixing up jQuery using the appropriate closure externs file (see http://code.google.com/p/closure-compiler/source/browse/trunk/contrib/externs/jquery-1.7.js), I was still having issues getting my code to run with ajax data. Specifically, parts of my code accessed the data from a callback:
function onSuccess(data, status, request) { if (data.success) { ... }
Whenever closure would compile this bit of code, it would replace data.success with something like d.st. Of course, since the server didn’t know that success was renamed, the success token was actually in d.success. The workaround, it turns out, is to write a thin translation library, and to process all of your data both before looking at received data and before sending data back to the server. This way, the client code can translate variables from the longhand that the server might use to the shorthand in the minified JS. The code looks like this:
//This is a dict containing all of the attributes that we might see in remote //responses that we use by name in code. Due to the way closure works, this //is how it has to be. var closureToRemote = { ts: 'ts', snippet: 'snippet', username: 'username' , hasMore: 'hasMore', success: 'success', multi: 'multi' , id: 'id', multiWithSelf: 'multiWithSelf', since: 'since' }; var closureToLocal = {}; for (var i in closureToRemote) { closureToLocal[closureToRemote[i]] = i; } function _closureTranslate(mapping, data) { //Creates a new version of data, which is recursively mapped to work with //closure. //mapping is one of closureToRemote or closureToLocal var ndata; if (data === null || data === undefined) { //Special handling for null since it is technically an object, and we //throw in undefined since they're related ndata = data; } else if ($.isArray(data)) { ndata = [] for (var i = 0, m = data.length; i < m; i++) { ndata.push(_closureTranslate(mapping, data[i])); } } else if (typeof data === 'object') { ndata = {}; for (var i in data) { ndata[mapping[i] || i] = _closureTranslate(mapping, data[i]); } } else { ndata = data; } return ndata; } function closureizeData(data) { return _closureTranslate(closureToLocal, data); } function declosureizeData(data) { return _closureTranslate(closureToRemote, data); }
Usage is pretty straightforward - the first line of the onSuccess() method becomes
data = closureizeData(data);
And the first line of my gateway method for sending data to the server becomes
dataToSend = declosureizeData(dataToSend);
After that, my application worked completely with closure’s advanced optimizations, for a size reduction from 206kb with YUI compressor’s minification down to 134kb with Google Closure’s advanced compilation minification.