A web design and development studio

A com­mon prob­lem web­site devel­op­ers run into when reg­u­lar­ly deploy­ing changes to their site is that the old­er ver­sions of the JavaScript, CSS and image files con­tin­ue to get served to users even after they’ve updat­ed them.

This is because the web­serv­er has told the users’ brows­er to cache those files local­ly for a long peri­od of time. Even though the devel­op­er has pushed new files to the serv­er, the brows­er does­n’t know they’ve changed (as the file names look exact­ly the same) so it con­tin­ues to use the old version.

There are a num­ber of ways to avoid this com­mon prob­lem but the rec­om­mend­ed approach is to gen­er­ate unique file­names of your assets. 

How does it work? You append a unique string to the file­name of the files you are serv­ing. So instead of just serv­ing styles.min.css, you add a dif­fer­ent string to the end of the file every time you change it, for exam­lple styles.min.a3h4sfveaac.css. Because the file­name is now unique, the brows­er will always request it from the serv­er instead of using the cache (as it’s nev­er seen it before).

But how does that string get gen­er­at­ed? You could just man­u­al­ly add the string, for exam­ple styles.min.v1.css styles.min.v2.css etc. but this is tedious and prone to error.

There’s a bet­ter way to do this using a hash func­tion. A hash func­tion reads the con­tent of a file and returns a unique string rep­re­sent­ing it. If any part of the files changes (for exam­ple if you removed a line of CSS) the result­ing string also changes. 

Using these is an easy way to gen­er­ate com­plete­ly unique file­names for assets and doing so will mean your brows­er nev­er loads an old file from its cache.

Exist­ing approaches

We’re not the first peo­ple to think of this. There are a num­ber of exist­ing solu­tions and approach­es to this prob­lem with Craft. There are a few plu­g­ins as well as best-prac­tise guides to fol­low. While these are great resources, they don’t quite tick all of the box­es for us.

Ver­sion­ing your assets should be an auto­mat­ic, trans­par­ent process that hap­pens as part of your deploy­ment pipeline. It’s not some­thing you should be man­u­al­ly han­dling dur­ing local devel­op­ment. Your ver­sioned files should not be a part of your Github repos­i­to­ry. The less involve­ment you have the better

Our Approach

Asset Ver­sion­er auto­mates this process by pro­vid­ing you with two things:

First, a CLI com­mand ./craft asset-versioner/scan that auto­mat­i­cal­ly scans your web fold­er and finds all of the files you are inter­est­ed in ver­sion­ing (JS, CSS, etc.). You run this as a part of your deploy­ment script.

    Sec­ond­ly a twig fil­ter {{ "styles.min.css" | version }} that you add to any files in your tem­plates that you want ver­sioned. You ref­er­ence the orig­i­nal” file name and Asset Ver­sion­er auto­mat­i­cal­ly finds the lat­est hashed file path.

    With these two com­po­nents, your ver­sioned files are auto­mat­i­cal­ly gen­er­at­ed and ref­er­enced from your tem­plates. This approach is envi­ron­ment-depen­dant. In oth­er words you can con­fig­ure this to hap­pen on stag­ing and/​or pro­duc­tion, but not local­ly. Asset Ver­sion­er also uses the Yii cache to store the map­pings from the orig­i­nal file­name to ver­sion. This means you don’t need to man­age a man­i­fest file but you get the per­for­mance benefits. 

    Instal­la­tion

    To get start­ed with Asset Ver­sion­er sim­ply install is as a plu­g­in via the Plu­g­in Store or use the com­mand line:

    composer install weareferal/asset-versioner
    ./craft install/plugin asset-versioner
    

    Bear in mind there is pur­pose­ly no Con­trol Pan­el set­tings page — every­thing is con­fig­ured via a con­fig­u­ra­tion file. This is by design

    Con­fig­u­ra­tion

    To con­fig­ure Asset Ver­sion­er, cre­ate a config/asset-versioner.php con­fig­u­ra­tion file. This allows you to enable/​disable the plu­g­in on a per-envi­ron­ment basis.

    We rec­om­mend run­ning Asset Ver­sion­er on staging and/​or production but not local.

    Here’s an example:

    <?php
    
    return [
        '*' => [
            'staticVersioningEnabled' => false,
            'assetVersioningEnabled' => false,
        ],
        'production' => [
            // Whether or not to enable versioning for static files specifically. 
            // Static files are the JS, CSS, png, jpeg, fonts ... that you use
            // part of your development workflow
            'staticVersioningEnabled' => true,
    
            // The extensions of the static files you are interested in being
            // hashed. In this example, we are only hashing JS & CSS files
            'staticVersioningExtensions' => 'css,js',
    
            // The name of the folder within your webroot to storge copied, versioned
            // files. This makes it easy to add this folder to your gitignore so that
            // generated version files are not in your repo.
            'staticVersioningPrefix' => 'versions',
    
            // Whether or not to enable versioning for uploaded Craft asset files.
            // This may or may not be something you need.
            'assetVersioningEnabled' => true,
    
            // The extensions of the asset files you are interested in being hashed.
            'assetVersioningExtensions' => 'png,jpg,jpeg,pdf'
        ]
    ];
    

    config/asset-versioner.php

    Usage

    The first step is to replace the files you want ver­sioned in your tem­plate. For example:

    <link rel="stylesheet" href="/css/styles.min.css" type="text/css" charset="utf-8">
    <!-- Replace with -->
    <link rel="stylesheet" href="{{ '/css/styles.min.css' | version }}" type="text/css" charset="utf-8">
    

    base.html

    Then all you need to do is add the fol­low­ing com­mand to your deploy­ment script.

    Here’s an exam­ple of a Forge deploy­ment script we’ve been using:

    #! /bin/bash
    
    # A simple script used to update a Craft site during deployment
    composer install --ignore-platform-reqs --no-interaction --optimize-autoloader
    ./craft migrate/all
    ./craft project-config/sync
    ./craft clear-caches/all
    ./craft asset-versioner/scan
    

    deployment.sh

    That’s it! Now, every time you deploy your code your tem­plates will be ren­dered with the lat­est ver­sioned filenames. 

    If you made it this far, you may also like

    We've launched two Craft CMS plugins

    We've launched two plugins

    Eager to fix the problems we see with the world, we've published two new Craft CMS plugins to the plugin store.