Using Webpack 4 and SASS with WordPress

Published

Updated Guide Available

I published an updated guide in July of 2022 on using webpack 5 with a WordPress theme . Check it out here: https://taylor.callsen.me/using-webpack-5-and-sass-with-wordpress/

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 initialized a new Node project:

npm init -f

Then I installed Webpack and the Webpack CLI:

npm i --save-dev webpack webpack-cli

With npm’s 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:

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 Webpack config file – webpack.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({})
    ]
  }
};

Including the Gutenberg Block Editor styles in the SASS build (Optional)

WordPress 5+ ships with Gutenberg, the new and improved Block Editor used for authoring content. The CSS styles that support the Block Editor are served automatically by WordPress as a separate enqueued script.

In order to bundle these styles into the Webpack SASS build, I had to first disable the enqueued script in my functions.php:

// remove default guttenberg block editor stylesheet
//  https://wpassist.me/how-to-remove-block-library-css-from-wordpress/
function wpassist_remove_block_library_css(){
    wp_dequeue_style( 'wp-block-library' );
} 
add_action( 'wp_enqueue_scripts', 'wpassist_remove_block_library_css' );

Next I had to modify my main app.scss SASS build entry point (./taylor/css/src/app.scss) to add an include referencing the Block Editor styles from within wp-includes:

// Guttenberg and Block Editor Styles (columns, etc)
@import '../../../../../wp-includes/css/dist/block-library/style.css';

Determining the relative path was tricky, but trying the ‘cd’ command from the same working directory as my main.scss file helped me figure out how many ../s to include.

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.js' , 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/*']),
]

Note: newer versions of CleanWebpackPlugin expect a full object as the configuration parameter (Thank you Moochou for pointing this out below!). For example:

plugins: [
  new CleanWebpackPlugin({
    cleanOnceBeforeBuildPatterns: ['./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]

Subscribe by Email

Enter your email address below to be notified about updates and new posts.


Comments

Loading comments..

No responses yet

Leave a Reply

Your email address will not be published. Required fields are marked *