How to Hot Reload your WordPress Blocks in Gutenberg

We’re inching closer to the public implementation of the Gutenberg editor in WordPress 5!

As you are ramping up your dev workflow and environment for the best iterative experience, you may have noticed that making updates and changes to your custom Gutenberg block is, well, kinda annoying.

Make a change. Refresh your browser. Make a fix. Reload. Add a component. Refresh.

There’s gotta be a better way, right?

Right!

In this tutorial, you’ll learn how to get rid of the constant reload workflow so that all of your changes in the Editor are injected automatically into the page, preventing the need to manually reload your browser every time you make changes to your edit function. Let’s get started!

Step 1: Install Extra Dependencies

In a typical WordPress environment, every time you want a script to be loaded up on a page, you might be used to seeing the wp_enqueue_script function, which tells WordPress to load up a Javascript file from a specific URL relative to your theme or plugin.

The resulting call might look something like this:

wp_enqueue_script( 'aceblocks', plugin_dir_url( __FILE__ ) . 'scripts/my-js-file.js', array( 'wp-blocks', 'wp-i18n', 'wp-element' ) );

Nothing too fancy there, we’re just loading up a file called my-js-file.js onto the page that lives inside of the scripts folder in the plugin root directory.

You’re going to use a similar approach for hot reloading. However, instead of loading the javascript file directly from an existing location in your plugin folder, you’re instead going to host it on a brand new local server which will serve the javascript and also package it up with some extra code that tells your browser to load in any changes you make to that file as soon as the changes occur.

To do this, you’ll first need to add a few helper packages to our project. Make sure that your plugin has a package.json file, then run the following command in your terminal to install the two dependencies you need for this to work: npm install webpack-serve react-hot-loader

Step 2: Configure your Webpack build

This is the hairy-scary part, especially if you haven’t done much Webpack configuration. Everybody’s project layout and needs are different, but this boilerplate should get your WordPress plugin set up appropriately to hot reload every time.

const isProduction = process.env.NODE_ENV === 'production';
const styleLoader = isProduction ? MiniCssExtractPlugin.loader : 'style-loader';

module.exports = {
  mode: isProduction ? 'production' : 'development',
  devtool: isProduction ? 'cheap-module-source-map' : 'eval-source-map',
  target: 'web',
  entry: './src/index',

  output: {
    filename: isProduction ? 'my-js-file.js' : 'scripts.js',
    path: path.resolve(__dirname, 'dist')
  },

  module: {
    rules: [{
      test: /\.js?$/,
      exclude: /node_modules/,
      loader: 'babel-loader'
    },
    {
      test: /\.(scss|css)$/,
      use: [
        styleLoader,
        "css-loader",
        "postcss-loader",
        "sass-loader"
      ]
    }]
  },
}

if (!isProduction) {
  module.exports.output.publicPath = 'http://localhost:8080/';

  module.exports.serve = {
    hot: true,
    dev: {
      publicPath: 'http://localhost:8080/',
      historyApiFallback: true,
      headers: {
        'Access-Control-Allow-Origin': '*'
      }
    }
  };
}

Now you need to tell WordPress that while you are in development mode, you’re going to load the script that is being served by your Webpack development server rather than the script that exists on your filesystem.

Based on the Webpack configuration above, here’s how I do it:

    if ( defined('ACEBLOCKS_DEV') && ACEBLOCKS_DEV ) {
      $path = 'http://localhost:8080/scripts.js';
    } else {
      $path = plugin_dir_url( __FILE__ ) . 'scripts/my-js-file.js';
    }

    wp_enqueue_script( 'aceblocks', $path, array( 'wp-blocks', 'wp-i18n', 'wp-element' ) );

Make sure you add define('ACEBLOCKS_DEV, true); somewhere at the bottom of your wp-config.php file and don’t allow that line to be committed to your production environment.

Step 3: Refactor your function to a component

When attempting to hot reload a component, a simple edit function just isn’t gonna cut it. We need to change the function into a full blown React component and then import it into our registration file.

WordPress docs offer the following example for what an implementation of the edit function might look like. We’ll use it as the starting point for refactoring it into a hot-reloadable component.

// Defining the edit interface
edit( { attributes, setAttributes, className, isSelected } ) {
	// Simplify access to attributes
	const { content, mySetting } = attributes;

	// Toggle a setting when the user clicks the button
	const toggleSetting = () => setAttributes( { mySetting: ! mySetting } );
	return (
		<div className={ className }>
			{ content }
			{ isSelected &&
				<button onClick={ toggleSetting }>Toggle setting</button>
			}
		</div>
	);
}

First, create a new file in your block directory and call it EditBlock.js

The format of this new file is going to look just like a React component with an ES6 class structure:

import React, { Component } from "react";

class EditBlock extends Component {

  constructor(props) {
    super(props);
    this.setAttributes = props.setAttributes;
    this.isSelected = props.isSelected;
    this.className = props.className;
    this.attributes = props.attributes;
  }

  // Toggle a setting when the user clicks the button
  toggleSetting() {
    this.setAttributes({ mySetting: ! mySetting });
  }

  render() {
    // Simplify access to attributes
    const { content, mySetting } = this.attributes;

    return (
      <div className={ className }>
        { content }
        { isSelected &&
          <button onClick={() => this.toggleSetting }>Toggle setting</button>
        }
      </div>
    );
  }
}

export default EditBlock;

Then, at the top of your index.js file where you are registering your block, you can import this new EditBlock.js file and pass the component to the edit function.

import EditBlock from "./EditBlock.js"

const { __ } = wp.i18n
const { registerBlockType } = wp.blocks

registerBlockType("demo/my-block", {
  title: __("AceBlocks™ Demo Block"),
  description: __("Demonstrating Gutenberg hot reloading."),
  category: "common",
  edit: EditBlock,
  save: ({ attributes }) => {
    return (
      <div>
        Hello, world!
      </div>
    )
  },
})

Step 4: Use the Hot Loader HOC

Now that you have a refactored component that works, let’s prep it to work with hot reloading.

import { hot } from "react-hot-loader"
import React, { Component } from "react";

class EditBlock extends Component {

  constructor(props) {
    super(props);
    this.setAttributes = props.setAttributes;
    this.isSelected = props.isSelected;
    this.className = props.className;
    this.attributes = props.attributes;
  }

  // Toggle a setting when the user clicks the button
  toggleSetting() {
    this.setAttributes({ mySetting: ! mySetting });
  }

  render() {
    // Simplify access to attributes
    const { content, mySetting } = this.attributes;

    return (
      <div className={ className }>
        { content }
        { isSelected &&
          <button onClick={() => this.toggleSetting }>Toggle setting</button>
        }
      </div>
    );
  }
}

export default hot(module)(EditBlock);

Step 5: Start your dev server

Now you’ll need to spin up the node server so Webpack can host the modified javascript that your browser needs to reload on demand. Open a new tab in your Terminal.app and run the following command in your plugin root directory: webpack-serve ./webpack.config.js

If you wanna go the extra step to make this even easier to remember, you can add an npm script that will run this command for you. Add the following to your package.json file:

  "scripts": {
    "start": "webpack-serve ./webpack.config.js",
  },

Now you can start the server with npm run start

Step 6: Refresh your page and marvel!

Make a change to your EditBlock.js file and watch as it automatically gets loaded in to your Gutenberg editor – no refresh or page reload needed!

Leave a Reply

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