Cache Busting with Gulp

The issues:

  • We switched from using Bundles to Gulp for CSS & JS
  • Best practice is very long expiry dates on static content (multiple years)
  • Without a cache busting strategy website users will be seeing broken content
  • Most of the time you won't even notice the issue, but website users will
  • We had a manual process
    (spoiler alert: manual processes don't work

The perfect solution:

An automated system for invalidating cache at build time

 

No manual involvement

 

If the file hasn't changed the user's cache is kept

 

Not adding anything except CSS/JS/Image changes to commit history

 

Not querystring based but filename based

Gulp-Rev

This will take one or more files and create a copy of those files including a hash of the file in the filename.

e.g. /css/site.css becomes /css/site-273c2cin3f.css

 

​Hashes are important because if the file hasn't change the version hash will stay the same

 

It will then create a JSON manifest file listing the changes:

{
  "css/site.js": "css/site-273c2cin3f.css",
​  "js/site.js": "js/site-d41d8cd98f​.js"
}

Version 1 - Pure Gulp

A pure Gulp solution using gulp-rev, gulp-rev-delete and gulp-rev-replace

 

Got us 80% of the way there but​ .cshtml files have to be updated whenever CSS changes

var gulp = require('gulp'),
    bower = require('gulp-bower'),
    sass = require('gulp-sass'),
    sourcemaps = require('gulp-sourcemaps'),
    concat = require('gulp-concat'),
    uglify = require('gulp-uglify'),
    rev = require('gulp-rev'),
    revdel = require('gulp-rev-delete-original'),
    revReplace = require('gulp-rev-replace'),
    filter = require('gulp-filter');

gulp.task('css', function () {
    return gulp.src('./gulp/scss/**/*.scss')
        .pipe(sourcemaps.init())
        .pipe(sass({ outputStyle: 'compressed' }))
        .pipe(sourcemaps.write('.'))
        .pipe(gulp.dest('./css'))
        .pipe(filter('**/*.css'))
        .pipe(rev())
        .pipe(revdel())
        .pipe(gulp.dest('./css'))
        .pipe(rev.manifest())
	.pipe(gulp.dest('./css'));
});

gulp.task('revreplace', ['css'], function () {
    var manifest = gulp.src("./css/rev-manifest.json");

    return gulp.src('./views/shared/stylesheets.template.cshtml')
	.pipe(revReplace({ manifest: manifest, replaceInExtensions: ['.cshtml'] }))
	.pipe(gulp.dest('./views/shared/stylesheets.cshtml'));
});

Version 1.1 - Gulp

A mixed Gulp and C# solution to do the revisions at build time but pick up the manifests at run time.

var gulp = require('gulp'),
    bower = require('gulp-bower'),
    sass = require('gulp-sass'),
    sourcemaps = require('gulp-sourcemaps'),
    concat = require('gulp-concat'),
    uglify = require('gulp-uglify'),
    rev = require('gulp-rev'),
    revdel = require('gulp-rev-delete-original'),
    filter = require('gulp-filter');

gulp.task('css', function () {
    return gulp.src('./gulp/scss/**/*.scss')
        .pipe(sourcemaps.init())
        .pipe(sass({ outputStyle: 'compressed' }))
        .pipe(sourcemaps.write('.'))
        .pipe(gulp.dest('./css'))
        .pipe(filter('**/*.css'))
        .pipe(rev())
        .pipe(revdel())
        .pipe(gulp.dest('./css'))
        .pipe(rev.manifest())
        .pipe(gulp.dest('./css'));
});

Version 1.1 - C#

Install-Package Gibe.CacheBusting
<section name="cacheBusting" type="Gibe.CacheBusting.Config.CacheBustingSection, 
  Gibe.CacheBusting" />

<cacheBusting>
    <manifests>
        <add path="/css/" file="~/css/rev-manifest.json" />
        <add path="/js/" file="~/js/rev-manifest.json" />
    </manifests>
</cacheBusting>
<link rel="stylesheet" href="@Url.Asset("/css/site.css")" />

Will end up like:

<link rel="stylesheet" href="/css/site-273c2cin3f.css" />

Now you can use it like this:

Version 1.1

Cached so it loads once on site load and then keeps the manifest in memory

 

Uses a dictionary to do the lookup internally so is super fast lookup

 

If the entry isn't found returns the URL that was passed in

 

Monitors the file system so if a manifest file changes then it loads the new version

Version 1.2

A mix of the 2:

 

Static images (sprites etc) are changed and the CSS is updated using gulp-rev-replace

 

CSS and JS are updated using gulp-rev but the URLs are using the Gibe.CacheBusting library

Thanks

https://github.com/sindresorhus/gulp-rev

https://github.com/jamesknelson/gulp-rev-replace

https://github.com/Gibe/Gibe.CacheBusting

Made with Slides.com