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.