If you're a conscientious web developer you make sure when you update static assets, like CSS and javascript, that your site forces a visitor to download a fresh copy with the updated code in it. This is otherwise known as cache-busting.
There are a few different ways to do this. There is file name, e.g. file.xxxx.css
, or file path versioning /xxxx/file.css
, and query string versioning, e.g. file.css?v=xxxx
.
Query strings are known to have caching issues with some CDNs. This was discussed as far back as 2008 by renowned web performance expert, Steve Souders, but the advice still holds true today.
I've been using Laravel Mix for a while now as part of my front-end build process for compiling static assets, and it has a version
function that can be used to create unique file names that are stored in a mix-manifest.json
file.
You can then reference those unique file names in your code if you're running a Laravel project with, for example, mix('css/filename.css')
. (We'll look at how you can do this on Craft CMS sites later.)
Mix's versioning drawback
The problem with using Mix's version
function is that since version 1, it outputs file names with query strings, not by altering the path or file name. There was some discussion last year about it being an option to be added to version 6.1, but it's not there yet.
So what do you do if you want file- or path-based versioning now? I took a look at this a while ago and didn't find a solution so moved on, but this week I was looking at it again, and found the answer on StackOverflow (as is often the case).
I had to pull a few different bits and pieces together to get this to work, so I thought it'd be worth putting it all in one place to share.
The process
First, we're going to use Mix's version
function which will populate a mix-manifest.json
file with something that looks like this:
I'm assuming you already have Laravel Mix installed and for my example, I'm also using the PurgeCSS wrapper for Laravel Mix.
The webpack.mix.js
file I'm using that creates the mix-manifest.json
file looks like this:
One thing to note about the above is that when you add the version
function to your script, you also need to add setPublicPath('public-folder')
, otherwise you'll get an error like:
UnhandledPromiseRejectionWarning: Error: ENOENT: no such file or directory, open '/public-folder/assets/js/file1.js'
With this webpack.mix.js
file, when we run npm run production
, you'll get output with query strings on the end of the file names.
But we want our file names to look something like this:
So we need to run a script that will convert those file names which comes from the StackOverflow question linked above which I modified a bit:
To be able to run that script, we need to add a new script to the package.json
file like so:
I renamed the original production
script to production:first
(although it could be anything), added a new rewrite-manifest
script and then use npm run production
to run them both sequentially.
This is done with run-s
which is a shorthand command provided by npm-run-all
which you'll have to install first with npm i npm-run-all
.
So we've now got the file names in mix-manifest.json
the way we want them. I don't generally work on Laravel projects, so if I was using Craft which I'm more likely to be using, I'd install the AssetRev plugin and add an assetrev.php
flle to my config
folder.
Then you call < link href="{{ rev('/_css/combined.min.css') }}" rel="stylesheet">
or < script src="{{ rev('/_js/app.min.js') }}">
in your templates.
The only thing left to do is to tell the server that when it encounters a file like app.x7ddjdj7x.min.css
, which doesn't exist, that it treats it like the x7ddjdj7x
part of the file name wasn't there and instead serves up app.min.css
which does exist.
How you do that will depend on your web server, but in my case, on Apache, I added the following to the .htaccess
file:
And that should be it. It's a few more steps than what I would generally like to have to make to achieve this result, but sometimes you just have to go that extra mile to achieve the best result.