- Posted by liammclennan on April 24, 2009
Some time ago I wrote about optimising javascript and css file inclusion during the build process. This is necessary because our natural tendancy as developers, to neatly split these file into small modules with plenty of whitespace and commenting, result in unacceptable performance. It turns out that loading many small js files in a page has terrible performance characteristics and whitespace and commenting can slow things down considerably. I consider the technique described a success because the output was good from the performance point-of-view. Unfortunately, the developer experience was poor. There were a lot of steps required and it was easy to make a mistake and introduce a difference between the development environment and the production environment, which is high on my not-to-do list.
I am working on a new project and at the point where I need to think about optimising css and javascript again. This time I have gone with a slightly less optimal, but much simpler approach, based on conventions and adding extension methods to the UrlHelper class (this is part of Asp.Net MVC). Here is how it works.
In my web project I have the standard Content folder for storing resources. With Content I have created Scripts for javascript files and CSS for CSS files. Within Scripts and CSS I have created min folders, which will store the minified versions of the resources. The convention is that every js or css resource has a minified equivalent with the same name in the /min directory. The minified versions will be produced automatically during the build process.

During development I don't want to use minified files. I like to use Firefox to inspect js and css and minification makes this impossible. To make the loading of resources (js and css) choose between full and minified versions transparently I have implemented helper methods on the UrlHelper class. The methods are Script(...) and CSS(...).
public static class UrlExtensions
{
/// <summary>Returns the url for a javascript file. In production the files come from the min sub-directory.</summary>
public static string Script(this UrlHelper url, string filename)
{
return ResolveUrlForEnvironment(url, "~/Scripts/", filename);
}
public static string CSS(this UrlHelper url, string filename)
{
return ResolveUrlForEnvironment(url, "~/Content/CSS/", filename);
}
private static string ResolveUrlForEnvironment(UrlHelper url, string path, string filename)
{
if (!IsProduction()) return url.Content(path + filename);
return url.Content(path + "min/" + filename);
}
private static bool IsProduction()
{
return (HttpContext.Current != null && !HttpContext.Current.IsDebuggingEnabled);
}
}
At runtime the application determines if it is in a 'production' environment by checking the IsDebuggingEnabled property. This corresponds to the debug attribute on the compilation element in web.config. If it is production then the helpers return the paths to the minified files, otherwise the path to the full files is returned. These helpers are used like this:
<link href="<%= Url.CSS("Site.css") %>" rel="stylesheet" type="text/css" />
<script src="<%= Url.Script("jquery-1.3.2.js") %>" type="text/javascript"></script>
To make this strategy work the build process has to: set the debug="true" attribute in web.config, and produce the minified js and css files. The minification can be done with packer and js min as described in my previous post. Changing the web.config can be done using NAnt or MSBuild.
To tackle the problem of many http requests to fetch js and css I plan to use the lo-fi approach of limiting the number of files. I will try to group my css and javascript so that there is roughly one file of each type loaded per page loaded in my application. This means that I will have many javascript 'classes' per javascript file. It is not ideal but it is simple and performant.
Overall I am much happier with my new approach. It is an acceptable compromise that gives me a much simpler model to work with, and I think that simplicity is something that we do not value high enough.