How to Develop a WordPress Plugin Using Webpack 3 & React (Part 1)

#
By Peter Tasker

If you’ve been working with JavaScript in the last couple years you’ve probably heard of Webpack. It’s pretty essential in today’s JavaScript workflow and has taken the spot of other build tools like Grunt and Gulp. The WordPress core team is planning to switch to Webpack, so I thought it was high-time to see how Webpack could be integrated into a plugin development workflow.

In this article, we’ll go over how to build a React plugin interface using Webpack and all (well, most of) the bells and whistles.

What is Webpack Anyway?

Ok so what the heck is Webpack anyway? In short, Webpack is an asset bundler, which means it bundles your JavaScript, CSS, images and other assets together into one file.

Wait, what?

Why would you want this? Well the purpose of Webpack really boils down to the concept of code splitting and application structure. With ES2015, you can import assets and dependencies quite easily and with Webpack and the appropriate loader, you can even import CSS/Sass/Less and images with your JavaScript.

For example, with the style and css loaders you can do things like this:

//App.css
body{
    background: blue;
}

//App.js
import React from 'react';
import './App.css';

export default class App extends React.Component {
    render() {
        return (
            <div>
                <h1>Hello World</h1>
            </div>
        );
    }
}   

The import './App.css'; line embeds the contents of App.css in our markup so that the styles can be applied to the page.

Webpack is quite a large topic, so we’ll go over some of the main concepts and gotchas when it comes specifically to working with WordPress.

Configuration

The first thing to know about Webpack is that by default you can get things going without any config, simply using the CLI interface.

./node_modules/.bin/webpack src/index.js dist/bundle.js

The above command will take the src/index.js file with it’s dependencies and compile it to dist/bundle.js.

However, in most cases you’ll use a config file with Webpack, so that’s what we’ll be doing as well. The default configuration file is webpack.config.js, but you can specify other files with the --config flag if need be. The cool thing about Webpack is that it lets you define dependencies in JavaScript. You can use ES2015’s import function to load in whichever file you need.

In the config file you need to define the entry point(s) and an output location. The output file can be named anything but it’s typically bundle.js. An example webpack.config.js file could look like this.

const path = require( 'path' );
const webpackConfig = {
    entry: [
        './src/index.js'
    ],
    output: {
        filename: 'bundle.js',
        path: path.resolve( __dirname, 'dist' )
    }
};

module.exports = webpackConfig;

Here the entry file is located at ./src/index.js and the output file ./dist/bundle.js.

Loaders

The import './App.css' line in our earlier example includes our styles in our JavaScript. Cool right? You can do the same with images, and arbitrary text files as well, as long as you include the correct loader. Loaders are described as ‘pre-processors’ that can transform files from other languages or formats into JavaScript.

Let’s look at how loaders work. For the styles example we’ll have to get a couple of NPM packages installed.

yarn add style-loader css-loader --dev

We’re using Yarn here, but you could also use plain ‘ol npm. Once these packages are installed we’ll need to update our webpack.config.js file adding these new loaders.

  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          { loader: 'style-loader' },
          {
            loader: 'css-loader',
            options: {
              modules: true
            }
          }
        ]
      }
    ]
  }

Without getting too far into the weeds here, the module.rules functionality allows us to specify loaders and options based on the test regex. In this case we’re just handling anything that ends in .css.

And that’s really it, you can now use import './App.css' to include CSS in your JavaScript app. Cool beans!

Webpack Plugins

The next item to cover is plugins. Webpack Plugins are basically loaders with more muscle. They let you include other functionality that is more complex and generally work at the end of the compilation phase. They usually work with the compiled bundle, rather than with individual files like loaders do. Loaders also typically work before files are loaded in. So you can think of it as loaders = before compilation; plugins = after compilation + 💪.

WordPress and BrowserSync

In this example we’re using the BrowserSync Webpack plugin. BrowserSync will be our ‘live reload’ tool, reloading the page whenever our files change.

Webpack, however, does have a recommended dev server called (unsurprisingly) webpack-dev-server. While it’s great for static sites, it’s fairly difficult to get configured and working with another server. In our case, WordPress, and particularly, wp-admin, are PHP apps running under a separate web server (Apache or Nginx). What we need to do is proxy our WordPress site and inject our Webpack bundle. I gave webpack-dev-server a fair shot, but ran into too many issues. BrowserSync just works® so that’s what we’re going with!

To get BrowserSync running with Webpack we just need to install a couple packages.

yarn add browser-sync browser-sync-webpack-plugin --dev

In our webpack.config.js the below is all we need to add to get BrowserSync working.

plugins: [
    new BrowserSyncPlugin( {
            proxy: config.proxyURL,
            files: [
                '**/*.php'
            ],
            reloadDelay: 0
        }
    ),
]

config.proxyURL is the URL to our WordPress plugin admin page, in my case http://plugins.dev/wp-admin/tools.php?page=wp-react-boilerplate but this URL can be set in config.json. We’re only watching PHP files with BrowserSync because Webpack is handling the JS and CSS files for us.

I should mention that by not using webpack-dev-server we’re kind of going against the grain in terms of React development. The great thing about webpack-dev-server is that it gives you Hot Module Replacement (HMR). The selling point of HMR is that it doesn’t require a whole page refresh. Only the components that have changed are refreshed, as if by magic.

In our case, a full page reload is ok for now, and there are ways to get HMR going with BrowserSync. I just couldn’t get any existing plugin running with Webpack 3 😩.

Building for Production

The last item to cover in terms of Webpack configuration is building for production. React can be quite hefty when uncompressed, so we’ll need to make sure we minify React and our own code so that it loads as quick as possible. Luckily, Webpack makes this really easy for us. We’ll add a script to run the build to save us some typing.

yarn build is an alias for NODE_ENV=production ./node_modules/.bin/webpack. What this command does is set the NODE_ENV environment variable to production, telling any modules to do ‘production’ stuff – like minify and/or optimize imports.

In our webpack.config.js file we’ll add a little bit more configuration to set some things up for just when we run a build.

if ( process.env.NODE_ENV === 'production' ) {
    const buildFolder = path.resolve( __dirname, 'wp-react-boilerplate-built' );
    webpackConfig.plugins.push( new webpack.optimize.UglifyJsPlugin( {
        "mangle": {
            "screw_ie8": true
        },
        "compress": {
            "screw_ie8": true,
            "warnings": false
        },
        "sourceMap": false
    } ) );

    webpackConfig.plugins.push(
        new CleanWebpackPlugin( [ buildFolder ] )
    );

    webpackConfig.plugins.push(
        new CopyWebpackPlugin( [
            { from: path.resolve( __dirname, 'server' ) + '/**', to: buildFolder },
            { from: path.resolve( __dirname, 'wp-react-boilerplate.php' ), to: buildFolder },
        ], {

            // By default, we only copy modified files during
            // a watch or webpack-dev-server build. Setting this
            // to `true` copies all files.
            copyUnmodified: true
        } )
    );

    webpackConfig.output.path = buildFolder + '/dist';
}

What that whole swath of JavaScript does is tell Webpack to only run the following config if we’re in ‘production’ mode. We add a few more plugins to our config, webpack.optimize.UglifyJsPlugin() will minify and uglify our JavaScript to make it more performant. We then use the CleanWebpackPlugin() to remove the build folder so we can start fresh on each new build. And lastly, we use the CopyWebpackPlugin() to copy over our PHP files to finish building our plugin. Notice that we’re only copying one PHP file and the server directory. All of the other files aren’t needed after the build as all our JavaScript and CSS is one file – bundle.js.

The completed Webpack config is all set, so we’re ready to move onto making our plugin admin page.

Integrating with WordPress

So far we’ve covered all the Webpack stuff, but what about turning this into a WordPress plugin? Welp, that’s the easy part. Here you’ll see how much logic is required to set up a new admin page under the ‘Tools’ menu item.

public function admin_menu() {
    $title = __( 'WP React Boilerplate', $this->plugin_domain );

    $hook_suffix = add_management_page( $title, $title, 'export', $this->plugin_domain, array(
        $this,
        'load_admin_view',
    ) );

    add_action( 'load-' . $hook_suffix, array( $this, 'load_bundle' ) );
}

We’re using the admin_menu action call in the class constructor, which invokes the above method. All we’re doing is adding a management page to house our React app. Easy-peasy. Then we add an add_action() call to load the dist/bundle.js file with a wp_enqueue_script()

public function load_bundle() {
    wp_enqueue_script( $this->plugin_domain . '-bundle', plugin_dir_url( __FILE__ ) . 'dist/bundle.js', array(), $this->version, 'all' );
}

And that’s pretty much all there is on the WordPress side (for now). I also created a ./server folder to hold other PHP views and logic, but using it is optional.

Next Steps

You’ve finished part one ‎🎉 In the next part, we’ll go over how to build out the React side of the plugin (make sure you’re subscribed below if you want to be sure to see it).

The plugin is ready to go now, but it doesn’t do too much. Next time we’ll look at how to set up the REST API and configure React to talk to these end-points. We’ll also cover things like CSS pre-processors and other fancy things like animations.

Have you managed to integrate modern JavaScript and WordPress? What are your thoughts on Webpack? Let us know in the comments!

About the Author

Peter Tasker

Peter is a PHP and JavaScript developer from Ottawa, Ontario, Canada. In a previous life he worked for marketing and public relations agencies. Love's WordPress, dislikes FTP.