Go Back

How to automatically enqueue JavaScript and CSS from webpack

Posted: 

This odyssey started when I needed to get React working with an existing WordPress site. The site had a webpack build but when I tried to add a mini-React app it just wouldn't work.

I did a little sleuthing and found that React just never ended up being enqueued with WordPress so of course it wouldn't render.

How do I enqueue files I don't know what they're called?

I dug a little deeper to find that the current way that JavaScript and CSS from the webpack build was being enqueued with WordPress was using a plugin called wordpress-enqueue-chunks-webpack-plugin. It would essentially create a manifest file that had all the resulting bundled JavaScript and CSS files and their relationships. This was then embedded directly in a PHP file that was dynamically generated which you would include in your functions.php. Kind of ingenious really.

But in our case the plugin had be modified for reasons that were before my time and the plugin hasn't been updated in almost 5 years. Time for a change.

Embracing the Manifest File Concept

I really like this idea of a manifest file that listed all the resulting JavaScript and CSS files and their relationships. Once we had something like that it would be fairly easy to whip up a script to determine what should be enqueued for any particular WordPress template or page.

Did a little research and ran into this plugin that is actually mentioned on the official webpack site. webpack-manifest-plugin

This plugin gives us the power to output a manifest file that will give the output filenames, including hashes if relevant, as well as their dependencies. This is great!

The manifest plan

The goal is to have each webpack entrypoint correspond with something in WordPress. It might be a template, a page, whatever. But someway we can just enqueue based on that entrypoint and have all the resulting dependence JavaScript and CSS files be automatically enqueued.

Doing it this way means we can take advantage of all of the advanced code-splitting that webpack offers without having to manage it ourself.

Webpack config entry points

We start with the webpack config entry points. For our example I'm just going to list 2 entry points for simplicity.

{
main: "./src/main.js",
home: "./src/home.js"
}

SplitChunks

Now we configure the splitChunks so that the resulting bundles will be broken up in an efficient way to speed loading in the browser. That means for each entry point we could end up with 2, 5, 10 or who knows how many chunks based on what the content is. The two entry points may even share chunks. The beauty splitChunks is we don't have to care how it does it, we'll get all the dependent files in the manifest.

splitChunks: {
chunks: 'all'
}

There is a whole lot of customization we could get into, but that's not the focus of this blog.

WebpackManifestPlugin

Now we need to configure the manifest plugin.

new WebpackManifestPlugin({
generate: (seed, files, entries) => {
return generateManifest(entries, "dist");
},
})

This is where my knowledge starts to grow thin a little. We're using the generate function of the WebpackManifestPlugin because the default manifest output file doesn't break the files up by entry point.

WebpackManifestPlugin generate function

With the generate function we get entries which basically is what we're looking for, each final output file grouped by entry point. However, we want to still modify this slightly. That is where the generateManifest function comes in. I broke it out into a separate function for clarity.

Here is the generateManifest function.

/**
* Make sure all output files have a hash otherwise this will break
*/
function generateManifest(entrypoints, basePath) {
// reduce allows us to take the existing entrypoints and modify it
const newEntrypoints = Object.entries(entrypoints).reduce((acc, [entry, files]) => {
// make sure this entry point has been added to object
if (!(entry in acc)) {
acc[entry] = [];
}
/**
* map over the filename to break it up into more detailed information
*
* filename: the full filename with basePath
* name: handle based on the entrypoint name
* hash: content based hash
* extension: to identify type of file
*
* */
acc[entry] = files.map((file) => {
// Use regex.exec to capture the different parts of filename
const regex =
/^(?[^-]+(?:-[^-]+)*)-(?\w+)\.(?\w+)$/;
const filenameParts = regex.exec(file).groups;
return {filename: [basePath, file].join("/"), ...filenameParts};
});
return acc;
}, {});
return newEntrypoints;
}

It has to make an assumption about the file output having a hash. I spent a huge amount of time, mostly with ChatGPT trying to perfect that regex to give me the different parts of the filename but couldn't quite perfect it both having and not having the hash. So for now the hash has to be there.

We also might be able to loop through the files that the generate function gives us, but I couldn't quite make sense of it. As well, the entries were so close to what I needed I decided to just use that.

All that aside, let me go through this function.

Reformatting the manifest file for WordPress

We're breaking out the filename into different parts, mainly so we can distinguish the name of the script that we'll use to alias it in WordPress, and the type of the file, so we can use either wp_register_style or wp_register_script.

The resulting manifest file will be written and look something like this:

{
"main": [
{
"filename": "build/main-9255f05c9fb450911385.css",
"name": "main",
"hash": "9255f05c9fb450911385",
"extension": "css"
},
{
"filename": "build/main-9255f05c9fb450911385.js",
"name": "main",
"hash": "9255f05c9fb450911385",
"extension": "js"
}
],
"home": [
{
"filename": "build/home-9255f05c9fb450911385.css",
"name": "home",
"hash": "9255f05c9fb450911385",
"extension": "css"
},
{
"filename": "build/home-9255f05c9fb450911385.js",
"name": "home",
"hash": "9255f05c9fb450911385",
"extension": "js"
}
]
}

I think I'll leave it at that for today. I'll write another blog that will cover how to take this file in WordPress and use it to register and enqueue only the JavaScript and CSS you need for any given situation.