Gutenberg Tutorials: #1 How to Build a Simple Gutenberg Block with Webpack

Franky Hung
Geek Culture
Published in
11 min readMay 4, 2021

--

This is the first article in the Gutenberg Tutorials Series. This series aims at helping Gutenberg beginner developers as well as myself(learning by teaching šŸ˜„). TBH, Iā€™m learning new stuff from even writing this article!

You can download the code used in this tutorial on the Github repository, or you can build the same plugin following all the steps below as well.

Check out my next tutorial: Gutenberg Tutorials: #2 How to Manage Dependencies when you do wp_register_script

Why am I writing this

Maybe you have already read the official Block Editor Handbook tutorials on how to build your first block type but feel like you donā€™t understand the whole thing. I felt the same when I first started off coding custom Gutenberg blocks, especially because I was used to developing themes and plugins in good olā€™ PHP and ES5 JavaScript. In the official tutorial, you will quickly discover that the ES5 syntax is so unreadable, and that most of the tutorials out there are using ESNext and JSX syntax already. Surely, ESNext + JSX syntax is the way to go.

Want to read this story later? Save it in Journal.

If you want to have a better understanding on how to use ESNext and JSX syntax to build Gutenberg blocks without using ready-made tools like @wordpress/scripts and create-guten-block, READ ON. I will explain clearly how I setup my JavaScript and SCSS build as simple as possible for building any Gutenberg block.

Sections in this Guide

  1. Buzzwords explained
  2. The Gutenberg Block plugin file
  3. Using Webpack to output transpiled js and css files
  4. Activate and Test the plugin
  5. Be amazed at how much you just learned! šŸŽŠ

Buzzwords explained

First, letā€™s have a quick overview of these terms so that weā€™re all on the same page further down this guide.

ESNext ā€” every latest JavaScript features not being in a current release is referred to as ESNext.

JSX ā€” an extension to the JavaScript language syntax developed alongside with React.js. This allows you to write html-like tags directly in your js files.

SCSS ā€” an extension to CSS, stands for Sassy CSS. Note that SCSS and Sass have different syntaxes: see this stackoverflow answer.

Babel ā€” a transpiler for JavaScript, it is used to transpile latest JavaScript syntax to an older version which is supported by most browsers.

Webpack ā€” a bundler that can bundle, transform and package JavaScript and asset files for usage in a browser.

The Gutenberg Block plugin file

At its simplest, you only need a plugin.php and an editor.js to have a working Gutenberg Block plugin at your disposal. The main purpose of the plugin.php is to let WordPress recognises it as a plugin and registers the block to ensure that the registered script and style handles are enqueued when the Block editor loads. And the main purpose of editor.js is to define how to block actually works in the Block Editor. But for the setup weā€™re going to build, 2 files arenā€™t enough. Thatā€™s because we want to write ESNext + JSX in editor.js plus SCSS, we will need a bunch of other files to help us in our cause. Now letā€™s dive into code!

Assuming you are in the WordPress plugins directory, make a new directory for the Gutenberg Block plugin. For simplicity, weā€™re only going to make a single-block plugin.

mkdir gtg-demo && cd gtg-demo
touch plugin.php

plugin.php contents:

<?php
/*
Plugin Name: Gutenberg Block Demo
*/
function gtg_demo_register_block() {
wp_register_script(
'gtg-demo-editor-script',
plugins_url( 'build/editor.js', __FILE__ ),
array( 'wp-blocks' ),
filemtime( plugin_dir_path( __FILE__ ) . 'build/editor.js' )
);
wp_register_style(
'gtg-demo-style',
plugins_url( 'build/style.css', __FILE__ ),
[],
filemtime( plugin_dir_path( __FILE__ ) . 'build/style.css' )
);
wp_register_style(
'gtg-demo-editor-style',
plugins_url( 'build/editor.css', __FILE__ ),
[],
filemtime( plugin_dir_path( __FILE__ ) . 'build/editor.css' )
);
register_block_type( 'myfirstblock/gtg-demo', array(
'editor_script' => 'gtg-demo-editor-script',
'editor_style' => 'gtg-demo-editor-style',
'style' => 'gtg-demo-style'
) );

}
add_action( 'init', 'gtg_demo_register_block' );

Here, we register build/editor.js (the js and css build files will be generated later, just put them down here first) as the gtg-demo-editor-script handle, and the css files as the gtg-demo-editor-style and gtg-demo-style handles respectively. These handles are then fed into register_block_type to register the block to ensure that the registered script and style handles are enqueued when the Block editor loads. The filemtime function returns the time when the content of the file was changed, which helps create a different url query to the file every time you changed it(e.g. editor.css?ver=341237498), busting any browser cache on the file. Quoting from the official tutorial:

The editor_script and editor_style files will only be enqueued in the editor, while the script and style will be enqueued both in the editor and when viewing a post on the front of your site.

Notice that the block type name we register here is namespaced with ā€˜myfirstblockā€™ in order to prevent collision with other registered block types.

Using Webpack to output transpiled js and css files

This part might be a bit lengthy, but definitely essential in understanding the build process of our Gutenberg block.

In order to use ESNext + JSX syntax to write our blocks, we need a transpiler to convert them into browser-compatible code. Together with the need to compile SCSS to CSS, the famous bundling tool ā€” Webpack now comes to the rescue! For someone who just got into Webpack or didnā€™t learn enough Webpack basics, the Webpack official guides are actually very helpful. I suggest you read the first 10 guides or so to have a clear understanding.

Weā€™ll need npm a lot in the following steps, make sure you have node.js and npm installed. Assuming you are still in the gtg-demo plugin directory, initialise the local npm project:

npm init -y

Here, we install Webpack from npm. As of now, Webpack 5 has already been released. For Webpack 4 or later, if we want to use Webpack on the command line we have to install webpack-cli too:

npm install webpack webpack-cli --save-dev
touch webpack.config.js

webpack.config.js contents:

const path = require("path");
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
entry: {
editor: path.resolve(__dirname, "src/editor.js"),
style: path.resolve(__dirname, "src/style.js"),
},
output: {
path: path.resolve(__dirname, "build"),
filename: '[name].js',
clean: true
},
plugins: [
new MiniCssExtractPlugin(),
],
module: {
rules: [
{
test: /\.scss$/,
use: [MiniCssExtractPlugin.loader, "css-loader", "postcss-loader", "sass-loader"]
},
{
test: /\.js$/,
exclude: /node_modules/,
use: ["babel-loader"]
}
],
},
};

Woah, thatā€™s quite a lot to take in. Let me explain what weā€™re doing with the wepback.config.js first before installing other development dependencies as well. By default, by running the command webpack in your directory, Webpack will follow the instructions stated in webpack.config.js found in the same directory.

The configuration object in module.exports is what defines all of our instructions to Webpack.

Firstly, the ā€˜entryā€™ property lets you define one or more entry scripts to be processed by Webpack. The idea behind is this:

By stating what dependencies your entry js file needs, Webpack can use this information to build a dependency graph. It then uses the graph to generate an optimized bundle where scripts will be executed in the correct order.

We will carry out corresponding steps as I explain each part of the webpack.config.js. For the ā€˜entryā€™ property, the syntax for each entry is [name]: [file_path]. The [name] part is later referenced in the ā€˜outputā€™ property. Letā€™s first create the src files for the entries:

mkdir src && cd src
touch editor.js style.js editor.scss style.scss

editor.js contents:

const { registerBlockType } = wp.blocks;
import './editor.scss';
registerBlockType( 'myfirstblock/gtg-demo', {
title: 'GTG Demo Block',
icon: 'heading', // https://developer.wordpress.org/resource/dashicons/#heading
category: 'text',
edit: props => {
return (
<h1 className={'gtg-demo-h1'}>Hello World!</h1>
);
},
save: props => {
return (
<h1 className={'gtg-demo-h1'}>Hello World!</h1>
);
},
} );

editor.scss contents:

$start-color: green;
$end-color: red;
h1.gtg-demo-h1 {
background: linear-gradient(to bottom, $start-color, $end-color);
}

This editor.js has JSX syntax which will have to be transpiled by our babel loader that will be installed later. In registerBlockType, the ā€˜editā€™ property let us define how the block renders/behaves in the Block Editor, and the ā€˜saveā€™ property let us define how the block should be saved in the database (and how it renders in the frontend). Our simple block here only displays an h1 heading with ā€˜gtg-demo-h1ā€™ css class.

Notice that the first argument to the registerBlockType should be the same as the namespaced block type we previously registered in plugin.php. The ā€˜iconā€™ property let you insert a dashicon icon name which will be the icon for your block in the blocks listing. The ā€˜categoryā€™ let you specify under which category should your block appear in the blocks listing. There are many more other properties you can specify in this function, but we will keep it light for now.

Okay, at the top of the file you should see weā€™re importing the editor.scss file as a dependency of editor.js. The editor.scss file will be later transpiled to build/editor.css, which will be applied only in the Block Editor as I have explained earlier. For various reasons when you are developing more complicated Gutenberg blocks, you will need to have different css styles for the Editor and the frontend.

style.js contents:

import './style.scss';

style.scss contents:

h1.gtg-demo-h1 {
color: blue;
}

The style.js is actually not used by our Gutenberg Block, itā€™s only here to serve as a stepping stone to pull in our style.scss dependency, which will be later transpiled to build/style.css, which will be applied both in the Block Editor and the frontend as I have explained in the plugin.php section.

Now let me explain what the ā€˜outputā€™ property does in webpack.config.js.

...
output: {
path: path.resolve(__dirname, "build"),
filename: '[name].js',
clean: true
},
...

The ā€˜pathā€™ property specifies where you want Webpack to output the bundled files. Here, Webpack will put the bundled files in the ā€œbuildā€ directory, it will also create the directory if it has not already been created.

The ā€˜filenameā€™ property specifies how you want to name the bundled files. This [name] handle represents the corresponding entry point name we defined previously in the ā€˜entryā€™ property.

The ā€˜cleanā€™ set to true means Webpack will clean the build directory each time before new files are being built.

Next, weā€™ll move on to the module rules:

...
module: {
rules: [
{
test: /\.scss$/,
use: [MiniCssExtractPlugin.loader, "css-loader", "postcss-loader", "sass-loader"]
},
{
test: /\.js$/,
exclude: /node_modules/,
use: ["babel-loader"]
}
],
},
...

These rules instruct Webpack on how you want to transpile or bundle your source files. The ā€˜testā€™ property accepts a regex expression, such that only matched source files will be processed by the loaders in the ā€˜useā€™ property. Beware that the loaders in the array are loaded in reverse order, that means we will run sass-loader first and MiniCssExtractPlugin.loader last.

To use the sass-loader to transpile SCSS code into CSS code, we need to install(cd back to the root directory in case you are still in the src/ directory):

cd ..
npm install sass-loader sass --save-dev

To use the postcss-loader to convert modern CSS into something most browsers can understand(e.g. adding -webkit prefixes to your css rules), we need to install:

npm install postcss-loader postcss-preset-env --save-dev
touch postcss.config.js

postcss.config.js contents:

module.exports = {
plugins: [
require('postcss-preset-env')({
browsers: 'last 2 versions',
}),
],
};

The PostCSS plugin called postcss-preset-env is one of the most commonly used presets we can leverage so that we donā€™t have to configure all the details ourselves.

Next, the css-loader interprets @import and url() in your CSS like import/require() in your JavaScript and will resolve them correctly. The MiniCssExtractPlugin.loader creates a CSS file per JS file which contains CSS for you; it is commonly used alongside with the css-loader. Install them:

npm install css-loader mini-css-extract-plugin --save-dev

Finally, we need the babel-loader to transpile our ESNext + JSX code. We donā€™t have ESNext features in our demo code, but itā€™s a good practice as you donā€™t have to worry in case you have used ESNext syntax. We will use the @wordpress/babel-preset-default preset as this is also used by @wordpress/scripts.

Quoting from the preset description:

The preset includes configuration which enable language features and syntax extensions targeted for support by WordPress. This includes ECMAScript proposals which have reached Stage 4 (ā€œFinishedā€), as well as the JSX syntax extension.

Letā€™s install these:

npm install @babel/core babel-loader @wordpress/babel-preset-default --save-dev
touch babel.config.json

babel.config.json contents:

{
"presets": [ "@wordpress/default" ]
}

Now the board is set, letā€™s get the pieces moving! Open your package.json file ā€” in the ā€˜scriptsā€™ property, add the ā€˜buildā€™ and ā€˜watchā€™ scripts:

"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack",
"watch": "webpack --watch"
},

The ā€˜buildā€™ script simply runs the webpack command to build our bundle according to our webpack.config.js, while the ā€˜watchā€™ script literally watches if any files in the dependency chain changes, Webpack will automatically rebuild the files for you, so you donā€™t have to manually run npm run build every time. For now just run:

npm run build

After the build is successful, you should notice these files should be generated:

build/editor.js
build/editor.css
build/style.js
build/style.css

The story that begins from plugin.php finally goes full circle šŸŽŠ! Now you should understand why we register scripts and styles using these files at the beginning.

Activate and Test the plugin

Open up your WordPress site and activate your newly created Gutenberg Block plugin.

Create a test post, and open the blocks listing to see if our block is there:

Click on the ā€˜GTG Demo Blockā€™ to add it to the post:

Voila! Well, it looks quite eerie but it proves our css is working! From the inspect console, we see that our css styles are properly prefixed as well:

Letā€™s save the post and view it in the frontend:

The color: blue rule from style.css is not overridden by Block Editor styles this time, and the background gradient is gone, since the editor.css is not applied in the frontend.

Thatā€™s it for this tutorial. Iā€™m so grateful that you read until the very end!

TBH, this is my first blog post on Medium! Please point out any mistakes I made. I would love to know what you think about this setup too! Cheers!

--

--

Franky Hung
Geek Culture

Founder of Arkon Digital. Iā€™m not a code fanatic, but Iā€™m always amazed by what code can do. The endless possibilities in coding is what fascinates me everyday.