Gutenbergはじめてのカスタムブロック

WordPress5.0から採用されるGutenbergだが、決定的に新しい概念として、「ブロック」というものがある。ブロックの正体は <!--wp:xxx--><!--/wp:xxx--> というHTMLコメントで囲まれた独自タグのようなものだが、これは独自に追加することができる。ショートコードのようなものだと思ってもらっていい。

このコメント、筆者はあまり好きではない。純粋なHTMLを保存したかった。

これにかなり近い機能として、現時点でも使えるプラグインに Shortcake というものがあり、筆者も便利に使っていたのだが、こちらはBackbone製でおいそれとカスタマイズできるものではないので、公式採用にいたらなかったのだろう。

なにはともあれ、Gutenbergをカスタマイズして使いこなすにあたって避けられられないのがブロックである。今回はReactがなんなのかをよくわからないまま、とりあえずコピペでブロックを作ってみたい。

Gutenbergの拡張ドキュメント

日本語で書かれた資料としては、明日(3/1)開催のGutenberg Meetup(筆者も登壇する)の主催者である宇都宮さんのQiita記事に詳しい。ReactやBabelなどのフロントエンドに詳しい人向けの内容なので、本稿を読んで物足りなかった人は読んでみてほしい。

もっともたやすくカスタムブロックを追加できるのは、Github上のGutenbergリポジトリである。Gutenbergのドキュメントは非常によく整備されており、各フォルダのREADMEを読むだけで実装は可能だ。WordPressの文化をよく理解した人たちが作っているので、いきなり npm install を求めることはなく、素のJavascriptだけでそれなりに動くよう書かれている。

Gutenbergのドキュメント整備力はなかなかのもの。

ステップ1. エントリーポイントの作成

さて、まずは準備段階として、プラグインを作成しよう。プラグインフォルダに capital-block/capital-block.php というファイルを作成し、こんな内容を書く。

<?php
/**
 * Plugin Name: Capital Block
 * Plugin URI: https://capitalp.jp/
 * Description: Original custom blocks for Gutenberg.
 * Version: 0.8.0
 * Author: Takahashi_Fumiki
 * Author URI: https://capitalp.jp
 * License: GPL-3.0 or later
 * License URI: https://www.gnu.org/licenses/gpl-3.0.html
 * Text Domain: capitalb
 * Domain Path: languages
 */


defined( 'ABSPATH' ) || die();

これで何もしないプラグイン Capital Block ができたので、とりあえず有効化しよう。これで入り口が完成した。

ステップ2. Javascriptの読み込み

GutenbergはJavascriptが大半を占めているので、Javascriptを読み込ませよう。まずは capitalp-block/block.js というファイルを作成し、先ほど作成した capitalp-block.php に読み込みの指示を書く。

add_action( 'enqueue_block_editor_assets', function() {
  wp_enqueue_script(
    'myplugin-block',
    plugins_url( 'block.js', __FILE__ ),
    [ 'wp-blocks', 'wp-element' ]
  );
} );

これは先ほど紹介したREADMEに書いてあるコードをほとんどコピペしたものだ。ポイントは下記の通り。

  • Javascriptの読み込みは普通 wp_enqueue_scripts あるいは admin_enqueue_scripts だが、Gutenbergにおいては enqueue_block_editor_assets となっている。
  • wp-blocks および wp-element はカスタムブロックが依存するファイル。wp-element は実のところ、Reactへのショートハンドである。なぜReactと直接参照しないかというと、WordPressよりも先にReactがなくなる、あるいはReactを使わなくなる可能性があるので、抽象化しているわけだ。

これでエディターを開くと、block.js が読み込まれるはずだ。読み込まれたか不安な人は、alertでも書いておけば良い。

ステップ3. ブロックを記述する

さて、ブロックを記述するのだが、とりあえずREADMEのコードをコピペしてみよう。これは Random Image というブロックで、画像のプレースホルダーサービス lorempixel.com からジャンルを選んだ上でランダムに画像を表示するブロックのようだ。

( function( blocks, element ) {
  var el = element.createElement,
    source = blocks.source;

  function RandomImage( props ) {
    var src = 'http://lorempixel.com/400/200/' + props.category;

    return el( 'img', {
      src: src,
      alt: props.category
    } );
  }

  blocks.registerBlockType( 'myplugin/random-image', {
    title: 'Random Image',

    icon: 'format-image',

    category: 'common',

    attributes: {
      category: {
        type: 'string',
        source: 'attribute',
        attribute: 'alt',
        selector: 'img',
      }
    },

    edit: function( props ) {
      var category = props.attributes.category,
        children;

      function setCategory( event ) {
        var selected = event.target.querySelector( 'option:checked' );
        props.setAttributes( { category: selected.value } );
        event.preventDefault();
      }

      children = [];
      if ( category ) {
        children.push( RandomImage( { category: category } ) );
      }

      children.push(
        el( 'select', { value: category, onChange: setCategory },
          el( 'option', null, '- Select -' ),
          el( 'option', { value: 'sports' }, 'Sports' ),
          el( 'option', { value: 'animals' }, 'Animals' ),
          el( 'option', { value: 'nature' }, 'Nature' )
        )
      );

      return el( 'form', { onSubmit: setCategory }, children );
    },

    save: function( props ) {
      return RandomImage( { category: props.attributes.category } );
    }
  } );
} )(
  window.wp.blocks,
  window.wp.element
);

さて、このコードを読むためにいくつか説明をしよう。まず、冒頭の el である。おまえはいったいなんなのだ?

これは React.createElement というメソッドのショートハンドで、Reactはこのメソッドを使ってDOMオブジェクトのようなものを作成する。 <p></p> と書く代わりに、 React.createElement('p') と書くわけだ。これを返すと、Reactはpタグを描画する。Javascriptに親しんだ人なら、 document.createElement('div')と書いたことがあるかもしれないが、似たようなものだ。今回のケースでは、毎回 wp.element.createElementと書くのがめんどくさいので、 var el = element.createElement としている。

el( 'option', { value: 'sports' }, 'Sports' )のような表記は、<option value="sports">Sports</option>のようなHTMLとして描画される。ただし、HTMLとまったくイコールなわけではない。

続いて、 blocks.registerBlockType である。ここで新しいブロックを登録しているわけだ。引数は ブロックのID(myplugin/random-image)と設定である。設定はオブジェクトになっており、属性を持っている。 titleiconについては特に説明はいらないだろう。categoryはGutenbergのブロックがリストに並ぶときに使われるようだ。

ブロックはカテゴリーごとにわかれて表示される

editはブロックを編集している時の中身を表示するメソッド、そしてsaveは保存すべき内容を返すメソッドである。このコードでは、RandomImageというメソッドを定義することで、editsaveの共通部分を切り出している。

propsという変数は、おそらくそのブロックが持つ設定値を保持するプロパティだ。Reactでは、このpropsを持ち回ることで描画と保存を一方向同期することができる。ポイントとしては、 props.category = 'animal' とするのはダメで、 props.setAttributes({category: 'animal'}) としなければならない。やや冗長ではあるが、このように書くことで、レンダリングが自動的に反映される仕組みになっている。

結論

さて、ほぼコピペではあるが、カスタムブロックを追加することができた。思ったほど難しくはない印象だ。もちろん、動的なブロック(ex. ログインしているユーザーにだけ内容を表示)も作ることはできるのだが、本日はここら辺で終わりとしよう。続きは Gutenberg Meetupでお届けする予定だ。

有料会員向けに、ブロックを実際にカスタマイズするところを撮影した動画があるので、そちらをご覧いただきたい。また、Capital Pの有料会員は、管理画面から「聖書」というカスタム投稿タイプをGutenbergで編集できるようにしているので、使ってみてほしい。

追記

Gutenberg Meetup Vol.1で発表した資料を添付しておく。セッションの内容はだいたいこの記事と同じである。

続きの 3% を読み、添付されたファイルにアクセスするには、Gumroadでライセンスキーを取得してください!

“Gutenbergはじめてのカスタムブロック”への3件の反応

コメントを残す

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください