Skip to content
Daniel Shaw · WordPress & WooCommerce Developer Wellington, New Zealand

👋 Not available for new client work right now!

How to create and retrieve cache-busting filenames for static assets in WordPress

Admission: I’ve never had a workflow in place for developing WordPress websites where static assets can be set to expire in the far future without the worry of how to serve an updated version.

I would guess and cherry-pick assets with a high probability of remaining unchanged, telling browsers to assume they would never expire, while manually versioning “likely to change” files with a query string (and, yup, I’m aware of the ancient 2008 prophecy with regards to URLs using query strings).

In actuality, this hasn’t been as horrific as it sounds, largely due to the type of sites I tend to end up working on: highly-customised WordPress websites that are treated as set-and-forget by clients as soon as they’re launched. Regardless: so sorry.

But! I recently worked on a new site using Hugo, treating it as an opportunity to quash some bad practices. Of course, someone had already solved and typed up their sweet solution for cache-busting static assets for Hugo. This got me thinking about how to implement something similar in WordPress.

To keep things simple, let’s deal with a single asset: the WordPress theme’s stylesheet.

The WordPress way

Every WordPress theme expects a file named style.css, and it’s one of the few files that cannot be omitted from a theme directory. This file performs two tasks:

  • It serves as a known location within WordPress for the theme’s stylesheet.
  • It contains a header which is used to provide useful details about the theme: name, author, version; and so on.

This means any theme directory serving an alternate file containing the theme’s style rules should still contain a style.css file. As you’ll see in the PHP function below, the default style.css will serve as a fallback in case of a missing or mis-matched hashed filename.

Creating a hashed filename

This part is really down to what you’re using for tooling: Grunt; gulp; webpack; that new one everyone is using that I’ll find out about in a few months’ time.

For example, the following webpack config generates a CSS file called style.[contenthash].min.css to dist/css/, where [contenthash] is a unique string. A JSON hash map in /dist will be used by the PHP function below to map the correct hashed filename to style.css.

const path = require('path'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const WebpackAssetsManifest = require('webpack-assets-manifest'); module.exports = { entry: './src/sass/index.js', output: { style: path.resolve(__dirname, 'dist') }, plugins: [ new MiniCssExtractPlugin({ filename: 'css/[name].[contenthash].css' }), new WebpackAssetsManifest() ], module: { rules: [ { test: /\.scss$/, use: [ MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader' ] } ] } }

Regardless of what is used, the following needs to be generated:

  • A version of style.css with a content hash as part of the filename.
  • A JSON object with the key as style.css and the value as the hashed filename.

Additionally, you’ll need new locations for the new files. In the following example I’ll use “data” and “dist”:

  • data/ contains the JSON hash map.
  • dist/ contains the hash-appended files.

Using the JSON hash map

PHP time! Here’s a function to place in functions.php that will perform the work of retrieving a hashed filename, if it exists.

/** * Serve theme styles via a hashed filename instead of WordPress' default style.css. * * Checks for a hashed filename as a value in a JSON object. * If it exists: the hashed filename is enqueued in place of style.css. * Fallback: the default style.css will be passed through. * * @param string $css is WordPress’ required, known location for CSS: style.css */ function get_path_css( $css ) { $map = get_template_directory() . '/data/css/hash.json'; static $hash = null; if ( null === $hash ) { $hash = file_exists( $map ) ? json_decode( file_get_contents( $map ), true ) : []; } if ( array_key_exists( $css, $hash ) ) { return '/dist/css/' . $hash[ $css ]; } return $css; }

How it works

  • When the function is called a value of style.css is passed in.
  • A value for $hash is set if it doesn't exist already (as a static variable its value persists, so the check for a null value ensures the value is only set one time).
  • The retrieved JSON hash map is converted to a PHP array; if nothing is retrieved an empty array is set.
  • array_key_exists() is used to check the existence of the function's passed in value—style.css—as an array key, and, if it exists, the value for this key is returned.
  • Otherwise, the default style.css is returned.

Serving the hashed filename

Calling the function occurs as part of the enqueuing process for style.css. get_path_css() hijacks the second parameter of wp_enqueue_style in order to dynamically determine the correct path.

/** * Enqueue the stylesheet. */ wp_enqueue_style( 'theme-styles', get_stylesheet_directory_uri() . get_path_css( 'style.css' ) );

Fallback

If there's a mismatch in filenames or the file simply doesn’t exist, the default style.css will be enqueued.

π