creating your first gutenberg plugin – part I

March 12, 2019

As wordpress 5 ships with a new editor – Gutenberg – it’s time to take a deep dive under the hood.

The goal of this article series is to memoize the concepts and provide just enough know-how to get you kick-started. So let’s talk about..

prerequisites

Gutenbergs is technologically based on top of React and makes heavy use of modern React application design:

  • you should be familiar with React and JSX and how to deal with props and state
  • the internal API makes heavy use of higher order functions to mix in editor functionality
  • editor state is managed by redux. You don’t need to know much about it. Basically one manages the global editor state by using predefined callbacks imported from internal APIs.

The developer handbook is really excellent. The editor source itself is organized inside a lerna multi package repo, all npm packages are shipping in the namespace @wordpress. This data is available globally. E.g. registerBlockType is available from window.wp.blocks.

getting started

I want to ship these blocks independently so I’ve created a plugin to manage my custom blocks. Of course you can ship your custom blocks within your theme, too.

A minimal plugin definition could look like the following:

plugin.php

<?php
/**
 * Plugin Name: blocks tutorial
 * Plugin URI: http://jscouch.de/creating-your-first-gutenberg-plugin-1/
 */

if (!defined('ABSPATH')) {
  exit;
}

function gutenbergEditorAssets() {
  wp_enqueue_script(
    'blocks-tutorial-js',
    plugins_url('blocks-tutorial/plugin.js', __DIR__),
    ['wp-blocks', 'wp-editor', 'wp-i18n', 'wp-element', 'wp-components'],
    false,
    true
  );
}
add_action('enqueue_block_editor_assets', 'gutenbergEditorAssets');

This file registers a new wordpress plugin (see top comment) as well as our javascript file we need to extend the editor functionality.

So let’s register our first block:

plugin.js

wp.blocks.registerBlockType('vendor/blockname', {
  title: wp.i18n.__('example block'),
  category: 'common',
  edit: function(props) {
    return React.createElement('div', {
      className: props.className,
    }, 'this block shows inside your editor instance');
  },
  save: function(props) {
    return React.createElement('div', {
      className: props.className,
    }, 'this block shows in your rendered markup');
  }
});

That’s the pretty minimum to get a hello world-example up and running. Of course it’s just displaying a dumb line for the moment, but it shows the concept pretty well.

The registerBlockType function takes two arguments, the block name and a configuration object.

Take another look at the edit/save methods. The className of our props depends on the block name we pass as first argument. The auto generated css class transforms to wp-blocks-vendor-blockname, which we can use to encapsulate styles accordingly.

The title will be translated (the double underscore) by the wordpress i18n package. If there’s no translation available it prints the original string. Even if you don’t register any translation you should use the i18n package and you are prepared for further extensions.

The configuration object indicates that one can pass any React Component as edit or save property. Flexbility? Check!

enhancing the dev experience

With webpack and babel properly configured the upper code transforms the following:

plugin.js

import { registerBlockType } from '@wordpress/blocks'
import { __ } from '@wordpress/i18n';

registerBlockType('vendor/blockname', {
  title: __('example block'),
  category: 'common',
  edit(props) {
    return <div className={props.className}>this block shows inside your editor instance</div>;
  },
  save(props) {
    return <div className={props.className}>this block shows in your rendered markup</div>;
  }
});

This looks way better as we got rid of the global variable access and the locally installed corresponding editor packages are used. This way the IDE supports us while coding, most errors appear while typing and we may detect future update incompatibilities by updating our local dependencies.

This is crucial as by now one can jump to declaration inside our IDE – well commented source code really worth a look. A lot of documentation exists as markdown files alongside to the related wordpress package.

The key here is to map external packages to their related globals using webpack externals.

webpack.config.js

module.exports = {
  externals: {
    react: 'React',
    'react-dom': 'ReactDOM',
    '@wordpress/blocks': ['wp', 'blocks'],
    '@wordpress/block-library': ['wp', 'blockLibrary'],
    '@wordpress/components': ['wp', 'components'],
    '@wordpress/data': ['wp', 'data'],
    '@wordpress/date': ['wp', 'date'],
    '@wordpress/editor': ['wp', 'editor'],
    '@wordpress/element': ['wp', 'element'],
    '@wordpress/i18n': ['wp', 'i18n'],
    '@wordpress/utils': ['wp', 'utils'],
    'lodash': ['lodash'],
  },
}

As redux is running we can inspect the editor state transformations under the hood by using the excellent chrome redux devtools extension.

screenshot from redux devtools

Looks like we are armed with advanced debugging skills now. It’s time to show you something real.

an extended code block

Within the already shipping standard blocks there’s an element called “Code”. This element renders any text monospaced and keeps your formatting. We’re going to write a new version of this widget providing more information.

We want to manage the following entities:

  • the filename/title of the code snippet we want to share
  • the content
  • the programming language

These entities are managed by another state alike mechanism called attributes. Within attributes – provided by props – we define data types and tell the editor how it can aggregate the entities from our saved markup.

Whenever the markup of our save method changes the block validation will fail. Therefore it might be a good idea to start with the save component whenever you develop a new block.

the save component

registerBlockType('jscouch/code', {
  title: __('DP Code'),
  icon: <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path d="M0,0h24v24H0V0z" fill="none"/>
    <Path d="M9.4,16.6L4.8,12l4.6-4.6L8,6l-6,6l6,6L9.4,16.6z M14.6,16.6l4.6-4.6l-4.6-4.6L16,6l6,6l-6,6L14.6,16.6z"/>
  </SVG>,
  category: 'formatting',
  save({attributes, className}) {
    return (
      <div className={className}>
        <h4>
          <span>{attributes.filePath}</span>
        </h4>
        <pre>
          <code className={attributes.language}>{attributes.content}</code>
        </pre>
      </div>
    );
  },
});

We are rendering the attributes filePath and content as DOMNode content here. Let’s throw in our corresponding attributes.

attributes

registerBlockType('jscouch/code', {
  save({attributes, className}) {
  },
  attributes: {
    language: {
      type: 'string',
      default: 'javascript',
    },
    filePath: {
      type: 'string',
      source: 'text',
      selector: 'h4 span',
    },
    content: {
      type: 'string',
      source: 'text',
      selector: 'code',
    },
  },
});

These attributes rendered as inner content are mapped back by the selector definition inside its’ attributes definition. The language attribute is only used as a simple string and doesn’t use a selector. It will be stored within the widgets meta data.

This widget only deals with plain text, so the source of filePath and content is defined as text. We can tell the editor within the block definition that this widget doesn’t support rich html editing:

supports

registerBlockType('jscouch/code', {
  save({attributes, className}) {
  },
  attributes: {
  },
  supports: {
    html: false,
  },
});

We still need to declare an edit component. We’ll make use of the editors’ <PlainText /> component from @wordpress/editor.

simple edit component

class CodeEditComponent extends Component {
  setFilePath = (filePath) => {
    this.props.setAttributes({filePath});
  };

  setContent = (content) => {
    this.props.setAttributes({content});
  };

  render() {
    const {attributes, className, mergeBlocks} = this.props;

    return (
      <div className={className}>
        <h4>
          <PlainText
            tagName='span'
            value={attributes.filePath}
            onChange={this.setFilePath}
            placeholder={__('<> path/to/file…')}
          />
        </h4>
        <pre>
          <PlainText
            tagName='code'
            value={attributes.content}
            onChange={this.setContent}
            placeholder={__('your code snippet…')}
            onMerge={mergeBlocks}
          />
        </pre>
      </div>
    );
  }
}

This will render two textareas where we can inline edit the content already. Compare the markup with the content provided by the save component. We only replace the inner rendering blocks by editable components.

Like setState we are calling setAttributes to persist any user change. The setter setAttributes is available from the props of the edit component.

We still need a way to edit the programming language. Whenever a block is focussed it can inject content into the block settings shown in the right sidebar. Let’s extend it with a dropdown so we can select the programming language.

complete edit component

import {createBlock, registerBlockType} from '@wordpress/blocks'
import {Component, Fragment} from '@wordpress/element';
import {InspectorControls, PlainText} from '@wordpress/editor';
import {__} from '@wordpress/i18n';
import {PanelBody, Path, SelectControl, SVG,} from '@wordpress/components';

class CodeEditComponent extends Component {

  static languages = [
    {
      value: 'javascript',
      label: 'javascript',
    },
    {
      value: 'typescript',
      label: 'typescript',
    },
  ];

  setLanguage = (language) => {
    this.props.setAttributes({language});
  };

  setFilePath = (filePath) => {
    this.props.setAttributes({filePath});
  };

  setContent = (content) => {
    this.props.setAttributes({content});
  };

  render() {
    const {attributes, className, mergeBlocks} = this.props;

    return (
      <Fragment>
        <InspectorControls>
          <PanelBody>
            <SelectControl
              type='string'
              label={__('programming language')}
              value={attributes.language}
              onChange={this.setLanguage}
              options={CodeEditComponent.languages}
            />
          </PanelBody>
        </InspectorControls>
        <div className={className}>
          <h4>
            <PlainText
              tagName='span'
              value={attributes.filePath}
              onChange={this.setFilePath}
              placeholder={__('<> path/to/file…')}
            />
          </h4>
          <pre>
            <PlainText
              tagName='code'
              value={attributes.content}
              onChange={this.setContent}
              placeholder={__('your code snippet…')}
              onMerge={mergeBlocks}
            />
          </pre>
        </div>
      </Fragment>
    );
  }
}
registerBlockType('jscouch/code', {
  edit: CodeEditComponent, 
});

The complete edit markup is now wrapped inside a React Fragment. The block settings are extended by using InspectorControls from the editor package. Inside we make use of <PanelBody/> and inject a <SelectControl /> to pick the related programming language.

So – what’s missing? We should care about style encapsulation, provide a transformation from core/code to jscouch/code implementations back and forth and we need a migration path if the markup inside the save method changes between releases. And of course there’re RichText, InnerBlocks, MediaPlaceholder and other core blocks we haven’t had a look on it – yet.

If you’ve got any feedback feel free to contact me anytime. Thank you very much for your attention.

Leave a comment