Using Webpack 4 and SASS with WordPress

Published

The idea of using Webpack as a bundler has been on my mind since launching this site. Originally I was relying on code minification plugins in WordPress, but began to see some unpredictable behavior (plus I was dying for the ability to use SASS to style my site). Incorporating Webpack gave me more control, the ability to work with SASS, and laid the groundwork for exploring code chunking in the future.

Installing and Initializing Webpack

I started in the root folder of my WordPress Theme (for me this was [WordPress-root]/wp-content/themes/taylor/). Once inside, I installed Webpack and Webpack CLI:

npm i --save-dev webpack webpack-cli

Then I initialized a new Node project:

npm init -f

With the auto-generated package.json file, I added three script options as shorthand for the Webpack build and watch commands. I differentiated between a development build and a production build because setting the build mode to “production” provides extra optimization inside of Webpack1:

{
  "name": "tcallsen_blog_wp_theme",
  "version": "1.0.0",
  "description": "WordPress Theme for Taylor Callsen's Blog",
  "scripts": {
    "build": "webpack --mode development",
    "dist": "webpack --mode production",
    "watch": "webpack --watch --mode development"
  },
  "author": "Taylor Callsen",
  "devDependencies": {},
  "dependencies": {}
}

Organizing the Source Code

How to organize code is absolutely a matter of preference, and I don’t think there is really any wrong way to do it as long as things feel clean and organized. For this guide, I created a “js” directory and a “css”directory inside of my theme’s root folder. Each of these directories had a “src” folder, which contained my source code, and a “build” folder, which acted as the build target for my Webpack compiled assets:

Sample directory structure for a WordPress Theme that uses Webpack to compile the JavaScript and SASS assets.

Configuring the JavaScript Build (with Babel and Minification)

I used a pretty standard JavaScript build configuration, and decided to incorporate Babel for maximum browser compatibility. Code minification was also plug-and-play using the uglifyjs-webpack-plugin.

To install the Babel and the code minification dependencies, I ran:

npm i --save-dev babel-core babel-loader babel-preset-env uglifyjs-webpack-plugin

Next, I created the Webpage config file – webpage.config.js – in my theme’s root folder with the following contents:

const path = require('path');

// include the js minification plugin
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');

module.exports = {
  entry: ['./js/src/app.js'],
  output: {
    filename: './js/build/app.min.js',
    path: path.resolve(__dirname)
  },
  module: {
    rules: [
      // perform js babelization on all .js files
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader",
          options: {
          presets: ['babel-preset-env']
          }
        }
      }
    ]
  },
  optimization: {
    minimizer: [
      // enable the js minification plugin
      new UglifyJSPlugin({
        cache: true,
        parallel: true
      })
    ]
  }
};

Configuring the SASS Build

Incorporating SASS compilation involved more trial and error, especially with some of the new Webpack 4 configurations. Webpack 4 introduced the mini-css-extract-plugin, which replaces the ever-popular extract-text-webpack-plugin2. Essentially this plugin allows Webpack to extract CSS code into its own individual file separate from the rest of the JavaScript code. In my build, I also included the optimize-css-assets-webpack-plugin to minify the compiled CSS:

npm i --save-dev sass-loader css-loader node-sass mini-css-extract-plugin optimize-css-assets-webpack-plugin

After installing dependences, it was time to make the following updates to my webpack.config.js file:

  1. Including the mini-css-extract-plugin and optimize-css-assets-webpack-plugin for use in our config
  2. Adding ./css/src/main.scss as an entry point
  3. Defining a new module rule to handle .sass and .scss files with the mini-css-extract-plugin and sass-loader
  4. Specifying the output file name in the plugins section
  5. Adding the optimize-css-assets-webpack-plugin as an additional minimizer

Here is the updated webpack.config.js file that contains both the JavaScript and SASS configurations:

const path = require('path');

// include the js minification plugin
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');

// include the css extraction and minification plugins
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");

module.exports = {
  entry: ['./js/src/app.js', './css/src/app.scss'],
  output: {
    filename: './js/build/app.js',
    path: path.resolve(__dirname)
  },
  module: {
    rules: [
      // perform js babelization on all .js files
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader",
          options: {
            presets: ['babel-preset-env']
         }
        }
      },
      // compile all .scss files to plain old css
      {
        test: /\.(sass|scss)$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader']
      }
    ]
  },
  plugins: [
    // extract css into dedicated file
    new MiniCssExtractPlugin({
      filename: './css/build/main.min.css'
    })
  ],
  optimization: {
    minimizer: [
      // enable the js minification plugin
      new UglifyJSPlugin({
        cache: true,
        parallel: true
      }),
      // enable the css minification plugin
      new OptimizeCSSAssetsPlugin({})
    ]
  }
};

Loading the compiled assets into WordPress

Now that Webpack was compiling the JavaScript and CSS into their respective build directories, the last step was to enqueue these assets into my WordPress theme. This required a few lines in my theme’s functions.php:

// register webpack stylesheet and js with theme
wp_enqueue_style( 'site_main_css', get_template_directory_uri() . '/css/build/main.min.css' );
wp_enqueue_script( 'site_main_js', get_template_directory_uri() . '/js/build/app.min.css' , null , null , true );

Source Control and .gitignore

In order to avoid adding build files and node_modules to my git repository, I added the following lines to my .gitignore file:

/* ignore build and node_modules folders */
node_modules/
build/

Versioning the JavaScript and CSS Assets

Given that my WordPress instance was sitting behind an AWS CloudFront caching layer, it made make sense to version the Webpack compiled JavaScript and CSS assets so that each version could be accessed (and cached) independently. To do this, I simply added a hash to the output filenames in my webpack.config.js file:

// for the JavaScript build
output: {
  filename: './js/build/app.min.[hash].js',
  path: path.resolve(__dirname)
}

// for the SASS/CSS build
plugins: [
  new MiniCssExtractPlugin({
    filename: './css/build/main.min.[hash].css'
  })
]

Next, I incorporated the clean-webpack-plugin to remove old JavaScript and CSS files from my build directories (which made it easier to find the compiled JavaScript and CSS files when enqueuing them in WordPress):

plugins: [
  // clean out build directories on each build
  new CleanWebpackPlugin(['./js/build/*','./css/build/*']),
]

Now that Webpack was producing files with unique and versioned filenames, I needed a way to enqueue them in WordPress without knowing the hash value in their filenames. I ended up using the following logic in order to do this:

// include the css file
$cssFilePath = glob( get_template_directory() . '/css/build/main.min.*' );
$cssFileURI = get_template_directory_uri() . '/css/build/' . basename($cssFilePath[0]);
wp_enqueue_style( 'site_main_css', $cssFileURI );
// include the javascript file
$jsFilePath = glob( get_template_directory() . '/js/build/app.min.*.js' );
$jsFileURI = get_template_directory_uri() . '/js/build/' . basename($jsFilePath[0]);
wp_enqueue_script( 'site_main_js', $jsFileURI , null , null , true );

References

  1. Webpack4 Release Nodes
  2. The Future of [extract-text-webpack-plugin]