February 21, 2013

As web applications become richer and more complicated the amount of JavaScript running them increases. More code means longer download times which means more waiting before your application or web site is usable. Thankfully there’s an easy solution that’s already widely used in the web development community: minification. Minified JavaScript strips whitespace and renames variables to produce a smaller download size.

Removing whitespace is pretty straightforward, but renaming variables is a more abstract concept. With tools like UglifyJS this is handled for you but here’s an example of what’s going on. Let’s look at this simple function that takes a parameter and alerts a message with that variable:


var sayHi = function(name) {
  alert('Hi ' + name + '!');
};

With script minification the above code will be changed to this:


var sayHi=function(a){alert("Hi "+a+"!")};

without altering the behavior of the function. As you can see, all whitespace that can be removed has been and the variable “name” was changed to “a”. The end result is the removal of 26 characters from the code. It’s not hard to see that the savings from minification will quickly add up!

You can take this one step further by wrapping your code in immediately-invoked function expressions (IIFEs). That’s a pretty scary looking name, but chances are you’ve seen these before, this is an example:


(function($) {
  $('h1').css('text-decoration', 'blink');
}(jQuery));

The function wrapped in parentheses is immediately invoked with jQuery as its argument. Within the function jQuery is accessed with the $ variable. Abstracting variables in IIFEs is not required though, consider this case:


(function(Drupal) {
  Drupal.behaviors.sayHi = {
    attach: function(name) {
      alert('Hi ' + name + '!');
    }
  };
}(Drupal));

Minifying this code produces the following:


(function(a){a.behaviors.sayHi={attach:function(a){alert("Hi "+a+"!")}}})(Drupal);

Keeping the “Drupal” variable in your script makes sense because that’s the namespace you’re used to accessing it with and it does have meaning in the code, but by including it as an argument to the IIFE UglifyJS will replace any instances of “Drupal” with a shortened variable.

What about Drupal?

So, this is pretty cool, right? Unfortunately Drupal 7 doesn’t provide minification out of the box. Thankfully, there are contrib solutions to this problem! The Speedy module provides pre-minified scripts but unfortunately needs to be re-released every time core is updated or you need to re-minify the files yourself on core updates.

I wasn’t completely satisfied with this approach so I built uglify.me and a companion Drupal module, UglifyJS, to do script minification on the fly. uglify.me isn’t the only “minifier-as-a-service” out there, but I wanted to be able to throw something up quickly and be fairly confident that it wouldn’t upset some other poor developer out there hosting their own service.

The uglify.me service accepts POST requests of un-minified JavaScript and returns the minified version. Simple! The UglifyJS module provides an API to expose Drupal scripts that should be minified:


/**
 * Implements hook_uglifyjs_info().
 */
function mymodule_uglifyjs_info() {
  return array(
    drupal_get_path('module', 'mymodule') . '/js/mymodule.js',
  );
}

The downside to this approach is that each script will create another request to the uglify.me service when the site cache is cold. If you expose a lot of scripts to the UglifyJS API this will be time consuming and could cause timeouts.

If you’re running Pressflow 7.20.1+ or apply this patch to core and you have JavaScript concatenation enabled (which you should, if you’re in production) the UglifyJS module will automatically minify the concatenated scripts. This greatly reduces the number of requests to the web service and overhead associated with minification.

Caveats

The biggest issue that I’ve encountered with this module is the requests made to the external web service. If there’s a problem connecting to the remote server the time spent waiting for the response is wasted and can cause timeouts on cold caches. This is mitigated if you’re using Pressflow 7.20.1+ since core will not request files to be rebuilt if the hash of the concatenated scripts did not change.

The uglify.me service also currently strips some header comments from scripts which would could remove any copyright or license information if they exist. This should be fixable from the uglify.me service, so if it’s bothering you and I haven’t had time to fix it before you need it, As of uglify.me v0.1.0 any comment containing the words “license” or “copyright” (case insensitive), or the common build tags “@preserve” and “@cc_on” will not be stripped from the source code. This is controlled by a regular expression so if you find a general case that’s not met by the current regular expression open a pull request to fix it!

Conclusion

Script minification is a great way to reduce your site’s download footprint and increase usability. If bandwidth and performance are concerns you should be minifying, regardless of your ultimate solution. Informal testing on the recently released Full Plate Living site showed reduction in the front page’s download weight by an average of 110KB*. Not monumental, but not too bad either.

Happy minifying!

* Note: The UglifyJS module is not currently in use on the production Full Plate Living site, but it likely will be soon!

Elliott Foster is a developer and standard nerd.

Comments