Selecting and Dispatching with the Data Module

When you start building web applications with React, you begin to realize that you are often passing your application’s state (or portions of the state) down to child components. Other times you are lifting state up to a common ancestor component for the child components to share. A solution to centralizing the application state is by using Redux which brings a lot of great features to make working with your application’s state easier.

The WordPress Data Module, like Redux, is used to manage application state for both plugins and WordPress. The data module is implemented on top of Redux but provides a way to create separate stores. A store is an object that holds the application’s state. You can read more about how the WordPress Data Module differs from Redux in the Gutenberg Handbook.

Gutenberg registers a handful of stores for individual modules within the application’s state by default. You can also view the reference for these data modules in the Gutenberg Handbook. We are going to build an example block that utilizing the selectors and actions of the core/editor and core/notices data modules in this walk-through.

What is a selector?

Selectors are the primary mechanism for retrieving data from the application state. For example, if we wanted to retrieve a list of all blocks from this post we would call the getBlocks() selector from the core/editor data store.

Here’s how to do it in your browser’s developer console within the Gutenberg editor.

wp.data.select('core/editor').getBlocks();

We would receive the following array containing all the block data of this post.

[
  { "clientId": "a8f90fd9-3c96-4226-b578-1a53a5adc836", "name": "core/paragraph", ... },
  { "clientId": "34208a79-7da9-4d07-89cb-3a3a54d5f7a0", "name": "core/paragraph", ... },
  { "clientId": "967498a2-1055-48e5-8d09-71bc1a40ff80", "name": "core/paragraph", ... },
  { "clientId": "bffa3517-0ca3-42be-b70d-2399c7007a57", "name": "core/heading", ... },
  { "clientId": "5113943b-75dd-4793-8ae5-2752f28955e3", "name": "core/paragraph", ... },
  { "clientId": "f692fc2f-6092-42a4-9f37-68bacd3c18ff", "name": "core/code", ... },
  { "clientId": "2f85326b-3b1c-4cfe-b328-851435043c17", "name": "core/paragraph", ... },
  { "clientId": "cf1702ea-2f08-4f01-be86-19ece86ee45a", "name": "core/code", ... },
  { "clientId": "0c09eefd-0cec-4552-be05-1aadcf0609a7", "name": "core/heading", ... },
  { "clientId": "181b4b26-b6f8-4c00-bd8e-2f1b7806fe3d", "name": "core/paragraph", ... }
]

What is an action

Actions are the primary mechanism for making changes to the application state. For this example, we can create a new notice by dispatching the createNotice() action from the core/editor data store.

Here’s how to do it in your browser’s developer console within the Gutenberg editor.

wp.data.dispatch('core/notices').createNotice( 'success', 'Here is our notice!' );

We would then see the following notice displayed directly below the editor’s top bar.

Setting up our block

In Getting Started with Block Development in ES.Next, we created a simple block with a minimal build process using webpack and Babel. In How to Internationalize Your Block, we updated our simple block with internationalization and did a little refactoring so let’s start from that. You can also grab the code from GitHub.

We need to update the wp_register_script call to include the wp-data and wp-compose dependencies.

<?php
wp_register_script(
'gwg-block',
GWG_ESNEXT_PLUGIN_URL . 'block.build.js',
[ 'wp-blocks', 'wp-i18n', 'wp-data', 'wp-compose' ],
GWG_ESNEXT_VERSION,
true // Enqueue script in the footer.
);
view raw 01.php hosted with ❤ by GitHub
View this gist on GitHub

Create our block

We’re going to put together a simple block that will display the block types in the current post and include buttons to trigger different notification types. This should give you a good starting point to explore other data stores and select or dispatch the available actions.

Let’s get started by creating a functional component, which we’ll use in our edit function, and importing our dependencies.

const { __ } = wp.i18n;
const { registerBlockType } = wp.blocks;
const { compose } = wp.compose;
const { withSelect, withDispatch } = wp.data;
const MyComponentBase = ({ createNotice, blocks, className }) => {
const triggerNotice = (type) => {
createNotice(type, __('Hello, Notices Data!', 'gwg'));
}
return (
<div className={className}>
<p>{__('Functional Component', 'gwg')}</p>
<ul>
<li><a onClick={() => triggerNotice('info')}>Trigger info notice</a></li>
<li><a onClick={() => triggerNotice('error')}>Trigger error notice</a></li>
<li><a onClick={() => triggerNotice('warning')}>Trigger warning notice</a></li>
<li><a onClick={() => triggerNotice('success')}>Trigger success notice</a></li>
</ul>
<ul>
{blocks.map(block => <li>{block.name}</li>)}
</ul>
</div>
);
}
view raw 02.jsx hosted with ❤ by GitHub
View this gist on GitHub

There are a few new functions and components we’re importing to work with the data stores: compose, withSelect, and withDispatch.

withSelect

This is a higher-order component that we use to pass data as props from the selected data store to our component.

Read more in the Gutenberg Handbook

withDispatch

This is a higher-order component that we use to pass dispatch functions as props from the selected data store to our component.

Read more in the Gutenberg Handbook

compose

This function composes multiple higher-order components into a single higher-order component. We will pass our withSelect and withDispatch function calls as parameters of compose alongside our component. Our component will then have access to the selectors and actions we define.

Read more in the Gutenberg Handbook

Write the component

Next we’ve created a component named MyComponentBase which accepts our props and renders the output of our block edit function. I’ve used variable deconstruction in the received parameters to select what we’re going to use from the props passed in. This is another cool feature of ES.Next that I love using.

// Variable deconstruction.
({ createNotice, blocks, className }) => {}
// Alternative.
(params) => {
const { createNotice, blocks, className } = params;
}
view raw 03.jsx hosted with ❤ by GitHub
View this gist on GitHub

We’re also creating a function to easily dispatch the createNotice action. In the returned JSX, we’re calling this function whenever one of the links are clicked.

const triggerNotice = (type) => {
createNotice(type, __('Hello, Notices Data!', 'gwg'));
}
return (
<ul>
<li><a onClick={() => triggerNotice('info')}>Trigger info notice</a></li>
</ul>
);
view raw 04.jsx hosted with ❤ by GitHub
View this gist on GitHub

And finally we’re creating an unordered list by looping through all the blocks passed and outputting their name.

return (
<ul>
{blocks.map(block => <li>{block.name}</li>)}
</ul>
);
view raw 05.jsx hosted with ❤ by GitHub
View this gist on GitHub

Now that we have our component ready, it’s time to pass in our selectors and actions. Using withSelect and withDispatch we need to return an object which will be mapped to our component as props. We need one selector to retrieve all the blocks in the post and one action to dispatch our notifications.

const applyWithSelect = withSelect(select => {
const { getBlocks } = select('core/editor');
return { blocks: getBlocks() };
});
const applyWithDispatch = withDispatch(dispatch => {
return { createNotice } = dispatch('core/notices');
});
view raw 06.js hosted with ❤ by GitHub
View this gist on GitHub

Note that I am using another shorthand available in ES.Next to deconstruct variables from the returned dispatch('core/notices') call and at the same time returning an object containing it. This would be the equivalent:

const applyWithDispatch = withDispatch(dispatch => {
const { createNotice } = dispatch('core/notices');
return { createNotice: createNotice };
});
view raw 08.jsx hosted with ❤ by GitHub
View this gist on GitHub

We can get a list of all blocks in the post with the getBlocks selector of the core/editor data store. In our withSelect call we are passing the result under the blocks property. That way we can use props.blocks in our component to work with them. What is really cool is that our component will update with any blocks added or removed during editing!

We can dispatch a notification to the editor with the createNotice action of the core/notices data store. In our withDispatch call we are passing the action dispatcher under the createNotice property. We can then use props.createNotice() to dispatch the notice within our component.

Finally, let’s put it all together with compose and update our edit function within the registerBlockType call.

const MyComponent = compose(
applyWithSelect,
applyWithDispatch,
)(MyComponentBase);
registerBlockType('gwg/esnext-data', {
title: __('Get With Gutenberg - ESNext Data', 'gwg'),
category: 'common',
edit(props) {
return <MyComponent {...props} />;
},
save(props) {
return <p className={props.className}>{__('Hello saved content.', 'gwg')}</p>;
},
});
view raw 07.jsx hosted with ❤ by GitHub
View this gist on GitHub

Activate the plugin and add the block to a post to see it in action! You should see something like this:

Conclusion

Hopefully this quick walk-through gives you a simple overview of how to interact with the data stores in Gutenberg. You should be able to explore the existing selectors and actions of the core data stores and interact with them in a similar way. In a future post we’ll tackle creating our own data stores with selectors and actions.

I’ve put all the code for this walk-through up on GitHub for reference.

Creating a Block Category

Block categories are a great way to organize blocks within the block inserter. There are a number of default categories you can use, but if a category doesn’t fit well with your block, you can make your own!

Block categories within the block inserter.

In order to create a category of your own you will need to hook into the block_categories filter. In the code snippet below we’re going to check if our category exists before adding it. The filter callback will be given the existing categories as an array and we need to ensure we’re also returning our modified array.

<?php
/**
* Add a block category for "Get With Gutenberg" if it doesn't exist already.
*
* @param array $categories Array of block categories.
*
* @return array
*/
function gwg_block_categories( $categories ) {
$category_slugs = wp_list_pluck( $categories, 'slug' );
return in_array( 'gwg', $category_slugs, true ) ? $categories : array_merge(
$categories,
array(
array(
'slug' => 'gwg',
'title' => __( 'Get With Gutenberg', 'gwg' ),
'icon' => null,
),
)
);
}
add_filter( 'block_categories', 'gwg_block_categories' );

First we will use wp_list_pluck to create an array of category slugs from the $categories array passed to our callback. We then use in_array to check if our slug exists in the $categories array. If our category slug exists we will return the unmodifed array or we will merge in our category array definition with array_merge, returning the modified array.

The required properties for a block category is a slug, and a title. You can also set an icon which can be a slug of a WordPress Dashicon. If you want to set a custom SVG icon, you’ll need to set and render it on the front-end. You can see an example from CoBlocks in the image above.

I did find that icons might be removed in a future version of Gutenberg. The idea behind removing icons is that they are a distraction and add cognitive load. You can track the issue on GitHub.

You can read more in the Gutenberg Handbook.

How to Internationalize Your Block

WordPress is used all over the world and is translated into many non-english languages. Just like WordPress we need our plugins, themes, and blocks to provide that same access. Because internationalization (often referred to as i18n) is such an important aspect of WordPress, an API is exposed to help developers implement i18n features into their plugins and themes.

With the introduction of Gutenberg we need to bring i18n into our JavaScript code and WordPress has already provided the same API for us to use. I’m going to walk you through getting i18n setup for your Gutenberg block. I will not be going through how to use the i18n API’s WordPress provides but you can check out the official documentation linked below:

Getting started

In Getting Started with Block Development in ES.Next, we created a simple block with a minimal build process using webpack and Babel. We’re going to use that as a base to work from. Work through that article to get the base setup or grab the code from GitHub.

We are going to be using two utilities on the command line that will generate the proper .pot, .po, .mo, and .json files for our translations. You will need to install these if you haven’t already:

  • WP-CLI – The command line interface for WordPress. WP-CLI won’t need an installation of WordPress to use the commands we’ll be using.
  • gettext – internationalization and localization system used for writing multilingual programs. We will be utilizing the msgfmt utility that’s included with gettext.

Although we don’t have any translatable strings in our PHP code, we’re still going to setup and load our language files for the entire project.

Updating our starting point

From implementing i18n into my own projects I found that changing how the scripts and styles for our block are registered needs to change. Instead of hooking into enqueue_block_assets and enqueue_block_editor_assets we will utilize the register_block_type function within the init hook.

After removing the gwg_block_assets and gwg_editor_assets functions, we will create a new function named gwg_register_block_type with the following contents.

<?php
function gwg_register_block_type() {
if ( ! function_exists( 'register_block_type' ) ) {
// Gutenberg is not active.
return;
}
wp_register_style(
'gwg-style',
GWG_ESNEXT_PLUGIN_URL . 'style.css',
[],
GWG_ESNEXT_VERSION
);
wp_register_style(
'gwg-editor',
GWG_ESNEXT_PLUGIN_URL . 'editor.css',
[],
GWG_ESNEXT_VERSION
);
wp_register_script(
'gwg-block',
GWG_ESNEXT_PLUGIN_URL . 'block.build.js',
[ 'wp-blocks', 'wp-i18n' ],
GWG_ESNEXT_VERSION,
true // Enqueue script in the footer.
);
register_block_type(
'gwg/esnext-starter',
[
'editor_script' => 'gwg-block',
'editor_style' => 'gwg-editor',
'style' => 'gwg-style',
]
);
}
add_action( 'init', 'gwg_register_block_type' );
view raw 01.php hosted with ❤ by GitHub

In this function we first make sure that Gutenberg is active before registering the related styles and scripts. We then use the register_block_type to tell WordPress about the scripts and styles we’ve registered, if they should load in the editor or in the theme, and which block they belong to. It’s important to use register_block_type here so that when we tell WordPress about our translation files, the slugs will be registered in the background on time.

Make your strings translatable

We will be utilizing the @wordpress/i18n component which provides the i18n functions we’re used to in PHP but for JavaScript. Within our plugin’s index.php file, we’re going to update our wp_register_script call to include this dependency.

<?php
wp_register_script(
'gwg-block',
GWG_ESNEXT_PLUGIN_URL . 'block.build.js',
[ 'wp-blocks', 'wp-i18n' ],
GWG_ESNEXT_VERSION,
true // Enqueue script in the footer.
);
view raw 02.php hosted with ❤ by GitHub

Now we’ll use the __() (double-underscore) function to retrieve the translation of the text pass to it in our JavaScript code. First import the function from the i18n component accessed from the wp global variable in our block.js file.

const { __ } = wp.i18n;
view raw 03.js hosted with ❤ by GitHub

Next we’ll update all translatable strings in our code just like we would in PHP.

registerBlockType('gwg/esnext-starter', {
title: __('Get With Gutenberg - ESNext Starter', 'gwg'),
category: 'common',
edit(props) {
return <p className={props.className}>{__('Hello editor.', 'gwg')}</p>;
},
save(props) {
return <p className={props.className}>{__('Hello saved content.', 'gwg')}</p>;
},
});
view raw 04.js hosted with ❤ by GitHub

Generate the language files

Using WP-CLI we are going to generate our .pot file using the wp i18n make-pot command. This will search all files (excluding vendor/, node_modules/, and others for strings passed to our i18n functions with the defined text domain for our plugin and generate the .pot file used as a template for translators to use.

Check the documentation for more information about the available parameters if you’d like to customize it. I like to make sure the “Last-Translator” and “Language-Team” headers are passed into the generated file for reference. When using the WP-CLI command you can pass these headers as a json string to the --headers flag.

$ wp i18n make-pot ./ ./languages/gwg.pot --headers='{"Last-Translator":"JR Tashjian <jr@getwithgutenberg.com>","Language-Team":"Get With Gutenberg <info@getwithgutenberg.com>"}'

The WP-CLI command will create the langauges/ directory with our gwg.pot file within it. If you take a look at that file you will see references toblock.js file with our translatable strings.

#: block.js:5
msgid "Get With Gutenberg - ESNext Starter"
msgstr ""
#: block.js:9
msgid "Hello editor."
msgstr ""
#: block.js:13
msgid "Hello saved content."
msgstr ""
view raw 06.pot hosted with ❤ by GitHub

Now that we have our base translation file, let’s go ahead and add the en_US .po file since that is the locale I’m writing this for. All we have to do is copy our gwg.pot file to gwg-en_US.po.

$ cp ./languages/gwg.pot ./languages/gwg-en_US.po

Let’s make this easily repeatable by utilizing our npm scripts. Let’s make a new script called i18n-pot that we can run with npm run i18n-pot to automatically do the above two steps for us.

"scripts": {
"i18n-pot": "wp i18n make-pot ./ ./languages/gwg.pot --headers='{\"Last-Translator\":\"JR Tashjian <jr@getwithgutenberg.com>\",\"Language-Team\":\"Get With Gutenberg <info@getwithgutenberg.com>\"}' && cp ./languages/gwg.pot ./languages/gwg-en_US.po"
}
view raw 08.json hosted with ❤ by GitHub

We now have the translation file for our plugin to use. However this is only going to be used on the PHP side right now. The way the @wordpress/i18n component works is that it will read the data from a localized variable. This localized variable is generated by PHP when we load a .json file containing these translated strings through the wp_set_script_translations function in WordPress (available in version 5.0.0).

We’re going to use WP-CLI again to generate our .json file using the wp i18n make-json command. This command will extract the strings referenced in our build.js file from the gwg-en_US.po file and create our .json file.

$ wp i18n make-json languages/

This WP-CLI command will create a new .json file within our languages/ directory for every .po file within it. You’ll notice these files have an md5 hash appended to them which is a hash of the file path. We will change this because the path used for the md5 hash doesn’t match the one the wp_set_script_translations function in WordPress creates. This might be a bug or I might be missing something but after diving into the code I found renaming the file was a quick fix. We will rename the md5 hash portion of the filename to our enqueued script slug (gwg-block) which is the first file the function looks for.

$ mv languages/gwg-en_us-5bd7b3c720e0df736021f834799d5ef3.json languages/gwg-en_US-gwg-block.json

Again, let’s make this easily repeatable for us by adding a new script called i18n-json that we can run with npm run i18n-json. I’m going to add a call to the unix tool rename to rename all the md5 hashes our .json files have to gwg-block.

$ wp i18n make-json languages/ && rename 's/(gwg-[a-zA-Z_]+-)[^\.]*(\.json)/$1gwg-block$2/' ./languages/*

And here it is altogether in our package.json file.

"scripts": {
"i18n-json": "wp i18n make-json languages/ && rename 's/(gwg-[a-zA-Z_]+-)[^\\.]*(\\.json)/$1gwg-block$2/' ./languages/*"
}
view raw 12.json hosted with ❤ by GitHub

And finally the last portion of generating our translation files, the .mo files. We will use msgfmt to generate a .mo file for every .po file we have in the languages directory.

$ for file in `find . -name "*.po"` ; do msgfmt -o ${file/.po/.mo} $file ; done

And here it is in out package.json file.

"scripts": {
"i18n-mo": "for file in `find . -name \"*.po\"` ; do msgfmt -o ${file/.po/.mo} $file ; done"
}
view raw 14.json hosted with ❤ by GitHub

Now let’s add it all together into a single command. We’ve prefixed each command with i18n and I think that’s the perfect name to consolidate all the commands. We can then run every command with npm run i18n.

"scripts": {
"i18n": "npm run i18n-pot && npm run i18n-json && npm run i18n-mo"
}
view raw 15.json hosted with ❤ by GitHub

Putting it all together now

We have translated the strings in our code and we’ve generated the required files used to provide new translations to our project. We now only need to tell WordPress where these files are for our translations to load properly. We need to hook into the init action and call two functions in order for everything to work properly. First, we’ll add a new function to handle loading our translation files for PHP to use.

<?php
function gwg_init() {
load_plugin_textdomain( 'gwg', false, GWG_ESNEXT_PLUGIN_DIR . '/languages' );
}
add_action( 'init', 'gwg_init' );
view raw 16.php hosted with ❤ by GitHub

Now we can update our gwg_register_block_type function at the end with a call to wp_set_script_translations.

<?php
if ( function_exists( 'wp_set_script_translations' ) ) {
wp_set_script_translations( 'gwg-block', 'gwg', GWG_ESNEXT_PLUGIN_DIR . '/languages' );
}
view raw 17.php hosted with ❤ by GitHub

If you create a new post or page with Gutenberg and view the page source in your browser, search for gwg-esnext-i18n/block.build.js. You should see this snippet of code making your translated strings available to the @wordpress/i18n component.

<script type='text/javascript'>
( function( domain, translations ) {
var localeData = translations.locale_data[ domain ] || translations.locale_data.messages;
localeData[""].domain = domain;
wp.i18n.setLocaleData( localeData, domain );
} )( "gwg", {"translation-revision-date":"YEAR-MO-DA HO:MI+ZONE","generator":"WP-CLI\/2.1.0","domain":"messages","locale_data":{"messages":{"":{"domain":"messages","lang":"en","plural-forms":"nplurals=2; plural=(n != 1);"},"Get With Gutenberg - ESNext Starter":[""],"Hello editor.":[""],"Hello saved content.":[""]}}} );
</script>
view raw 18.html hosted with ❤ by GitHub

Conclusion

There you have it. Internationalization is a part of plugin and theme development that should not be overlooked by developers. With only a little bit of additional work you can help bring your code to an entirely new audience and help others contribute to WordPress by providing translations.

I’ve put all the code for this walk-through up on Github for reference.

Getting Started with Block Development in ES.Next

Whether you’re new to the ways modern JavaScript is written today or not, it’s important to know why our toolset is the way it is. The biggest barriers I’ve seen in developers getting into block development has been around the build processes, modern JavaScript, and webpack. I’m going to walk you through setting up a basic WordPress plugin for developing a block for Gutenberg.

Let’s get started with a couple definitions.

What is ES.Next?

ECMAScript (ES) is a scripting-language specification created to standardize JavaScript. You’ve most likely already written JavaScript according to the ECMAScript 5 (ES5) standard which is supported by all the major browser vendors. A build process in your project is not absolutely necessary here because of this support.

ES.Next refers to versions of ECMAScript from ES6 and beyond. ES6+ brings a load of great features to the scripting language but is not completely supported by all browsers. In order for us to take advantage of these new features we must add a build process to our project with a transpiler.

What is a transpiler?

A transpiler is a source-to-source compiler that takes the source code of a program written in one programming language as its input and produces the equivalent source code in another programming language (Wikipedia).

For our project the transpiler will convert ECMAScript 6+ code into a backwards compatible version of JavaScript (ECMAScript 5) for existing browsers. We will accomplish this by using Babel within our build process.

Setting up the build process

We will be using NPM to manage the JavaScript dependencies for our project. Through it we will use Babel to transpile our code and webpack to bundle everything together. First make sure Node.js is installed and ready to go.

Create a new folder in the plugins directory of your WordPress installation and initialize the NPM package.json file:

# Create our plugin folder and move into it
$ mkdir wp-content/plugins/esnext-starter
$ cd wp-content/plugins/esnext-starter
# Initialize our NPM package.json file.
$ npm init -y

Next we’ll install our dependencies that NPM will track in our newly created package.json file.

# Install webpack
$ npm install --save-dev webpack webpack-cli
# Install Babel
$ npm install --save-dev @babel/core @babel/preset-env @babel/preset-react babel-loader
# Install utility for using environment variables across platforms.
$ npm install --save-dev cross-env

Let’s go over each dependency we just installed to understand the purpose behind installing it.

webpack and webpack-cli

Webpack is our module bundler and the core of the build process. We will setup a configuration file which tells webpack how to bundle together all of our assets. Webpack-cli is a tool used to run webpack on the command line.

@babel/core and babel-loader

@babel/core is the compiler core. We need this to use Babel within our build process. Babel-loader is a transformation (loader) for webpack that is applied on your source code. This package tells webpack to run the transpiling we’ve configured using Babel.

@babel/preset-env

This package is a configuration preset for Babel that allows you to use the latest JavaScript without needing to configure the syntax transforms or browser polyfills needed. Instead, we will setup a .browserlistrc file with our target environment(s) which the preset will use to transpile everything.

@babel/preset-react

This package is a configuration preset for Babel that includes plugins specific to transpiling with React. Since we are using React to develop Gutenberg blocks, we’re going to need this.

These are the packages included with this preset as of this writing:

@babel/plugin-syntax-jsx: Allow parsing of JSX.

@babel/plugin-transform-react-jsx: Turn JSX into React function calls.

@babel/plugin-transform-react-display-name: Add displayName to React.createClass calls. The displayName string is used in debugging messages.

cross-env

This package allows us to set and use environment variables across platforms. The most common use I’ve experienced is setting the environment to development or production which is used to set or load separate configurations.

Configuring Babel, webpack, and scripts

Now that we have all the dependencies installed, let’s put everything together so we can start writing our block. For this walk-through I’m only going to be concerned with configuring the JavaScript. We will be writing and loading regular CSS without any pre-processing or bundling.

First let’s create a .browserlistrc file and add the following content to it. Remember this file is automatically read by the @babel/preset-env package which sets our transpiler target environments. I’m using the same configuration that the Gutenberg project is using to ensure consistency.

> 1%
ie >= 11
last 1 Android versions
last 1 ChromeAndroid versions
last 2 Chrome versions
last 2 Firefox versions
last 2 Safari versions
last 2 iOS versions
last 2 Edge versions
last 2 Opera versions

Now create a .babelrc file and add the following content to it. For now we’re just telling Babel to load the @babel/preset-env and @babel/preset-react presets when it runs.

{
"presets": [ "@babel/preset-env", "@babel/preset-react" ]
}

Finally, lets configure webpack by creating a webpack.config.js file with the following contents.

const externals = {
wp: 'wp',
react: 'React',
'react-dom': 'ReactDOM',
};
const isProduction = process.env.NODE_ENV === 'production';
const mode = isProduction ? 'production' : 'development';
module.exports = {
mode,
entry: './block.js',
output: {
path: __dirname,
filename: 'block.build.js',
},
externals,
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
},
},
],
},
};
view raw 05.js hosted with ❤ by GitHub

At the top of this configuration we are setting up a few variables that we use in the actual webpack configuration (the module.exports code block).

const externals = {
wp: 'wp',
react: 'React',
'react-dom': 'ReactDOM',
};
view raw 06.js hosted with ❤ by GitHub

By configuring our “externals” we are telling webpack to exclude these dependencies, which are referenced throughout our code, from the compiled output. WordPress Core provides Gutenberg’s own components from the wp global variable as well as React and ReactDOM from react and react-dom respectively. We don’t need to include these in our own output just like we’ve not had to include jQuery with our plugins and themes.

You can read more about Externals in the webpack documentation.

const isProduction = process.env.NODE_ENV === 'production';
const mode = isProduction ? 'production' : 'development';
view raw 07.js hosted with ❤ by GitHub

With the next two lines we are ensuring a mode has been set for webpack based on an environment variable passed. The mode options tells webpack which built-in optimizations it should use. We are setting this explicitly because webpack does not automatically set it.

You can read more about the Mode configuration option in the webpack documentation. You can also see what the built-in optimizations are set based on this setting.

module.exports = {
mode,
entry: './block.js',
output: {
path: __dirname,
filename: 'block.build.js',
},
externals,
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
},
},
],
},
};
view raw 08.js hosted with ❤ by GitHub

And finally we put it all together at the end to finish our webpack configuration. For this walk-through we are going to write our Gutenberg block in a single file named block.js. We are setting this as the entry file for webpack to start bundling from. From there we are telling webpack to put the output into a block.build.js file.

The last bit of our configuration has to do with Babel. Within the module property we are setting rules that webpack goes through and passes the target files to the module that will handle it. Here we are telling webpack to pass any file with the .js extension to the babel-loader module. Babel will do its magic and pass its result back to webpack for bundling.

Now that we’ve configured our build process, we should setup a couple scripts we can use to easily run our build process. Add the following to your package.json file.

"scripts": {
"build": "cross-env NODE_ENV=production webpack",
"dev": "cross-env webpack --watch"
}
view raw 09.json hosted with ❤ by GitHub

Now from the terminal you can run npm run build to bundle everything together according to webpack’s production optimizations. Alternatively run npm run dev to bundle with webpack’s development optimizations and re-run when a change has been detected.

Setting up our plugin

Now that our build process is setup we need to hook into WordPress and load our Gutenberg block. Let’s create a few new files in our plugin directory:

block.js: This file will hold all the JavaScript for our simple block.

editor.css: This file will contain the styles for our block while in the editor interface.

style.css: This file will contain the styles for our block within the editor and in our theme. It’s important that these styles are shared to keep the look exactly the same in the editor as it is on the front-end.

index.php: This is our plugin file where we will enqueue all the assets for our block.

Plugin File

There are only two hooks we need to use in order to load our Gutenberg block: enqueue_block_assets and enqueue_block_editor_assets.

We will hook into enqueue_block_assets to enqueue our style.css file for both the editor and front-end.

<?php
function gwg_block_assets() {
wp_enqueue_style(
'gwg-style-css',
plugin_dir_url( __FILE__ ) . 'style.css'
);
}
add_action( 'enqueue_block_assets', 'gwg_block_assets' );
view raw 10.php hosted with ❤ by GitHub

And we will hook into enqueue_block_editor_assets to enqueue our editor.css file and the bundled block.build.js file for the editing interface.

<?php
function gwg_editor_assets() {
wp_enqueue_script(
'gwg-block-js',
plugin_dir_url( __FILE__ ) . 'block.build.js',
[],
false,
true // Enqueue script in the footer.
);
wp_enqueue_style(
'gwg-editor-css',
plugin_dir_url( __FILE__ ) . 'editor.css',
);
}
add_action( 'enqueue_block_editor_assets', 'gwg_editor_assets' );
view raw 11.php hosted with ❤ by GitHub

Create a Block

Let’s add some code to our block.js file to create our new block. In order to register a new block we need to use the registerBlockType function from the @wordpress/blocks package.

Remember we don’t have to npm install @wordpress/blocks and instead Gutenberg exposes this package through the wp external we defined earlier. There are a number of packages accessible through this global variable but we only need wp.blocks right now. Here is how I’d access the registerBlockType function by using the destructuring assignment syntax in JavaScript.

const { registerBlockType } = wp.blocks;
view raw 12.js hosted with ❤ by GitHub

The minimal properties we need to create a block is a name, title, category, an edit function, and a save function.

The name needs to be a unique string. According to the Gutenberg Handbook names need to be structured as namespace/block-name, where namespace is the name of your plugin or theme. I’m going to use gwg/esnext-starter for this walk-through.

registerBlockType('gwg/esnext-starter', {
title: 'Get With Gutenberg - ES.Next Starter',
category: 'common',
edit(props) {
return <p className{props.className}>Hello editor.</p>
},
save(props) {
return <p className{props.className}>Hello saved content.</p>
},
});
view raw 13.js hosted with ❤ by GitHub

I think the Gutenberg Handbook explains the edit and save functions best:

“When registering a block, the edit and save functions provide the interface for how a block is going to be rendered within the editor, how it will operate and be manipulated, and how it will be saved.”

In my experience you will be working within the edit function the most. The edit function is where you will hook into different parts of the Gutenberg editor. Settings and controls for manipulating your block from within the editor will also be added and managed here.

The save function defines what the final markup should be. It’s important to think about your final markup before you dive deep into developing a block. It is possible to change the markup later but we’ll go over that in a future article.

The save function is not just used to define the final markup but also to verify the markup when the block is loaded within the editor. Should the markup not be exactly the same after the block has been saved you will see an invalid block error. My tip for avoiding this is to ensure only static content is returned here. Dynamic content doesn’t belong here.

You’ll notice we are rendering a paragraph tag with props.className as the selector. The edit and save functions receive a number of values through the props variable. className is one of them and is generated from our block name. Our block name would be converted to .wp-block-gwg-esnext-starter selector which we will use in our stylesheets for both the front-end and back-end.

Once you’ve added your own styles to the stylesheets we created, you will have created your first Gutenberg block! Here’s a couple screens of the block within the front-end and the back-end.

Front-End
Back-End

Conclusion

I hope this walk-through has helped you understand the why and how of a basic build process with React, Babel, and webpack. Most importantly I hope it’s helped you get started with block development for Gutenberg and WordPress.

I’ve put all the code for this walk-through up on Github for reference.