Simple JavaScript and CSS File Bundler

August 7th, 2008 posted by admin

Optimizing web page loading is not a new subject, but it remains one that doesn’t get as much attention as it deserves.  Web/UI developers often assume that site performance is mostly attributed to back-end operations (database interactions, processing, etc.), servers themselves (hardware and software) and other variants such as bandwidth, traffic, etc. But there are many ways to optimize client-side code in a way that will result in a noticeable improvement in site loading and performance.

Fortunately, some of these techniques are becoming more common. For example, CSS Sprites are frequently seen now days and are heavily used by Google (sample) and Yahoo (sample).  The reason CSS Sprites are a good idea is because reducing the number of requests means that servers have less to process and deliver, while the browsers have fewer components to download. This point is one particularly worth noting since before Firefox version 3 and Internet Explorer version 8, most browsers supported only 2 concurrent requests per domain. (With new browsers, that number goes up to 6, but the concept is no less important.)

So, loading fewer files is good for both the server and the client. CSS Sprites allow us to cut down the number of images which need to be downloaded (and also prevent having to preload images which are initially hidden, such as rollovers), but how can this technique be applied to other page components?

JavaScript files in particular have matured and grown both in number and size, and could really benefit from optimization. Use of libraries, as well as obfuscation and compression methods have thankfully increased as well, and there are now many options for delivering scripts in a quick and lean manner. However, not as much attention has been paid to minimizing the number of files which are requested. The New York Times homepage makes nearly 20 separate JavaScript include calls. These include various external libraries, such as Prototype and sIFR, tracking and ad-support utilities, etc.

Here’s just an section of what this ends up looking like in the source. (I’ve excluded many additional script includes, and obviously all CSS files):

<script type="text/javascript" src="http://graphics8.nytimes.com/js/Tacoda_AMS_DDC_Header.js"></script>
<script type="text/javascript" src="http://graphics8.nytimes.com/js/common/screen/DropDown.js"></script>
<script type="text/javascript" src="http://graphics8.nytimes.com/js/common.js"></script>
<script type="text/javascript" src="http://graphics8.nytimes.com/js/fileit.js"></script>
<script type="text/javascript" src="http://graphics8.nytimes.com/js/home/screen/common.js"></script>
<script type="text/javascript" src="http://graphics8.nytimes.com/js/app/analytics/controller_v1.1.js"></script>
<script type="text/javascript" src="http://graphics8.nytimes.com/js/app/analytics/gw.js?csid=H07707"></script>
<script type="text/javascript" src="http://graphics8.nytimes.com/js/app/analytics/revenuescience.js"></script>
<script type="text/javascript" src="http://graphics8.nytimes.com/js/app/analytics/wtinit.js"></script>
<script type="text/javascript" src="http://graphics8.nytimes.com/js/app/analytics/wtbase.js"></script>
<script type="text/javascript" src="http://graphics8.nytimes.com/packages/html/multimedia/js/swfobject.js"></script>
<script type="text/javascript" src="http://graphics8.nytimes.com/packages/html/multimedia/js/NYTInlineEmbed.js"></script>
<script type="text/javascript" src="http://graphics8.nytimes.com/feedroom/nytc4/nytd/embed.js"></script>
<script type="text/javascript" src="http://graphics8.nytimes.com/js/common/screen/InsideNYTimesBrowser.js"></script>
<script type="text/javascript" src="http://graphics8.nytimes.com/js/app/analytics/trackingTags_v1.1.js"></script>

Also keep in mind that loading of each JavaScript file stops the browser from loading remaining components until the script is completely loaded since each script may impact the page content which follows it, such as additional scripts, DOM elements, etc. So the benefit of minimizing the number of files requested is again a very real real.

Much of this also applies to CSS files as well. And even though we’re more limited when it comes to code compression, we can deliver all CSS declarations in a single file once again improving page performance and reducing server requests.

FileBundler

I was looking for a good way to accomplish JavaScript and CSS bundling and wasn’t thrilled about the solutions I came across, so I decided to build one myself. The result is a FileBundler object, written in PHP(5).

Some features of the FileBundler object:

  • It supports both JavaScript and CSS bundling and different compression methods for each file type. Currently supported is JavaScript compression using Dean Edwards’ Packer or Douglass Crockford’s jsMin. (Note that compression using either method requires additional PHP classes. Get a compatible copy of Packer here: http://joliclic.free.fr/php/javascript-packer/en/. PHP version of jsMin is available here: http://code.google.com/p/jsmin-php/.)
  • Since the order of JavaScript and CSS includes is almost always important, the bundle maintains the ordering. However, in the event that the order of certain files is irrelevant, the bundler is will check if a matching bundle already exists and will use that one before creating a new one. This reduces the number of generated bundles on sites where different developers (or templates) include files in sequences which are appropriate to them or their scenarios.
  • Source files are untouched and remain just that – source files. When updating the source, all you need to do is delete the outdated bundles and new changes will be re-bundled when FileBundler doesn’t find an existing appropriate bundle. (Personally, I clear out my /bundles directory whenever I roll out new changes. I also like the idea of source files existing in all environments, even if they’re not directly requested. This allows me to ensure that the correct file versions are where they should be and address any inconsistencies or code problems.)
  • File list can be included at the top of the bundle. This helps identify problems caused by missing or mis-ordered files.
  • Bundling can quickly and easily be disabled. In this case, FileBundler writes out separate SCRIPT (for JavaScript includes) or LINK (for CSS files) tags for each of the files. While this defeats the purpose, its a good way to isolate problems during development and determine if a problem occurs during bundling (or code compression) or elsewhere in the code.

Download the source code here: FileBundler

Here’s an example of it in use. The writeBundle() call should be made in your HEAD tag.

<?php
	require_once 'FileBundler.php';

	$jsBundler = new FileBundler(array("type"=>"js")); // create bundler for JavaScript files
	$jsBundler->addFile("/scripts/myScript.js"); // add single file to bundle
	$jsFiles = array("/scripts/otherScript.js", "/scripts/yetAnotherScript.js");
	$jsBundler->addFiles($jsFiles); // add multiple files to bundle
	$jsBundler->writeBundle(); // create new (or reuse if existing) bundle
?>

This produces code that looks something like this:

<script src="/js/bundles/983254dcc0b4f8ed385c1bbca2a4944d.js" type="text/javascript"></script>

A single, compressed file is included making your visitors and your systems guy happy.  And also making your HTML lighter and neater.

As you’ve probably guessed the bundle name is a generated (encrypted) string which corresponds to the specific sequence of files which are contained in the bundle. This way, no two bundles are the same, and a single bundle never exists more than once.

While researching this topic I came across a few sites which provided useful information. Among them are Rakaz.nl’s entry on this topic, Paul Annesley’s post on the SitePoint Blog, as well as this post on CakePHP.nu. Thanks to Henry Belmont of JavaScriptr.com and Julien Lecomte (creator of the mighty YUI Compressor) for additional inspiration.