Solving Browser Cache Hell With Gulp-Rev

Even if you control cache policy with server proxy, sometimes the only way you can guarantee that the client is caching your served files correctly is by changing it’s filenames… on every deploy.

This is called file revision, and here I’ll explain how to easily automate it with gulp.

“Everything is gonna be alright”, Dog says. Let’s just trust him for now.

The Problem

  1. Our t̶e̶a̶m̶ CI tool made up a deploy to the test environment
  2. Our costumer was unhappy because she couldn’t see the app changes
  3. We discovered that our costumer’s company were using an internal proxy which were ignoring our cache policies headers and storing our older version’s files ([*.js, *.html, *.css, *.svg]) of our app… forever

The Solution: Concept

We simply rename every html/js/img file served by our app by concatenating a random-hash before the file extension (i.e: jonh-doe.min.js would become john-doe-b3c929b2af.min.js).

After doing this, we update every reference for these newly-hashed files inside our app files and… voilá!

Our costumer browser won’t ever have the same hashed-file cached unless it is, in fact, the latest version being served.

We call it file revision.


The App: Quick Background

Our gulpfile has a compile task which… compiles js/pug/wathever files to a /dist directory.


The Solution: Implementation

Dependencies:

  • gulp, for automating our task
  • gulp-rev, for renaming our files with random hashes. In this example, imported as rev
  • gulp-rev-collector, for switching non-hashed references by hashed-references inside our files. In this example, imported as collect
  • rev-del, for deleting non-hashed files in our /dist folder. In this example, imported as revDel

Step 1: creating our revision:rename gulp task

This task’s pipeline will:

  • Get every compiled html, css, js and image file from our /dist folder
  • Generate & concatenate a hash on it’s name
  • Delete the previously compiled, unhashed file
  • Put the hashed file in the same place of it’s original, unhashed file
  • Generate a manifest.json (whose will be used in our next gulp task) that maps unhashed -> hashed filenames and put it in our /dist folder
gulp.task(“revision:rename”, [“compile”], () =>
gulp.src(["dist/**/*.html",
"dist/**/*.css",
"dist/**/*.js",
"dist/**/*.{jpg,png,jpeg,gif,svg}"])
.pipe(rev())
.pipe(revdel())
.pipe(gulp.dest(“dist”))
.pipe(rev.manifest({ path: “manifest.json” }))
.pipe(gulp.dest(“dist”))
);

A closer look to our manifest.json:

{
xpto.html: xpto-293820djdx.html,
charming.css: charming-ds9udjvci.css,
carreta-furacao.js: carreta-furacao-dijds9xc9.min.js,
anything.svg: anything-f9efdx8cv.svg
}

Step 2: creating our revision:updateReferences gulp task

This task’s pipeline will:

  • Point the path to our previously-generated manifest.json and a wildcard of files to look for unhashed references
  • Pass it to collect, which is expecting as a first parameter the path to a manifest.json and the following as paths of files to look inside
  • Rewrite every reference for every key of manifest.json to it’s respective value inside every html/json/css/js file (i.e: <link href=”charming.css”> would become <link href=”charming-ds9udjvci.css”>)
gulp.task(“revision:updateReferences”, [“compile”, “revision:rename”], () =>
gulp.src([“dist/manifest.json”,”dist/**/*.{html,json,css,js}”])
.pipe(collect())
.pipe(gulp.dest(“dist”))
);

Step 3: creating our compile:production gulp task

This task’s will just:

  • Depend on our actual compile task our front-end source files to the dist folder
  • Also depend on our newly created revision:rename & revision:updateReferences tasks
  • Be run just* in your CI tool’s deploy pipeline
gulp.task(“compile:production”, [“compile”, “revision:rename”, “revision:updateReferences”]);

*Because you don’t want to (and don’t need to) do file revision while coding locally, since it will take up more compilation time everytime a watched file is changed/compiled, decreasing your compiling performance.


There’s a whole lot more about web cache…

But these simple gulp tasks worked out for me and made my costumer happier. I hope it does work for you, too! :-)

I’d be happy to help & answer any questions on this subject. Just hit ‘reply’ if you need!


This post was written in Sorocaba, Brazil