Webpack and WordPress 💙

I’ve recently made the move from using Grunt and Gulp to build my front end to using Webpack instead. It’s been a steep learning curve as Webpack is a whole different beast. Getting started can be quite daunting, but once you’ve played with it a bit it all starts to make sense.

Webpack has given me access to a lot of useful tools, so I’ve put together a boilerplate WordPress theme which can be used as a starting point for new projects. I’ve released it on GitHub, and here I’ll take you through the different parts.


First up, for the initial version, I had a few goals in mind.

Hot Module Replacement

Right at the top of my list was Hot Module Replacement. This nifty little feature will appropriately update or refresh the browser as I work on files. For example, if I change some Sass it will update the page without the need for a full reload. That saves time. Cleverly, if I change something that can’t be updated like that the page will be refreshed. Neat!

Webpack! Sassy!

I like to write in Sass but browsers don’t work with it natively. It will need to be compiled into CSS.

In the same vein, Webpack can help me with autoprefixing — after it’s compiled my Sass to CSS it can add the prefixes I’ll need automatically so I don’t have to.

Tower of Babel

ES6 (and its descendants) bring lots of nice new features to JavaScript. To me, it makes JavaScript feel like a more mature language. That’s great! But certain browsers (no prizes for guessing which) don’t support it. Thankfully transpiling saves us here. I can write in ES6 and have Webpack convert it to older JavaScript, using Babel, making it compatible with older browsers.

Bust That Cache

I spend a lot of time optimising for speed — a quick site matters. One of the most fundamental ways of helping a site load quickly is by leveraging browser caching so that a user’s browser saves a copy of site assets, like CSS. Doing so means the browser doesn’t have to download the cached files for every request. This is great until you make a change and the user is left with an out-of-date file.

To work around that I’ll generate a manifest file that relates a file to its most recent version. I’ll go into it in detail later, but in a nutshell, you’ll have some JSON like this:

  "css/style.css": "path/to/current/css/style.abc1234d.css"

You can use that to have your theme queue up the latest version of the file, which has a hash appended to its filename so the latest files are always downloaded.

Automatically Generate Favicons

You need about a million favicons for a modern site. These are for the various browsers and devices that can access them. I don’t want to have to generate them all so I’ll have Webpack do the heavy lifting for me. It will take one source file, generate all the versions needed from that and then spit out the HTML needed to link to them. All I have to do is include it in my theme.

Minify All The Things

All the generated CSS and JavaScript should be minified to make it as small as can be. Whilst that’s happening we should generate sourcemaps so the compiled versions of our sources can be debugged easier. Let’s get started.


The Webpack parts of the boilerplate are split up into a few files to keep things logically separate. I’ve linked to the files below as they were when I wrote this article. They may have been updated since, but I’ll keep commits helpful and everything commented so you can see what’s changed, and why.


When developing WordPress I use PHP’s inbuilt server to serve the website, and Webpack Dev Server to serve the assets. The development configuration assumes you do the same, so if you work differently you will need to fiddle with some settings.

The command I’m running for the PHP server, which should be executed in the same directory as wp-config.php, is:

$ php -S localhost:8000


Let’s start with the development environment.

To get Hot Module Replacement working I use Webpack Dev Server. The configuration looks like this:

const configureDevServer = () => {
  return {
    contentBase: path.join(__dirname, 'dist'),
    host: 'localhost',
    hot: true,
    inline: true,
    overlay: true,
    port: 8080

The host and port options specify some details about where we’ll be hosting from. In this instance that will be http://localhost:8080. Note the port number - this is important. The Webpack Dev Server must not conflict with the PHP server.

We configure the contentBase that our assets will be served from. In this case, it’ll be the dist folder. When combined with the host set-up we’ll find our assets at http://localhost:8080/dist/.

The hot option is what enables the Hot Module Replacement feature and inline lets us toggle how the HMR happens.

Finally, overlay enables an error overlay on the page when a mistake is made.

Now we can bring it all together. We pass the devServer option the function we just wrote to configure it and add the webpack.HotModuleReplacementPlugin plugin to the export.

module.exports = {
  devServer: configureDevServer(),
  // ...
  plugins: [new webpack.HotModuleReplacementPlugin()]

The last bit to be aware of is the rules that match images; we don’t do anything fancy with them in development, so we just make sure they’re available with file-loader:

  test: /\.svg$/,
  use: {
    loader: 'file-loader',
    options: {
      name: '[name].[ext]',
      outputPath: path.join(__dirname, '/dist'),
      publicPath: 'http://localhost:8080/'

You may need to add some additional matches for all the images you use. Here’s an example of how to do that:

  test: /\.(svg|png|jpe?g)$/,
  // ...


To start the dev server, hop onto Terminal and navigate to the theme. Once you’re there run:

$ npm start


The dev environment is pretty light, this is deliberate - we don’t want to wait five minutes every time we change one line of Sass. Production, however, has a lot more going on. Let’s dive in.

Compiling Sass and Processing CSS

First-up we tell Webpack what to do with our Sass files. Remember that Webpack loaders get executed from bottom to top.

  test: /\.s[c|a]ss$/,
  use: [
    { loader: MiniCssExtractPlugin.loader },
    { loader: 'postcss-loader' },

sass-loader is what compiles our Sass to CSS. It uses node-sass to do this, which we include in our project dependencies. postcss-loader then runs PostCSS, which in this case adds prefixes to the CSS rules. css-loader handles any imports and MiniCssExtractPlugin writes everything to a file.

Transpiling ES6 JavaScript to ES5

As I mentioned before, Babel takes care of converting the modern JavaScript code to it’s older equivalent. It too is configured using a function that returns an object.

const configureBabel = (browserList) => {
  return {
    loader: 'babel-loader',
    options: {
      presets: [[
        '@babel/preset-env', {
          modules: false,
          useBuiltIns: 'usage',
          targets: { browsers: browserList }
      plugins: [[
        '@babel/plugin-transform-runtime', { 'regenerator': true }

First up, the function takes a list of browsers to support. We’ll supply the list from the browserlist option in package.json.

More on that in a minute.

We use babel-loader to make Babel and Webpack play nice together, and pass it some options to define how it should work. Babel has a number of presets that let us easily define what Babel should do without having to write lengthy configs and figure out what plugins we need. In WordPress Boilerplate I’m using @babel/preset-env which lets us pass an array of targets and then it works out what needs to happen.

Let’s look at the options and what they’re doing.

  • modules: false tells the preset not to transform modules to different types.
  • useBuiltIns: ‘usage’ tells Babel to include polyfills as required. Basically, if you use a JavaScript Promise it will include the corresponding polyfill. If we don’t it won’t.
  • targets: { browsers: browserList } is the really cool part, in my opinion. It takes a browserlist query and decides what needs to be done to the JavaScript source to meet the requirements that have been defined. It’s seriously clever.

Finally, we add a single plugin: babel-transform-runtime, which reduces file size by ensuring polyfills are only included once. It also helps avoids conflicts on browsers that don’t need the included polyfills at all.

Manifest Files

Webpack can automatically generate Manifest files of all our assets for us. Using these, we can automatically bust caches by appending a hash to the names of the files we generate. Here’s a short example of how that works. Take this manifest.json file:

  "css/site.css": "/wp-content/themes/wordpress-boilerplate/dist/css/site.5482fa8e.css"

When we enqueue our CSS (or JavaScript etc.) we can query this file and use the latest files. I’ve written a handy PHP function for this. You can see it in use in the enqueues.

So, every time we enqueue site.css the site.5482fa8e.css file will be used and browser caching can do its job. When the file changes the hash will change. This gets updated automatically in the manifest file and the new file gets loaded by the browser.

The manifest itself has only a little configuration going on.

const configureManifest = () => {
  return {
    map: (file) => {
      if (file.name.includes('/') || file.name == 'webapp.html') {
        return file

      let extension = file.name.split('.').pop();
      if ('map' === extension) {
        extension = 'js'

      file.name = `${extension}/${file.name}`;
      return file

The map option takes a callback. Essentially, without the callback the JavaScript, JavaScript source map and CSS (but not the CSS map, annoyingly) will not have a directory in their key:

  "style.css": "/dist/css/style.css"

The function will add prepend the css/ directory to the key in the example above. It’ll work without it, too, but I like to keep things consistent.

So Many Favicons

I may have exaggerated a touch earlier when I said millions of favicons are needed. This will actually generate 45 from one source image. WebAppWebpackPlugin generates the favicons needed and HtmlWebpackPlugin generates the HTML we need to include them.

const configureWebApp = () => {
  return {
    cache: false,
    favicons: {
      appName: process.env.APP_NAME,
      developerName: process.env.DEVELOPER_NAME,
      developerURL: process.env.DEVELOPER_URL
    inject: 'force',
    logo: process.env.LOGO_PATH

We’re populating the values from the .env file. Once WebAppWebpackPlugin has done its job, HtmlWebpackPlugin generates the HTML for us.

const configureHtmlWebpack = () => {
  return {
    templateContent: '',
    filename: 'webapp.html',
    inject: false

We tell the plugin to produce a file called webapp.html, which will be placed in the /dist folder. We can use it in our theme like so:

if ( ! WP_DEBUG and file_exists( __DIR__ . '/dist/webapp.html' ) ) {
  echo file_get_contents( __DIR__ . '/dist/webapp.html' );

All The Small Things

CSS minification is taken care of by OptimizeCSSAssetsPlugin, and JavaScript is worked on by Terser.

const configureJavaScriptOptimisation = () => {
  return {
    cache: true,
    parallel: true,
    sourceMap: true

We have Terser use parallel processes to build the JavaScript to significantly reduce the build time required.

Mopping Up

Well, that turned into a bit of a deep dive. I’ve found this setup to be a good starting point for my WordPress themes. There are a few bits I want to add, the most notable being modern and legacy builds and code splitting. I’ll get those added in the future and write another post about them then.

Remember — check out the GitHub repo for the most up-to-date release.