Deploying cached CSS and JS with Ant

by Mike on February 9, 2009

If you are using YSlow or an equivalent tool you’re probably aware of some of the best practices to follow to optimize client-side load times. In this post I’m going to outline a quick and easy approach that we use at Planitax to deploy static content using Ant.
The best practices that I hope to address with this build strategy are…
1) Add an Expires or a Cache-Control Header
2) Minify JavaScript and CSS
Both of these are pretty straightforward, but it’s easy to run into problems when deploying updates to content that might have an expire date in the future. You might run into this problem when you make changes to js, css, or images in your application. If a browser has cached content locally, the user might see stale images, or worse yet experience errors due to out of date javascript or css files.
When we developed a deployment strategy we wanted an approach that was transparent to the end user, easy to build, and had minimal impact on developers checking in code. We needed to meet the following criteria.

Content should be able to have a Expire date at any point in the future

Content can be deployed to QA/Production at any time using a one step build

Developers should always be able to check into a single repository location. Branching/Deploying should not require a copy of a file to be checked in.

Developers should not be required to run a build target in order to see changes to static content.

js/css should be minified on production deployments, but not in the source code repository.

From these requirements we came up with a strategy that looked something like this.

Static assets that can be cached will always live in the /assets folder off the application root. Content will be organized into subfolders for /assets/js, /assets/css, and /assets/images.

Developers will work with expires headers turned off for these folders so changes will appear immediately.

Developers will check files in directly to this location. The repository location will always be /assets.

When deploying to QA/Production, a copy of the static assets will be created with a new version identifier. This copy will also be minified as part of the deploy step.

The application will be able to determine the correct copy of static content to use regardless of the deployment environment.

QA/Production web servers will automatically add an expires header to any content located in the /assets folder.

The first step was to build an ant target that would take care of creating a new folder, copying the assets and minifying content appropriately. We use JSMin to minify our JS. In order to execute this example you will need to download the JSMin Ant Task from Google Code. Note, when setting up your taskdef, the classpath attribute should point to the relative location that you have saved the .jar file. For this example it will be in the /lib subfolder off of root.

This code creates a folder with a name that looks something like this… ./assets/js200901141546 , it’s named this way so we can do alpha sorts easily as you will see in the next step. We don’t check this folder in to source control.
Note: You can adapt this code for css or images by modifying the copy command, if you only need to copy/minify JS you might be able to get by without the copy command.
The next step is to make your application aware of the correct folder to be used. This is easy to do with a cfdirectory based on the naming convention we used above. First we will create a variable that identifies the most recent/current folder created by the name that contains the timestamp. The nice thing about this is that it will identify the correct folder for prod servers as well as developers who only have a ./assets/js folders. It does not require developers to run the build target in order to see changes.

The only tricky part is to make sure that all your links to assets include the new variable that you have created. Your links will look something like this…

I’m sure there are countless ways to approach this, and your deployment strategy may differ significantly based on your environment, and or application. I’m interested to see how others have approached this and other deployment tasks.