Developing Gutenberg Blocks with Parcel.js and Astroturf

A huge pain point for getting started with developing Gutenberg blocks in JavaScript is the learning curve for build tools.

In this demonstration we’ll pragmatically work our way up from mkdir to a working "Hello, World!" block, including support for styling with CSS-in-JS, an approach which bears out myriad workflow improvements. And we’ll do it all in about 15 minutes.

Here’s the plan, squad:

  • Scaffold our plugin
  • Register our scripts and styles
  • Create our build environment with Parcel
  • Create our block
  • Give it some style with astroturf

Create our scaffolding

However you want to do it, do it!

plugins/your-plugin-name/ # → Root of your plugin
├── src/           
│   ├── styled/
│   │   └── index.js      # → A CSS-in-JS component
│   └── block.js          # → A block
├── package.json
└── your-block-name.php   # → Plugin base file

Registering our scripts and styles

If you’ve never made a plugin before: in a similar way to how a WordPress theme really only technically needs a style.css and index.php file to function, creating a plugin is ludicrously easy at its core.

And, just as with a theme it can become very complex very fast. And while there is no single right way to handle that complexity there are n+1 decidedly wrong ways. What I’m demonstrating here is neither right or wrong. It is intentionally the bare minimum. It’s fine for our purposes — registering a single block.

As a quick aside, recently I’ve been working with the alpha version of Clover, Roots’ answer to the question "What is Modern WordPress Plugin Development?", and it has been pretty phenomenal so far. But we can talk about working with Gutenberg in the context of Clover some other time.

For now, in our your-block-name.php:

#1: Identify our plugin with WordPress’ meta data

<?php
/**
 * Plugin Name: Your Block Name
 * Plugin URI:  https://github.com/your-git/your-block-git
 * Description: Says "Hello, World!"
 * Version:     0.1.0
 * Author:      Kelly Mears
 * Author URI:  https://tinypixel.io
 * License:     MIT License
 * Text Domain: your-block-domain
 * Domain Path: /resources/lang
 **/

Everything else that follows can be done as an action bound to WordPress’ init.

#2: Register our block JavaScript

wp_register_script(
	'demo-block-js',
	plugin_dir_url(__FILE__) .'dist/block.js', 
	[
		'wp-element',
		'wp-i18n',
		'wp-blocks',
	],
	'',
	null,
	true
);

#3: Register our block CSS

wp_register_style(
    'demo-block-public-css',
    plugin_dir_url(__FILE__) .'dist/block.css',
    [],
    null
);

#4: Register the scripts with the block editor

register_block_type(
    'demo/block', [
        'editor_script' => 'demo-block-js',
        'editor_style'  => 'demo-block-public-css',
        'style'         => 'demo-block-public-css',
    ]
);

This is all decidedly straight forward. One thing to note is that in step #4 the first parameter (demo/block) must match the name of the block we provide in our JS.

Creating our build environment

There is no way in the context of this article to cover all the underlying software tools which underpin the reality of what I’m going to demonstrate. You can literally read entire O’Reilly-grade tomes dedicated wholly to any one of them: webpack, babel, node package manager and their plugins, configurations, and interplay.

Instead we’re going to largely punt this by leaning on a fantastic tool called Parcel.JS to do almost all of our dirty work for us. You could use something like CGB Scripts to set a build chain and asset pipeline on your behalf, but we’re going to do a magic trick in a little bit that is incompatible with that boilerplate. Parcel provides us just enough flexibility to do what we need to do and just enough structure to get it done safely despite our ignorance.

#1: Install Parcel

yarn add parcel-bundler --dev

This command will leave you with a node_modules folder containing 98MB of god-knows-what along with a package.json file. Let’s fill both of them up.

#2: Install some basic necessities

For WordPress we’ll need:

yarn add @wordpress/babel-preset-default @wordpress/blocks @wordpress/element --dev

For building our styles we’ll need:

yarn add astroturf postcss-modules precss --dev

And, that’s that.

When Parcel finishes resolving all your dependencies you should have something that looks kinda like this in package.json:

{
  "devDependencies": {
    "@wordpress/babel-plugin-makepot": "^3.0.0",
    "@wordpress/babel-preset-default": "^4.0.0",
    "@wordpress/blocks": "^6.1.0",
    "@wordpress/element": "^2.2.0",
    "astroturf": "^0.9.1",
    "parcel-bundler": "^1.12.1",
    "postcss-modules": "^1.4.1",
    "precss": "^4.0.0"
  }
}

Keep this file open so we can finish with a couple manual tweaks:

#3: Configure build

We just need to specify a couple things in our package.json: where our files are coming from and some modules that we’ll need to work with astroturf, our CSS-in-JS solution:

a. Specify file paths and build commands:

"main": "src/block.js",
"source": {
  "src/**/*.css": "dist/styles"
},
"scripts": {
  "start": "parcel src/block.js",
  "watch": "parcel watch src/block.js",
  "build": "parcel build src/block.js --no-content-hash"
},

b. Configure postcss:

"postcss": {
  "modules": true,
  "plugins": [
    "precss"
  ]
},

c. Configure babel

"babel": {
  "presets": [
    "@wordpress/default"
  ],
  "plugins": [
    ["astroturf/plugin"]
  ]
}

When all is in place let’s see if it builds: yarn start.

Green light? Alright!

Build our block

This is the most basic version of block it’s possible to write:

const { createElement } = wp.element;
const { registerBlockType } = wp.blocks;

registerBlockType('demo/block', {
  title: "Hello World",
  description: "A simple demonstration block",
  icon: "admin-site",
  category: "common",

  edit: function() {
    return <div> ( ∙_∙)>⌐■-■ </div>;
  },

  save: function() {
    return <div> (⌐■_■) </div>;
  }
});

In the first two lines we import the packages that we need from WordPress.

The rest is a call to a single function registerBlockType that contains the full definition of our block. Run yarn build and you should see your block in the editor. Love that "Deal With It" emoticon.

While we could continue exactly as we are right now, I think you might already be sweating what maintainability is going to look like as your block grows in scope. That’s because you are very smart. Let’s assuage your fears and intelligently break edit and save out of registerBlockType:

const edit = () => {
  return <div> ( ∙_∙)>⌐■-■ </div>;
}

const save = () => {
  return <div> (⌐■_■) </div>;
}

registerBlockType('demo/block', {
  title: "Hello World",
  description: "A simple demonstration block",
  icon: "admin-site",
  category: "common",
  edit,
  save
});

Much better. Deep breath. From here you can, and probably will, move them out into their own files and use the import syntax to bring them in for registration, but I’ll leave you to handle that on your own.

Instead, let’s see what all this CSS-in-JS is about.

Styling our block with astroturf

Astroturf is basically amazing. It lets us style our work directly in JS, and then during our build uses babel to pull all the styles we write in JS out into regular good, old-fashioned and inarguably legitimate .css files. What’s more — these CSS rules are automatically scoped using a random hash, so they are very good at overruling the oft heavy-handed specificity of the editor’s built-in stylesheets. If you don’t understand the benefits you might try to apply some styles without it and see how that suits you. I don’t like it at all, personally.

But, I ❤️ astroturf.

Let’s try it out in src/styled/index.js:

import styled, { css } from 'astroturf';

export const styles = css`
  .pantone {
    padding: 1rem;
    width: 300px;
    max-width: 100%;
    background: rgb(219, 112, 147);
    color: rgb(255, 255, 255);
  }
`;

Now, apply it in src/block.js through an import and JSX’s className convention:

...

import { styles } from './styled';

const edit = () => {
  return <div className={ styles.pantone }> ( ∙_∙)>⌐■-■ </div>;
}

const save = () => {
  return <div className={ styles.pantone }> (⌐■_■) </div>;
}

...

If you build this and check it out you should see our new block decked out in the hallmark color of 2019.

Let’s try one other thing in src/styled/index.js. Append a new export:

export const PantoneSwag = styled('div')`
  ${styles.pantone}
`

This exports a new JSX element that behaves exactly like a div but with all the aesthetics described by styles.pantone.

You aren’t limited to making styled('div')s or styled('h1')s, either. You can make a styled a version of any object that’s been imported — including core blocks. Think about that while we use our new styles in src/block.js by modifying our previous import as follows:

import { styles, PantoneSwag } from './styled';

And then replacing our old edit handler’s div with the new PantoneSwag "Styled Component" (get it?)

const edit = () => {
  return <PantoneSwag> ( ∙_∙)>⌐■-■ </PantoneSwag>;
}

const save = () => {
  return <div className={ styles.pantone }> (⌐■_■) </div>;
}

Note that I didn’t replace the div in the save handler. That’s because the block editor cannot deal with parsing React elements unless you jump through some serious hoops. It’s frustrating and honestly, in my opinion, one of the biggest failures of the new editor; WordPress has left developers in a frustrating limbo between modern React-powered goodness and the old editor’s relative stability.

You can try your hand with RawHTML and renderToString, which are both exported from wp.element. But, for my money, it’s easier to just deal with this restriction as I have above. WordPress’ parsing engine leaves a lot to be desired.

That said, for the edit handler the styled() syntax opens up a lot of doors (example: setting a color based on a variable, like <PantoneSwag color='rebeccaPurple' />).

By the way, you can use @mixins, nest your rules, and even do things like import './my-scss-file.scss'. So, if you prefer to do all of your composition in a regular CSS file and then import it that has a set of advantages all its own.

Closing notes

If you want to see a slightly more fully featured block that utilizes the above build process you can check out the source for Tiny MDE, a Markdown editor block I wrote which was made in a similar fashion.

I’d like to write a couple more pieces for Roots on the WordPress editor because I think that while it’s a bit rough around the edges it holds a lot of potential for WordPress devs to build amazing things. If you’d like to hear about something in particular you can find me on Roots Discourse or on the Roots Slack, which is available to patrons, but at the moment I’m considering:

  • Making custom blocks to replace trivial ACF components
  • Working with state and the WordPress JavaScript API
  • Creating a more advanced plugin building workflow (once Clover is released).

Until then!

Join the discussion on Roots Discourse

Help support our open-source development efforts

Help grow Roots

Join over 6,000 subscribers on our newsletter to get the latest Roots updates, along with occasional tips on building better WordPress sites.

Looking for WordPress plugin recommendations, the newest modern WordPress projects, and general web development tips and articles?

“Easily the best WordPress email I get.” Colin OBrien

Follow us on Twitter @rootswp

Ready to checkout?