Posted on The Distress Signal | web-development
If you’re setting long expires headers on your static assets—as God demands—then at some point, you will want to bust that browser cache. If you’re also using Jekyll, there’s a plugin for that. Here’s the thing though: not everyone needs a full-fledged, sprockets based, asset pipeline for their project. In the world of Jekyll—I’d actually say that the majority of folks don’t need that particular sledgehammer. It adds quite a lot of complexity to your build and, to be fair, the documentation is a bit inscrutable.
There’s nothing wrong with simple workarounds like this:
{%raw%}<link rel="stylesheet" href="/css/main.css?{{site.time | pubDate: '%s'}}">{%endraw%}
Which uses the site.time
variable formatted as seconds since the epoch (1970). When outputed, the ?1518881511
query paramater gets appended to the end of the filename—browsers see this as a new asset and download it. This is great, and will bust browser cache of the file everytime the site is built.
Ideally though, you’d only invalidate the browser cache when your asset has changed somehow. You could manually increment when changes occour using semantic versioning e.g. main.css?v=1.2
—but my Mtv addled brain would never remember to do that. Somewhere between a Rails asset pipeline and manually incrementing—lies a middle path. A custom plugin that reads in your _sass
files, geneartes an MD5 Digest—and then lets you append that digest to your css filname with a filter, e.g.:
{%raw%}<link rel="stylesheet" href="{{ site.baseurl }}/assets/css/{{ 'site.css' | bust_css_cache }}">{%endraw%}
Which will give us something like this in the markup: <link rel="stylesheet" href="/assets/css/site.css?c86a8588967580cc0631f5115d9d6b18">
. This way, the hash at the end only changes when the content of your Sass files change. A browser could hold onto your stylesheet for a year—but as soon as you make a change to the _typesetting.scss
partial, it will ask for the new version.
It’s not as complicated as it sounds. You can add a custom plugin by including an .rb file in the _plugins
directory at the root of your project. There are some caveats though, so first have a look at the documentation for running with custom plugins. Plugins can get pretty complicated, but for our purposes we just need to register a simple filter with Liquid.
module Jekyll
module CacheBust
# put the stuff that does stuff here
end
end
Liquid::Template.register_filter(Jekyll::CacheBust)
Basically, Liquid Filters take an input and return an output. In our case, our input will be the name of our CSS file, and the output will be the name of our CSS file with an MD5 Digest of our Sass files appended to it. I dropped the full plugin into this gist if you want to give it a look. The key bits are a CacheDigester
class that will take a file or directory of files and generate a digest, and the bust_css_cache
method which you call as a filter in Liquid.
We’re passing our _sass
directory to the CacheDigester
object because we write in Sass and our CSS file will only be generated at the end when the site is built. I keep my Sass files in assets/_sass
so if you plan to use this plugin you may have to change that bit in the bust_css_cache
method.
What I love about Jekyll, is what I love about Liquid, is what I love about Ruby, is what I love about Vue. They all do complex things, but offer simple affordances to the user. Extending the functionality of Liquid and Jekyll is relatively straightforward, as you can see above. This means you can build the right solution for your particular context—without being forced to use a sledgehammer to swat a fly.