WordPress WP Enqueue Dynamic Files

Asset pipeline tools allow users to generate two types of files; standard development build files and production build files which are optimized. The production files can be generated using a unique name for each build. Why have unique names? Cache Busting!

Development build file:

wp_enqueue_script( 'my_custom_plugin', MY_CUSTOM_PLUGIN_URI . 'assets/dist/my_custom_plugin.js' );

What happens when our webpack, gulp or rollup asset pipeline tool spits out unique names for each version? How do I enqueue a unique name?

Production build files:

wp_enqueue_script( 'my_custom_plugin', MY_CUSTOM_PLUGIN_URI . 'assets/dist/my_custom_plugin_cjdalfehi339auch4jalfdak.js' );

First thought is to use the "cache busting" version parameter in the wp_enqueue_script function and don't uniquely name your production build files. It sounds great in practice, but sometimes that just does not work, especially when dealing with a CDN. I also like the "cache busting" builds because you know 100% the file is not cached at the initial server, the CDN, a service worker or the browser's many levels of cache (cough Firefox Quantum cough).

So how DO you load dynamically named files using WordPress?

With a custom function!

wp_dynamic_enqueue uses the php glob function to search a pattern of file names. glob is a pattern matching function and does not use regex!

The new function takes a few parameters:

  • $path: The file path on the server. Must be a full path!
  • $glob: The glob pattern used to match files found in the path.
  • $options: An array of stdClass objects used to set parameters for the WordPress wp_enqueue functions.
function wp_dynamic_enqueue($path, $glob, $options)
{
    $files = glob("$path/$glob", GLOB_BRACE);
    $css_count = 0;
    $js_count = 0;

    foreach($files as $key => $file) {

        $filename = basename($file);
        $option_type = strpos($file, '.css') ? 'css' : 'js';

        if( isset( $options[ $option_type ] ) && is_object( $options[ $option_type ] ) ) {
            switch( $option_type ) {
                case 'css':
                    wp_enqueue_style(
                        $options[ $option_type ]->handle . ( $css_count ? "_$key" : '' ),
                        $options[ $option_type ]->src . $filename,
                        property_exists( $options[ $option_type ], 'deps' ) ? $options[ $option_type ]->deps : [],
                        property_exists( $options[ $option_type ], 'var' ) ? $options[ $option_type ]->ver : '0',
                        property_exists( $options[ $option_type ], 'media' ) ? $options[ $option_type ]->media : 'all'
                    );
                    $css_count++;
                    break;
                case 'js':
                    wp_enqueue_script(
                        $options[ $option_type ]->handle . ( $js_count ? "_$key" : '' ),
                        $options[ $option_type ]->src . $filename,
                        property_exists( $options[ $option_type ], 'deps' ) ? $options[ $option_type ]->deps : [],
                        property_exists( $options[ $option_type ], 'var' ) ? $options[ $option_type ]->ver : '0',
                        property_exists( $options[ $option_type ], 'in_footer' ) ? $options[ $option_type ]->in_footer : false
                    );
                    $js_count++;
                    break;
            }
        }
    }
}

Detailed Example usage:

wp_dynamic_enqueue( 
    // Full filepath to assets
    MY_CUSTOM_PLUGIN_URI. 'assets/dist',
    // Glob pattern used to match files - only supports js and css files. 
    'my_custom_plugin_*.{js,css}',
    // Options for loading the different types of files, js and css
    // Similar to wp_enqueue_script and wp_enqueue_style
    [
        // options for css files. casted into an stdClass
        'css' => (object) [
            'handle'    => 'my_plugin_css',
            'src'       => MY_CUSTOM_PLUGIN_URI . 'assets/dist/', // public uri source path
            'deps'      => [], //not required, dependencies for css file.
            'ver'       => '0.0.2' //not required
            'media'     => 'all'
        ],
        // options for js files. casted into an stdClass
        'js'  => (object) [
            'handle'    => 'my_plugin_js', // public uri source path
            'src'       => MY_CUSTOM_PLUGIN_URI . 'assets/dist/', // public uri source path
            'deps'      => ['jquery'], //not required
            'ver'       => '0.0.3', //not required
            'in_footer' => true //not required, only used on js files.
    ]
] );

Basic Example usage for only js files:

wp_dynamic_enqueue( 
    MY_CUSTOM_PLUGIN_URI. 'assets/dist',     
    'my_custom_plugin_*.js',        
    [        
        'js'  => (object) [
            'handle'    => 'my_plugin_js',
            'src'       => MY_CUSTOM_PLUGIN_URI . 'assets/dist/',
            'deps'      => ['jquery'],
    ]
] );

The above example will load any javascript file loaded in the custom plugin assets/dist folder. If for some reason the folder contains two files which match the glob pattern the function will load both files and add the unique key to the end of the handle.

What about manifest.json?

Most modern asset pipelines have the ability to generate a manifest.json file. The manifest is usually used for the browser but it can be used to load dynamic files.

The starter theme by Roots, Sage uses the manifest to load generated build files.

Sage uses a php class to load the assets key from the manifest file.

/**
 * Class JsonManifest
 * @package Roots\Sage
 * @author QWp6t
 */
class JsonManifest implements ManifestInterface
{
    /** @var array */
    public $manifest;
    /** @var string */
    public $dist;
    /**
     * JsonManifest constructor
     *
     * @param string $manifestPath Local filesystem path to JSON-encoded manifest
     * @param string $distUri Remote URI to assets root
     */
    public function __construct($manifestPath, $distUri)
    {
        $this->manifest = file_exists($manifestPath) ? json_decode(file_get_contents($manifestPath), true) : [];
        $this->dist = $distUri;
    }
    /** @inheritdoc */
    public function get($asset)
    {
        return isset($this->manifest[$asset]) ? $this->manifest[$asset] : $asset;
    }
    /** @inheritdoc */
    public function getUri($asset)
    {
        return "{$this->dist}/{$this->get($asset)}";
    }
}

View on Github

I highly recommend following the Roots team on twitter and using Sage (currently 9) for your next WordPress theme.

Thanks for reading. Code On.

Subscribe to our mailing list to receive updates about emerging tech trends, important security patches and really bad dad jokes.