Busting Cache With Grunt
If you aren’t using Grunt, you should start. It’s billed as “The Javascript Task Runner,” and it lives up to that in every way. You can automate almost anything. Last week, I wrote about using cache to speed up your site. Today, we’ll use Grunt to help bust that cache when we want to change a css file.
.htaccess
<IfModule mod_rewrite.c>
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.+)\.(\d+)\.(js|css|png|jpg|gif)$ $1.$3 [L]
</IfModule>
Ok, this may look like garbledegook (technical term) to you, so I’ll explain what I can.
First off, I took this straight from the HTML5 Boilerplate htaccess file. I highly recommend you check it out. There are some apache config gems in there.
What you are looking at is a rewrite rule to reroute all requests made to the above filetypes, when they have certain naming conventions. We’ll be renaming our files when we edit them with a unique number, so that’s what we’ll look for. For example, any request to style.1234567.css will reroute to style.css. By updating this number, we can force the browser to bust the cache setup on the file, and reset the time to live. So if cache on style.css is set to 365 days, and we update it after 20 days, it will again reset to 365 with the new updates. Now that we are busting cache, let’s use grunt to rename these files before we deploy.
Grunt Replace Setup
For the next step, I’m going to assume you have a working knowledge of Grunt setup. If not, read up on it here. I’ll wait.
In order to replace our css filename with a variable, I’ll be using the grunt-replace plugin. Cd to the root of your project (where you hopefully installed Grunt) and run the following:
npm install grunt-replace --save-dev
This will not only install the plugin, but will save it into your package.json file. This will let you update the plugin in the future via running npm install. Now that we’re setup, on to the…
Gruntfile
module.exports = function(grunt) {
// Project configuration.
grunt.initConfig({
// Replace object
replace: {
build_replace: {
options: {
variables: {
// Generate a truly random number by concatenating the current date with a random number
// The variable name corresponds with the same in our HTML file
'hash': '<%= ((new Date()).valueOf().toString()) + (Math.floor((Math.random()*1000000)+1).toString()) %>'
}
},
// Source and destination files
files: [
{
src: ['public/index.html'],
dest: 'public/build/index.html'
}
]
}
}
});
grunt.loadNpmTasks('grunt-replace');
// Default task, you can obviously name this whatever you like
grunt.registerTask('default', ['replace']);
};
To change our css names, we’ll first setup a unique number variable called hash to add to them. It’s important this number be totally unique, because if you reuse an old number that is cached, your browser may reference the cached file rather than your update:
'hash': '<%= ((new Date()).valueOf().toString()) + (Math.floor((Math.random()*1000000)+1).toString()) %>'
Then, we’ll setup a source and destination file. This will tell grunt-replace what file to look for the hash variable in our source file, and kick out a destination file. This is the file that will be pushed live, replaced with our random number.
Below is the code for our source file. The hash var is prepended with @@, and is what will be replaced with what we setup in our gruntfile.
HTML
<html>
<head>
<script src="build/index.@@hash.js"></script>
</head>
<body>
</body>
</html>
For this exercise, the replace task is setup as the default. This means that you can just run grunt on the command line, and it should be good to go. There are an infinite array of configurations you can add the replace task to. For my portfolio site, I’ve got grunt-replace wrapped into a build command I run, which also handles minification and uglification of assets.
Anywho, that’s a quick rundown of how you can use Grunt to bust cache. If you have another way of doing it, let me know! I’d love to hear it.
References
Comments (16)
Leave a Reply
Nice write up, Rob!
For cache busting, a hash of the file is a bit better than a true random number. If a user visits your site on Monday, you make changes to the CSS on Tuesday, revert those changes on Wednesday, and the user visits again on Thursday, the CSS in their cache and the CSS on your site is identical and they shouldn’t need to download it. Tying the cache busting string to the contents of the file and not the date lets that happen.
Link to commentGood point.. never thought of that.
But quite frankly.. if that would be the only benefit you get from it, I don’t think it’s worth the battery power to compute the hash 🙂
Link to commentRevving the references to styles and routing to plain files is cool idea. If you’re busting cache on a database driven site revving the files might work better, since your Grunt task won’t have any access to content referenced in the database. Grunt Rev is a task for that: https://github.com/cbas/grunt-rev.
And if you’re working on a static site you can do away with the htaccess rules entirely and use the Grunt Usemin (https://github.com/yeoman/grunt-usemin) and Grunt Rev combo to rev your assets and replace all references to them in your flat files. That’s how Yeoman does it — works great for flat file .js apps and generated static sites like Jekyll.
Link to commentI love that idea! I’m thinking this could become a guest post on the site…
Link to commentHa, quite possibly. Trade you for one when my site goes up.
Link to commentI’m game for that if you are!
Link to commentThanks for bringing the grunt-replace module to my attention i think it’s just what i need for solving my cache busting problem.
I’m attempting to cache bust AMD modules by adding an MD5 hash to them. This then meant i have to parse the compressed js and rewrite the filepaths to include the hashes. That plugin may be just the ticket!
Link to commentNo problem dude! Grunt replace should be just the ticket you need.
Link to commentI like the idea of using grunt-replace for cache busting. I reckon it’s more transparent and ‘controllable’ then fully automated solutions like grunt-rev.
Couldn’t you just add the hash as a get parameter like so:
That way you don’t have to worry about rewriting anything.
Link to commentQuerystring is not optimal – http://www.stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring/
Link to commentAh yes, the proxy conundrum. It’s an edge case, but worth accounting for. I use file renaming to solve this issue. You can read more on this post:
http://wp.me/p31cKW-mU
Link to commentI might be missing something very obvious, but I’m also revving my javascripts / css files using Grunt, so each time I deploy my app I see that v0.1 of my site had this
while the new v0.2 of my site now has this :
The problem however is that my index.html (the access point to my site) is still cached from time to time by the browser (sometimes when opening a new tab I get the fresh content, but I’ve noticed very often that opening a new tab showed the old content).
So instead of seeing the new scripts/671df76d.main it sees the old scripts/d7ba3e7b.main and picks it up from the cache.
As I cannot “rev” the index.html, how can I ensure that a fresh copy of the index.html is being served ?
Link to commentso v0.1 has this :
Link to comment<script data-main=”scripts/d7ba3e7b.main” src=”bower_components/requirejs/require.js”></script>
and v0.2 has this:
<script data-main=”scripts/671df76d.main” src=”bower_components/requirejs/require.js”></script>
I used no-cache headers on my web server to make sure index.html is always downloaded
Link to comment