WordPress Logo WordPress Gutenberg ブロックの作成

ブロックエディタ Gutenberg で独自のブロックタイプを作成(自作)するための基本的なことについての覚書です。

環境構築などはせずに、独自のブロック(カスタムブロック)を作成する方法を掲載しています。内容的には主に日本語の公式リファレンスの「ブロック」を元にしています。

以下で作成するブロックは単純なもので、JSX も使用しないので環境構築(Babel の設定など)の必要はありませんが、ローカルに WordPress の環境があることを前提にしています。

更新日:2020年11月02日

作成日:2020年09月03日

2019年09月05日(追記)ファイル構成や namespace など一部を修正しました。

初めてのブロック作成

現在はブロックに関する日本語の公式リファレンスやチュートリアルが豊富にあります。

以下は英語のブロックエディターの公式ページ及びブロックサンプルのリンクです。

このページ以外にも以下のようなブロックの作成に関するページがあります。

ブロック作成関連ページ

参考元:ブロック/初めてのブロックタイプ

ブロックは WordPress の「プラグイン」や「テーマの一部」として作成して、ブロックエディターに追加することができます。

ブロックを作成するには、ブロックがどのようなものかを定義する JavaScript ファイル(ブロックを実装するブロック用のスクリプト)とそのブロック用のスクリプトを読み込む PHP ファイルが必要です。

以下では主にプラグインとしてブロックを作成するので、ブロック用のスクリプトを読み込むためのファイルはプラグインファイルとして作成します。プラグインと言っても単にプラグインとして認識させるだけの単純なものです。テーマで拡張する方法については簡単に説明しています。

また、ブロック用のスクリプトを読み込む方法としては、enqueue_block_editor_assets フックを使う方法と register_block_type と init フックを使う方法があります。

プラグインとして作成

プラグインとして WordPress に認識させるには、プラグインのディレクトリ wp-content/plugins/ に新たにディレクトリを作成し、プラグインのファイルを配置し、プラグインヘッダと呼ばれる情報を記述します。

注意することとしては、プラグインを公式ディレクトリに登録しない場合でも、作成するプラグインの名前は他のプラグインと重複しないユニークな名前にする必要があります。また、使用する関数名も他のプラグインと重複するとエラーになったり、動作しなくなる原因になるので同様にユニークな関数名にする必要があります。

参考リンク:Codex(日本語)/プラグインの作成

以下では「My First Block」という名前のプラグインとして、単に固定の文字列を表示するだけの単純なブロックを作成します。

プラグインのディレクトリ

ブロックのプラグインには最低でもメインのプラグインファイル(PHP)とブロック用のスクリプト(JavaScript)の2つのファイルが必要です。プラグインを配置する wp-content/plugins/ に新たにブロック用のディレクトリを作成して、その中に必要なファイルを配置します。

ディレクトリの名前は、プラグイン名を元に単語と単語をハイフンでつないだものが一般的に使用されます。この例では、プラグイン名を「My First Block」とするので、ディレクトリ名は「my-first-block」のようにしていますが、ディレクトリ名は任意の名前を付けられます。

作成したプラグインのディレクトリの直下にはプラグインのメインの PHP ファイルを配置します。この PHP ファイルにはプラグインヘッダやブロック用のスクリプトやスタイルの読み込みなどを記述します。

この例ではファイル名はわかりやすいようにディレクトリ名と同じ名前(例 my-first-block.php)にしていますが、index.php や任意の名前でも問題ありません。

以下のような構成を作成します。この時点ではファイルの中身は空です。

  • my-first-block.php:プラグインのメインの PHP ファイルで、プラグインヘッダやブロック用のスクリプトやスタイルの読み込みなどを記述します。
  • block.js:ブロック用の JavaScript のファイルで、ブロックの実装(ブロックの定義と登録)を記述します。名前は任意の名前を使えます。
wp-content
└── plugins
    └── my-first-block //新規に作成するプラグインのディレクトリ
        ├── my-first-block.php  //プラグインファイル
        └── block.js  //ブロック用のスクリプト

プラグインヘッダ

WordPress にプラグインとして認識させるには、プラグイン本体の PHP ファイルにプラグインヘッダと呼ばれるコメントを記述する必要があります。

プラグインとして動作させるためには、少なくともプラグイン名を指定する Plugin Name を記述する必要があります。この例の場合、プラグイン名を「My First Block」としているので、my-first-block.php の先頭に以下を記述します。

wp-content/plugins/my-first-block/my-first-block.php
<?php
/*
Plugin Name: My First Block
*/

セキュリティ対策のコード

以下のように外部から直接 PHP ファイルにアクセスされる事を防ぐためのコードを追加します(6行目)。

wp-content/plugins/my-first-block/my-first-block.php
<?php
/*
Plugin Name: My First Block
*/

defined( 'ABSPATH' ) || exit;  
//または if ( ! defined( 'ABSPATH' ) ) exit; 

ABSPATH は WordPress がインストールされている(wp-load.php または wp-config.php が置かれている)ディレクトリのフルパスが入っている定数で、defined は指定した名前の定数が存在するかどうかを調べる関数です。

もし、直接ファイルにアクセスされた場合は wp-load.php を通らないため ABSPATH という定数が無いので、その場合は exit するようにしています。

上記のプラグインファイル(my-first-block.php )を保存して、管理画面の「プラグイン」を開くと以下のようにプラグインヘッダー(Plugin Name:)に記述した「My First Block」と言う名前のプラグインがリストされているので「有効化」をクリックして有効化します。

ブロック用のスクリプトの読み込み

ブロック用のスクリプトやスタイルを読み込む(登録する)方法は、enqueue_block_editor_assets フックを使う方法と register_block_type と init フックを使う方法がありますが、以下では enqueue_block_editor_assets を使う方法から説明しています。

ブロック用のスクリプト block.js に、動作確認のため以下(JavaScript)を記述して保存します。

wp-content/plugins/my-first-block/block.js
console.log('Hello from my first block!');

ブロック用のスクリプト block.js を読み込むためにメインのプラグインファイル my-first-block.php に以下(9〜16行目)を追加します。

wp-content/plugins/my-first-block/my-first-block.php
<?php
/*
Plugin Name: My First Block
*/

defined( 'ABSPATH' ) || exit; //外部からのアクセスを防止

//ブロック用のスクリプトの読み込み
function my_first_block_enqueue() {
  //ブロック用のスクリプトを登録し、出力用のキューに入れる
  wp_enqueue_script(
    'my-first-block-script', //スクリプトを識別するためのハンドル名
    plugins_url( 'block.js', __FILE__ )  //スクリプトの URL
  );
}
add_action( 'enqueue_block_editor_assets', 'my_first_block_enqueue' );

この例の JavaScript の読み込みには enqueue_block_editor_assets アクションフックで wp_enqueue_script() を使って、スクリプトがエディターにのみ読み込まれるようにしています。

enqueue_block_editor_assets フックはブロックエディターがロードされる際に呼び出され、wp_enqueue_script で指定した JavaScript ファイル block.js をエンキューします。

スクリプトの URL は plugins_url を使って取得しています。

※公式のチュートリアル「初めてのブロックタイプ」では init フックと wp_register_script 及び register_block_type を使っています。

新規投稿作成画面(または既存の投稿の編集画面)を開いて、ブラウザのデベロッパーツール(F12キー)でコンソールを表示して、JavaScript ファイルの読み込みを確認ます。

block.js に記述した console.log() の文字列「Hello from my first block!」が表示されていれば問題なく JavaScript が読み込まれています。

依存するスクリプトの指定

ブロック用のスクリプトの読み込みでは、ブロックとして機能するために必要な依存するスクリプトを指定する必要があります。

wp_enqueue_script() の第3パラメータに依存するスクリプトのハンドル名を配列で指定します。

この依存関係は作成するブロックが何に依存するかによって変わってきますが、この例ではブロックの実装で registerBlockType と createElement を使用するため wp-blocks と wp-element を指定します。

function my_first_block_enqueue() {
  wp_enqueue_script(
    'my-first-block-script',
    plugins_url( 'block.js', __FILE__ ),
    array( 'wp-blocks', 'wp-element' ) //依存するスクリプト(追加)
  );
}
add_action( 'enqueue_block_editor_assets', 'my_first_block_enqueue' );

ブロック用スクリプトを読み込むプラグインファイルは以下のようになります。これで完成です。

wp-content/plugins/my-first-block/my-first-block.php
<?php
/*
Plugin Name: My First Block
*/
 
defined( 'ABSPATH' ) || exit;  
 
//ブロック用スクリプトの読み込み
function my_first_block_enqueue() {
  //ブロック用のスクリプトを登録し出力用のキューに入れる
  wp_enqueue_script(
    'my-first-block-script', //スクリプトのハンドル名
    plugins_url( 'block.js', __FILE__ ), //スクリプトの URL
    array( 'wp-blocks', 'wp-element' )  //依存スクリプト
  );
}
//エディターでのみブロックのアセットをエンキューするためのフック
add_action( 'enqueue_block_editor_assets', 'my_first_block_enqueue' );    

上記では enqueue_block_editor_assets フックを使用していますが、この他に register_block_type と init フックを利用する方法もあります(後述)。

依存するスクリプト(パッケージ)

ブロックを作成する際にブロック用のスクリプトの読み込みで、ブロックのタイプにより依存関係パラメーターに含める JavaScript ライブラリ(パッケージ)がいくつかあります。

代表的なハンドル名には wp-element や wp-blocks、wp-components、wp-i18n、wp-editor などがあり、通常、少なくとも registerBlockType を使うための wp-blocks と createElement を使うための wp-element を依存スクリプトに含める必要があります。

また、後述のブロックの実装でも、ブロックの定義(作成)に必要なコンポーネントや関数を JavaScript ライブラリ(パッケージ)から読み込む必要があります。

以下はブロックに使用する最も一般的なパッケージの概要です。

ハンドル名 説明
wp-element
packages/element
要素の作成に使用できる React の上の抽象化レイヤーである wp.element へのアクセスを提供します。この中には React コンポーネントに対応する WordPress コンポーネントがあります。例えば、React.Component に対応する Component や React.Fragment に対応する Fragment、React.createElement に対応する createElement などが含まれています。
wp-blocks
packages/blocks
ブロックに使用されるコンポーネントと機能へのアクセスを提供する wp.blocks を参照します。registerBlockType などが含まれています。
wp-components
packages/components
ほとんどの UI 入力コンポーネントは wp.components 内にあります。例としては、さまざまなテキスト入力、select ボックス、チェックボックス、ラジオボタン、ドラッグ可能なコンポーネント、ボタン、カラーピッカー、日付ピッカーなどがあります。また、エディターのブロックツールバーや設定サイドバー(InspectorControls)のコンテンツに使用できる UI コンポーネントもこの中にあります。
wp-i18n
packages/packages-i18n
必須ではありませんが、JavaScript 内で使用できる国際化機能(多言語化機能)へのアクセスを提供します。
wp-editor
packages/editor
packages/block-editor
RichText や画像/メディアアップローダー関連のコンポーネント、ツールバーまたはカスタムインスペクター(サイドバー)パネルを追加するためのコンポーネントなどがあります。但し、WordPress 5.3 以降 wp.editor のいくつかのコンポーネントは wp.blockEditor に移されています。
wp-edit-blocks wp-components、wp-editor、wp-block-library、及び wp-block-library-theme が依存している CSS のハンドルです。エディターのスタイルの依存関係として使用することで、それらのハンドルに関連するスタイルも確実にキューに入れられます。

例えば、TextControl というコンポーネントは wp-components の中(gutenberg/packages/components/src/)にあります。

enqueue_block_editor_assets / enqueue_block_assets

enqueue_block_editor_assets は ver 5.0 で新しく導入された、ブロックのスクリプトとスタイル(ブロックのアセット)をエディターでのみエンキューするためのフックです。

enqueue_block_assets も ver 5.0 で新しく導入されたフックで、こちらはエディターとフロントエンドの両方でブロックのスクリプトとスタイル(ブロックのアセット)をエンキューするために使用されます。

ブロックを作成するためのメインのスクリプトは管理エディター画面でのみ必要なため、enqueue_block_editor_assets フックで wp_enqueue_script を使用してブロックに必要なファイルを読み込むことができます。

また、エディターにのみ必要なスタイルがある場合は、それらも enqueue_block_editor_assets フックで wp_enqueue_style を使用して読み込みます。

エディターとサイトのフロントエンドの両方で必要なスタイルは enqueue_block_assets フックで wp_enqueue_style を使って読み込みます。

場合によっては、フロントエンドまたはバックエンドの両方で実行される JavaScript が必要になることがあります。 たとえば、スライダーブロックを作成している場合、両方のインスタンスでスライド機能を動作させる必要があります。 その場合、それらのスクリプトを enqueue_block_assets でロードできます。

フロントエンドでのみ読み込む

もし、エディターではなくフロントエンドにのみ適用するスタイルなどがある場合は、enqueue_block_assets フックを使用して ! is_admin() で判定して、スクリプトまたはスタイルをエンキューします。

function my_block_enqueue() {
  //管理画面(エディタ画面)以外では
  if(! is_admin()) {
    wp_enqueue_style(
      'my-block-front-only',
      plugins_url( 'style.css', __FILE__ ),
      array(),
      filemtime( plugin_dir_path( __FILE__ ) . 'style.css' )
    );
  }
}
add_action( 'enqueue_block_assets', 'my_block_enqueue');    

参考元:Enqueueing Scripts and Styles for Gutenberg Blocks

register_block_type

register_block_type() はブロックタイプを登録する関数で以下が書式です。

register_block_type( $name, $args )
パラメータ
  • $name:namespace(名前空間)を含むブロックタイプの名前(namespace/block-name)。※ブロック名は英数小文字かダッシュのみが使え、文字で始まる必要があります。
  • $args:ブロックタイプのプロパティを連想配列で指定

$name に指定するブロックタイプの名前は、ブロック用のスクリプト(ブロックの実装)でブロックを登録する関数 registerBlockType の第1パラメータ Block Name と同じにします(合わせます)。

$args に指定できるプロパティは以下のようなものがあります。

キー 説明
render_callback このブロックをレンダリングするために使用するコールバック関数
attributes 任意の属性値を格納した連想配列
editor_script エディター用スクリプトをブロックに関連付け(ハンドル名を指定)
editor_style エディター用スタイルをブロックに関連付け(ハンドル名を指定)
script フロントとエディター用スクリプトをブロックに関連付け(ハンドル名を指定)
style フロントとエディター用スタイルをブロックに関連付け(ハンドル名を指定)

style でエンキューされるスタイルシートは基本のスタイルで最初にロードされ、editor_style スタイルシートは後でロードされます。(スタイルシートによるスタイルの適用

前述のブロック用のスクリプトの読み込みでは、ブロックエディター用のフック enqueue_block_editor_assets を使用してブロックに必要なスクリプトを読み込みましたが、ブロック用のスクリプトやスタイルを wp_register_script() や wp_register_style() で登録しておいて register_block_type() でブロックに関連付けることもできます。

register_block_type() では、第2パラメータで以下のキーを使ってスクリプトやスタイルを関連付けます。

register_block_type(
  //名前空間/ブロック名
  'namespace/block-name',  
  array( 
    //スクリプトやスタイルをブロックに関連付け(登録)
    'editor_script' => 'エディター用スクリプトのハンドル名',
    'editor_style'  => 'エディター用スタイルのハンドル名',
    'script'        => 'フロントとエディター用スクリプトのハンドル名',
    'style'         => 'フロントとエディター用スタイルのハンドル名',
  )
);

前述の例のメインのプラグインファイル my-first-block.php を register_block_type() を使って書き換えると以下のようになります。

この場合、init フックを使用します。また、wp_enqueue_style を使ってエンキューするのではなく、wp_register_script でスクリプトを登録するようにします。これは、各カスタムブロックを登録するために関数も呼び出す必要があり、その関数が必要に応じてスクリプトをエンキューするためです。

<?php
/*
Plugin Name: My First Block
*/

defined( 'ABSPATH' ) || exit;  

function my_first_block_enqueue() {
  //ブロック用のスクリプトを wp_register_script で登録
  wp_register_script(
    //スクリプトを識別するためのハンドル名
    'my-first-block-script',
    //スクリプトの URL
    plugins_url( 'block.js', __FILE__ ),
    //依存するスクリプト
    array( 'wp-blocks', 'wp-element' ) 
  );
  
  //ブロックタイプの登録
  register_block_type( 
    //namespace を含むブロックタイプの名前
    'my-blocks/my-first-block', 
    array(   
      //ブロックのスクリプト block.js をエディタ用スクリプトとして関連付け
      //上記 wp_enqueue_script で登録したハンドル名 my-first-block-script を指定
      'editor_script' => 'my-first-block-script', 
    ) 
  );
}
add_action( 'init', 'my_first_block_enqueue' );

公式チュートリアル「初めてのブロックタイプ」ではこちらの方法が用いられています。

また、WordPress が提供するブロック開発用 JavaScript ビルド環境をセットアップするためのパッケージ @wordpress/create-block で生成される雛形のファイルでもこちらの方法が使われているので、こちらの方が一般的(?)かも知れません。

ブロックの実装

JavaScript を使ってブロックを実装(ブロックの定義を登録)します。

ブロックの定義を登録するには registerBlockType 関数を使います。

registerBlockType 関数で、ブロックの名前とその動作を定義するオブジェクトを指定して新しいブロックを登録すると、ブロックが利用できるようになります。

ブロック用のスクリプト(block.js)に以下を記述します(ブロック用のスクリプトの読み込み」で動作確認のために記述した console.log の記述は削除します)。

即時関数として記述しているのは、グローバル環境を汚染しないためです(スコープの汚染防止)。即時関数の引数には、グローバル変数の window.wp.blocks と window.wp.element を渡しています。

window.wp はブロックの作成に使用するコンポーネントや関数が含まれている非常に大きな JavaScript のパッケージです。以下で使用している registerBlockType() は wp.blocks パッケージに、createElement() は wp.element パッケージに含まれています。

JavaScript パッケージは WordPress 内で登録済みスクリプトとして利用でき、wp グローバル変数を使用してアクセスします(パッケージリファレンス)。

wp-content/plugins/my-first-block/block.js
( function( blocks, element ) {
  var el = element.createElement;

  //スタイルを JavaScript のオブジェクト形式 { } で記述し変数に代入(区切りはカンマ)
  var blockStyle = {
    backgroundColor: '#900', //キャメルケース
    color: '#fff',
    padding: '20px',
  };

  //registerBlockType でブロックを登録
  blocks.registerBlockType( 
    //namespace を含むブロックの名前 namespace/block-name
    'my-blocks/my-first-block', 
    //ブロックのプロパティ(動作を定義するオブジェクト)
    {
      title: 'My First Block Sample',
      icon: 'universal-access-alt',
      category: 'layout',
      edit: function() {
        return el(
          'p',
          { style: blockStyle },  //インラインスタイル(style 属性)を指定
          'Hello World, sample 01 (edit/エディタ用).'
        );
      },
      save: function() {
        return el(
          'p',
          { style: blockStyle },
          'Hello World, sample 01 (save/フロントエンド用).'
        );
      },
    }
  );
}(
  window.wp.blocks,
  window.wp.element
) );

上記の block.js を保存して、ブロックエディタを使って投稿の新規作成または編集画面を開いてブロックを挿入しようとすると、ブロックインサーターに登録したブロックが title で指定した名前と icon で表示されます。

ブロックインサーターの「すべてを表示」をクリックすると、この例の場合、category: 'layout' を指定しているので「デザイン」の下に登録したブロックが表示されます。

登録したブロックを挿入すると、エディター画面では以下のように表示されます。

プレビュー画面(フロントエンド)では以下のように表示されます。

registerBlockType

registerBlockType 関数は以下の2つのパラメータを引数に取ります。

パラメータ 説明
Block Name
(文字列)
ブロックの一意の名前。名前は、namespace/block-name とする必要があります。※ブロック名は英数小文字かダッシュのみが使え、文字で始まる必要があります。
Block Configuration
(オブジェクト)
{ key: value } で指定したブロックのプロパティ(動作を定義するオブジェクト)

ブロック名はプラグイン専用の名前空間をプレフィックスに指定します。こうすることで2つ以上のプラグインが同じ名前でブロックを登録しても衝突を避けられます。

WordPress の標準のブロックの namespace は core が使われています。例えば、標準の paragraph のブロックは core/paragraph となっているので、core 以外の namespace を指定すれば paragraph という名前のブロックを登録できます。

この例の場合、名前空間は my-blocks としていますが、ユニークに識別できる文字列を指定すると良いと思います。

また、ブロック名は英数小文字かダッシュのみで指定し、文字で始まる必要があります。

blocks.registerBlockType( 
  //ブロックの名前 namespace/block-name
  'my-blocks/my-first-block', 
  //ブロックのプロパティ(動作を定義するオブジェクト)
  {
    title: 'My First Block Sample',
    icon: 'universal-access-alt',
    category: 'layout',
    edit: function() {
      return el(
        'p',
        { style: blockStyle },
        'Hello World, sample 01 (edit/エディタ用).'
      );
    },
    save: function() {
      return el(
        'p',
        { style: blockStyle },
        'Hello World, sample 01 (save/フロントエンド用).'
      );
    },
  }
);

第2パラメータ/ブロックのプロパティ

registerBlockType の第2パラメータに指定できるブロックのプロパティには以下のようなものがあります(ブロックの登録/registerBlockType 関数より)。

ブロックに指定できるプロパティ一部抜粋
プロパティ type 説明
title String (必須)ブロックの表示タイトル。ブロックインサーターに表示されます。
category String (必須)ブロックは見やすさ、検索しやすさのためカテゴリーにグループ分けされます。category には以下のいずれかの文字列を指定できます。
  • common(一般ブロック)
  • formatting(フォーマット)
  • layout(レイアウト要素)
  • widgets(ウィジェット)
  • embed(埋め込み)
カスタムカテゴリーを作成することもできます。
description String ブロックの簡単な説明。「設定」サイドバーの「ブロック」タブで表示されます。(オプション)
icon String ブロックを見つけやすくするために表示するアイコン画像の icon プロパティ。任意の WordPress Dashicon またはカスタム svg 要素を指定できます。icon プロパティは文字列として Dashicon の名前を取ります。例: dashicons-smiley なら smiley。(オプション)
keywords Array ユーザーが検索する際に見つけやすいよう別名を設定できます。(オプション)
styles Array ブロックスタイルを使用してブロックに代替のスタイルを与えられます。(オプション)
attributes Object 任意に設定できる利用可能な属性と対応する値を表すプロパティ。属性にはブロックに必要なデータを保存することができます。(オプション)
example Object このデータを使用してブロックのプレビューを作成します。example が定義されていない場合、プレビューは表示されません。※属性が定義されていない場合にもプレビューを表示するには、空の example オブジェクト example: {} を設定します。(オプション)
variations Object ユーザーが選択可能なブロックバリエーションを定義できます。(オプション)
transforms Object transform は、何をブロックに変換できるのか、またブロックは何に変換できるのかのルールを提供します。(オプション)
parent Array parent を設定したブロックは、特定のブロック内にネストした場合のみ利用可能になります。(オプション)
supports Object ブロック拡張サポート機能を指定します。(オプション)
edit function エディターにブロックが挿入された際のこのブロックのレンダリング結果(どのようにブロックを表示するか)を返す関数。
save function 投稿が保存された際に、エディターが post_content フィールドに挿入するブロックのレンダリング結果(ブロックがサイトのフロントエンドでどのように表示されるか)を返す関数。post_content フィールドは投稿のコンテンツを保存する WordPress データベース内のフィールドです。
createElement

registerBlockType の第2パラメータでは title などのプロパティと共に、 edit 関数と save 関数を使用してブロックがどのように動作し、操作、保存されるかを定義します(edit と save)。

以下は前述の block.js から抜粋です。

//変数 el に element.createElement を代入
var el = element.createElement;
・・・中略・・・
blocks.registerBlockType( 
  //名前空間/ブロック名(第1パラメータ)
  'my-blocks/my-first-block', 
  //ブロックのプロパティ(第2パラメータ)
  {
    title: 'My First Block Sample',
    icon: 'universal-access-alt',
    category: 'layout',
    //edit 関数
    edit: function() {
      return el(
        'p',
        { style: blockStyle },
        'Hello World, sample 01 (edit/エディタ用).'
      );
    },
    //save 関数
    save: function() {
      return el(
        'p',
        { style: blockStyle },
        'Hello World, sample 01 (save/フロントエンド用).'
      );
    },
  }
);

先頭で var el = element.createElement; のように、変数 el に element.createElement を代入していますが、以下と同じことになります。

上記13〜27行目の部分
edit: function() {
  return element.createElement(
    'p',
    { style: blockStyle },
    'Hello World, sample 01 (edit/エディタ用).'
  );
},
save: function() {
  return element.createElement(
    'p',
    { style: blockStyle },
    'Hello World, sample 01 (save/フロントエンド用).'
  );
}

上記の場合、以下のようなブロックが出力されます。

エディターでの出力
<p style="background-color: rgb(153, 0, 0); color: rgb(255, 255, 255); padding: 20px;">Hello World, sample 01 (edit/エディタ用).</p>

save 関数ではクラス名が自動的に追加されます。

フロントエンドでの出力
<p style="background-color:#900;color:#fff;padding:20px" class="wp-block-my-first-block-sample-01">Hello World, sample 01 (save/フロントエンド用).</p>

React.createElement()

element.createElement は React のメソッド React.createElement() に対応する関数で、 React 要素(DOM 要素のようなオブジェクト)を生成します。React 要素は画面に表示したい内容を記述したもので、実際の DOM ノード(要素)ではなく React が管理するオブジェクト(Virtual DOM)です。

以下は React.createElement() の書式です。

React.createElement(
  type,  //タグ名(文字列)や コンポーネント型
  [props],  //プロパティ
  [...children]  //子要素
)

element.createElement() もほぼ同じだと思います。この例の場合は以下のようになっています。

element.createElement(
  'p',  //タグ名
  { style: blockStyle },  //プロパティ
  'Hello World, sample 01 (edit/エディタ用).'  //子要素(この場合はテキスト)
);

この例ではプロパティにスタイルを指定する際に変数に入れてから指定していますが、以下のように直接指定することもできます。

スタイルの記述(style 属性の設定)は JavaScript のオブジェクト形式 { } で記述します。オブジェクト形式で指定するので backgroundColor のようにキャメルケースで指定する必要があります。区切りはセミコロンではなく、カンマになります。

edit: function() {
  return el(
    'p',
    { 
      style: { //style 属性を指定
        backgroundColor: '#900',
        color: '#fff',
        padding: '20px',
      } 
    },
    'Hello World, sample 01 (edit/エディタ用).'
  );
},

プロパティにはクラス属性なども指定することができます。クラス属性は className で指定します。

edit: function() {
  return el(
    'div',  // div 要素
    { 
      className: "sample-01",  //クラス属性を指定
      style: {
        backgroundColor: '#900',
        color: '#fff',
        padding: '20px',
      } 
    },
    'Hello World, sample 01 (edit/エディタ用).'
  );
},
エディターでの出力
<div class="sample-01" style="background-color: rgb(153, 0, 0); color: rgb(255, 255, 255); padding: 20px;">Hello World, sample 01 (edit/エディタ用).</div>

ここまでのまとめ

サンプルのブロック(プラグイン名: My First Block)のために作成したファイルは以下の2つのファイルです 。

  • my-first-block.php:メインのプラグインファイル(PHP)。
  • block.js:ブロック用のスクリプト(JavaScript)。

my-first-block.php

  • プラグインとして認識させるためのプラグインヘッダ(Plugin Name)を記述
  • enqueue_block_editor_assets フックでブロック用スクリプトの読み込み
wp-content/plugins/my-first-block/my-first-block.php
<?php
/*
Plugin Name: My First Block
*/

defined( 'ABSPATH' ) || exit;  

//ブロック用スクリプトの読み込み
function my_first_block_enqueue() {
  //ブロック用のスクリプトを登録し出力用のキューに入れる
  wp_enqueue_script(
    'my-first-block-script', //スクリプトのハンドル名
    plugins_url( 'block.js', __FILE__ ), //スクリプトの URL
    array( 'wp-blocks', 'wp-element' )  //依存スクリプト
  );
}
//エディターでのみブロックのアセットをエンキューするためのフック
add_action( 'enqueue_block_editor_assets', 'my_first_block_enqueue' );

上記の enqueue_block_editor_assets フックの代わりに init フックと register_block_type を使って以下のように記述することもできます。

my-first-block.php(register_block_type を使った場合)
<?php
/*
Plugin Name: My First Block
*/

defined( 'ABSPATH' ) || exit;  

//ブロック用スクリプトの読み込み
function my_first_block_enqueue() {
  wp_register_script(
    'my-first-block-script',
    plugins_url( 'block.js', __FILE__ ),
    array( 'wp-blocks', 'wp-element' ) 
  );
  
  //ブロックタイプを登録
  register_block_type( 
    //namespace/block name(名前空間/ブロック名)
    //ブロック用スクリプト registerBlockType の第1パラメータ Block Name と同じ
    'my-blocks/my-first-block', 
    array(   
      //エディタ用スクリプトとしてブロックのスクリプトを関連付け(ハンドル名を指定)
      'editor_script' => 'my-first-block-script', 
    ) 
  );
}
add_action( 'init', 'my_first_block_enqueue' );

block.js

  • ブロックの実装(ブロックの定義と登録)を記述
wp-content/plugins/my-first-block/block.js(ブロック用のスクリプト)
( function( blocks, element ) {
  var el = element.createElement;

  //インラインスタイル
  var blockStyle = {
    backgroundColor: '#900',
    color: '#fff',
    padding: '20px',
  };

  //registerBlockType でブロックを登録
  blocks.registerBlockType( 
    //ブロックの名前 (名前空間/ブロック名)
    'my-blocks/my-first-block', 
    //ブロックのプロパティ(動作を定義するオブジェクト)
    {
      title: 'My First Block Sample',
      icon: 'universal-access-alt',
      category: 'layout',
      //エディター内でのブロックの構造を記述する関数
      edit: function() {
        return el(
          'p',
          { style: blockStyle },
          'Hello World, sample 01 (edit/エディタ用).'
        );
      },
      //ブロックがフロントエンドでどのように表示されるかを記述する関数
      save: function() {
        return el(
          'p',
          { style: blockStyle },
          'Hello World, sample 01 (save/フロントエンド用).'
        );
      },
    }
  );
}(
  window.wp.blocks,
  window.wp.element
) );

テーマで拡張

前述のようにプラグインで拡張してブロックを作成する以外に、テーマで拡張して(テーマにファイルを配置して)ブロックを作成することもできます。

テーマで拡張すると、そのテーマを選択していれば何もせずに作成したブロックを使用できます(プラグインを有効化する必要がない)。但し、当然、他のテーマを選択するとそのブロックは利用できません。

テーマ開発で拡張する場合は、使用するテーマのディレクトリ wp-content/themes/テーマ名/ にブロック用のディレクトリを作成してブロック用スクリプトを配置します。

ディレクトリ構成は任意の構成を作成することができるので、例えば、JavaScript 専用のディレクトリがあれば、その中にブロックのディレクトリを配置するなど環境に応じてファイルを配置します。

この例では my-first-block と言う名前のディレクトリを作成し、その中にブロック用スクリプト block.js を配置します。

└── wp-content
    └── themes
        └── my-theme //使用するテーマのディレクトリ
            ├── category.php
            ├── front-page.php
            ├── functions.php
            ├── index.php
            │    ...
            └── my-first-block //ブロック用のディレクトリ
                └── block.js  //ブロック用スクリプト

ここでは、前述の例で作成したブロック用スクリプト block.js をそのまま使用します。そのため、前述のプラグイン My First Block は無効にします。

ブロック用スクリプト block.js を読み込むために functions.php に以下を記述します。(

[注意]以下を functions.php に記述すると、プラグインファイル my-first-block.php に記述した関数と重複してエラーになるので、記述する前にプラグイン My First Block は無効にする必要があります。

プラグインファイル my-first-block.php とほぼ同じですが、ブプラグインの場合は URL の指定に plugins_url() を使いましたが、テーマの場合は get_theme_file_uri() などを使って URL を指定します。

functions.php
function my_first_block_enqueue() {
  wp_enqueue_script(
    'my-first-block-script',
    //ブロック用スクリプトの URL は get_theme_file_uri() などを使用
    get_theme_file_uri('/my-first-block/block.js'),
    array( 'wp-blocks', 'wp-element' ),
    filemtime(get_theme_file_path('/my-first-block/block.js'))
  );
}
add_action( 'enqueue_block_editor_assets', 'my_first_block_enqueue' );

ブロック用スクリプトを配置したテーマで、投稿のエディタで登録したブロックがインサータに表示され、挿入することができれば完了です。

この例ではブロック用スクリプトの読み込みを functions.php に記述しましたが、実際には別ファイルを作成し、functions.php で require などを使って読み込むようにした方が管理しやすくなると思います。

└── wp-content
    └── themes
        └── my-theme //使用するテーマのディレクトリ
            ├── category.php
            ├── front-page.php
            ├── functions.php
            ├── index.php
            │    ...
            └── my-first-block //ブロック用のディレクトリ
                ├── register_block.php  //★ブロック用スクリプトを読み込むファイル
                └── block.js  //ブロック用スクリプト

functions.php には以下を記述してブロック用スクリプトを読み込むファイル(register_block.php)を読み込むようにします。

functions.php
require get_template_directory() . '/my-first-block/register_block.php';    
register_block.php
function my_first_block_enqueue() {
  wp_enqueue_script(
    'my-first-block-script',
    get_theme_file_uri('/my-first-block/block.js'),
    array( 'wp-blocks', 'wp-element' ),
    filemtime(get_theme_file_path('/my-first-block/block.js'))
  );
}
add_action( 'enqueue_block_editor_assets', 'my_first_block_enqueue' );

上記のブロック用スクリプトを読み込むファイルを register_block_type と init フックを使って書き換えると以下のようになります。

register_block.php
function my_first_block_enqueue() {
  wp_register_script(
    'my-first-block-script',
    get_theme_file_uri('/my-first-block/block.js'),
    array( 'wp-blocks', 'wp-element' ),
    filemtime(get_theme_file_path('/my-first-block/block.js'))
  );
  
  register_block_type( 
    'my-blocks/my-first-block', 
    array(   
      'editor_script' => 'my-first-block-script', 
    ) 
  );
}
add_action( 'init', 'my_first_block_enqueue' );

スタイルシートを使ったスタイルの適用

前のセクションで作成したブロックではインラインのスタイルを使用しましたが、以下では外部のスタイルシートを読み込んで適用するサンプルを作成します。

参考元:スタイルシートによるスタイルの適用

プラグインのディレクトリ(my-first-block2)を作成しその中に以下の4つのファイルを作成します。

wp-content
└── plugins
    └── my-first-block2
        ├── block2.js  //ブロック用のスクリプト
        ├── my-first-block2.php  //プラグインファイル
        ├── editor-02.css  //エディタ用スタイルシート
        └── style-02.css  //フロント用スタイルシート

エディターはそれぞれのブロックタイプに対して、独自のクラス名を自動的に生成するので、そのクラス名を使ってスタイルを指定します。このクラス名には edit 関数、save 関数に渡されるオブジェクト引数(props)でアクセスすることができます。

以下は新しいブロック用のスクリプト block2.js です。

前述の例のブロック用スクリプト block.js との違いは、edit 関数で引数に props(オブジェクト)を受け取り、element.createElement(el)の第2パラメータ(プロパティ)に自動的に付与されるクラス名(props.className)を指定しています。

block2.js(ブロック用のスクリプト)
( function( blocks, element ) {
  var el = element.createElement;

  blocks.registerBlockType( 
    'my-blocks/my-first-block2', 
    {
      title: 'My First Block Sample 2',
      icon: 'smiley',  //アイコンを変更
      category: 'layout',
      example: {},  //プレビュー表示
      // 引数に props を取る
      edit: function( props ) {
        return el(
          'p',
          //クラス属性(className)に props.className を指定
          { className: props.className },
          'Hello World, sample 02 (エディタ用 in green).'
        );
      },
      save: function() {
        return el(
          'p',
          {},
          'Hello World, sample 02 (フロントエンド用 in red).'
        );
      },
    }
  );
}(
  window.wp.blocks,
  window.wp.element
) );

save 関数ではクラス名が自動的に追加されます。(edit と save

Props

props は React の機能の1つで、基本的にはデータをコンポーネントに渡す方法です。別の言い方をすると、特定のコンポーネントが利用できるプロパティのリストのようなものです(React props)。

WordPress は、registerBlockType() の edit 関数と save 関数でいくつかの props を提供します。

edit 及び save 関数の引数に props を受け取ることができ、これらの関数内では props を使って className や attributes などの属性や setAttributes などのメソッドにアクセスすることができます。

どのような props があるかは、例えば edit 関数の引数に props を渡して return の前で console.log(props); を実行することで確認できます。

edit: function( props ) {
  //return の前で props を出力
  console.log(props);
  return el(
    'p',
    { className: props.className },
    'Hello World, sample 02 (エディタ用 in green).'
  );
},

//コンソールへの出力結果の例
{name: "my-blocks/my-first-block2", isSelected: false, attributes: {…}, setAttributes: ƒ, insertBlocksAfter: ƒ, …}

参考元:Props and WordPress Components

自動的に付与されるクラス名

クラス名はブロック名の前に wp-block- を付け、名前空間セパレーターのスラッシュ( / )を - で置換して生成されます。

この例の場合、ブロック名は my-blocks/my-first-block2 なので、自動的に生成されるクラス名は wp-block-my-blocks-my-first-block2 になり、このクラス名を使ってスタイルを指定します。

以下のようなエディター用のスタイルシートとフロント用のスタイルシートをプラグインフォルダの中に作成します。この例ではエディターとフロントに別々のスタイルを適用します。

editor-02.css(エディター用のスタイルシート)
.wp-block-my-blocks-my-first-block2 {
  color: green;
  background: #cfc;
  border: 2px solid #9c9;
  padding: 20px;
}
style-02.css(フロント用のスタイルシート)
.wp-block-my-blocks-my-first-block2 {
  color: darkred;
  background: #fcc;
  border: 2px solid #c99;
  padding: 20px;
}

ブロック用アセットの読み込み

ブロックのスタイルもスクリプト同様、メインのプラグインファイルで読み込む必要があります。

メインのプラグインファイル my-first-block2.php を作成し以下を記述して保存します。そして、このプラグイン My First Block 2 を有効化しておきます。

以下では、enqueue_block_editor_assets を使ってエディター用のスタイルシートを読み込み、フロント用のスタイルシートは enqueue_block_assets で !is_admin() の場合のみ読み込むようにしています。

my-first-block2.php
<?php
/*
Plugin Name: My First Block 2
*/
defined( 'ABSPATH' ) || exit; 

function my_first_block2_enqueue() {
  //ブロック用のスクリプトをエンキュー
  wp_enqueue_script(
    'my-first-block2-script',
    plugins_url( 'block2.js', __FILE__ ),
    array( 'wp-blocks', 'wp-element' ) 
  );
  
  //ブロックのエディター用のスタイルシートをエンキュー
  wp_enqueue_style(
    'my-first-block2-editor',
    plugins_url( 'editor-02.css', __FILE__ ),
    //エディター用 CSS を含めるための依存関係として wp-edit-blocks を指定
    array( 'wp-edit-blocks' ),
    filemtime( plugin_dir_path( __FILE__ ) . 'editor-02.css' )
  );
}
//ブロックのアセットをエディターでのみエンキューするためのフック
add_action( 'enqueue_block_editor_assets', 'my_first_block2_enqueue' );

function my_first_block_enqueue2_2() {
  //フロントのみに適用するので、!is_admin() の場合のみ読み込む(エンキュー)
  if(! is_admin()) {
    //ブロックのフロント用のスタイルシートをエンキュー
    wp_enqueue_style(
      'my-first-block2-front',
      plugins_url( 'style-02.css', __FILE__ ),
      array(),
      filemtime( plugin_dir_path( __FILE__ ) . 'style-02.css' )
    );
  }
}
//エディターとフロントエンドの両方でブロックのアセットをエンキューするフック
add_action( 'enqueue_block_assets', 'my_first_block_enqueue2_2');

上記を register_block_type() を使って書き換えると以下のようになります。公式チュートリアル「スタイルシートによるスタイルの適用」ではこちらの方法が使われています。

style でエンキューされるスタイルシートは最初にロードされ、editor_style スタイルシートは後でロードされます。

my-first-block2.php
<?php
/*
Plugin Name: My First Block 2
*/
defined( 'ABSPATH' ) || exit; 

function my_first_block2_enqueue() {
  //ブロック用のスクリプトを登録
  wp_register_script(
    'my-first-block2-script',
    plugins_url( 'block2.js', __FILE__ ),
    array( 'wp-blocks', 'wp-element' ) 
  );
  
  //ブロックのエディター用のスタイルシートの登録
  wp_register_style(
    'my-first-block2-editor',
    plugins_url( 'editor-02.css', __FILE__ ),
    //エディター用 CSS を含めるための依存関係として wp-edit-blocks を指定
    array( 'wp-edit-blocks' ),
    filemtime( plugin_dir_path( __FILE__ ) . 'editor-02.css' )
  );
  
  //ブロックのフロント用のスタイルシートの登録
  wp_register_style(
    'my-first-block2-front',
    plugins_url( 'style-02.css', __FILE__ ),
    array(),
    filemtime( plugin_dir_path( __FILE__ ) . 'style-02.css' )
  );

  register_block_type( 
    'my-blocks/my-first-block2',
    array(
      //エディター用スクリプトとしてブロックのスクリプト(block2.js)を関連付け
      'editor_script' => 'my-first-block2-script',
      //エディター用スタイルとしてスタイルシート(editor-02.css)を関連付け
      'editor_style' => 'my-first-block2-editor',
      //フロント用スタイルとしてスタイルシート(style-02.cs)を関連付け
      'style' => 'my-first-block2-front',    
    ) 
  );
}
add_action( 'init', 'my_first_block2_enqueue' );

作成したファイルを保存してプラグインを有効化してあれば、ブロックインサータに新しいブロックが追加されています。

作成したブロックを挿入するとエディター画面では以下のように表示されます。

この例ではブロック用のスクリプトの registerBlockType で example プロパティに空のオブジェクトを指定しているので、ブロックをすべて表示した際にマウスオーバーするとプレビューが表示されます(空の example オブジェクト example: {} を設定すると、属性が定義されていない場合にもプレビューが表示されます)。

フロント側では以下のように表示されます。

attributes

attributes(属性)はブロックが保持することができるデータで、registerBlockType() の第2パラメータに指定するプロパティの1つ(attributes プロパティ)として追加されます。

registerBlockType() を使って新しいブロックを登録する際に、edit 関数及び save 関数で受け取りたいデータ(オブジェクト)を attributes プロパティで定義することができます。

定義する個々の属性は少なくとも type プロパティを持たなければならず(必須)、type プロパティは属性の中に保存されるデータの型を表し、以下のいずれかの値を取る必要があります。

  • null
  • boolean
  • object
  • array
  • number
  • string
  • integer

オプションで default プロパティを指定して属性の初期値(その属性のデフォルト値)を設定できます。 default プロパティを指定しない場合、属性の値はデフォルトで null になります。

また、attributes は DOM から個々の属性のデータを抽出する方法をコンポーネントに知らせる(データの取得方法を定義するための)オブジェクトでもあり、以下のようなプロパティがあります。

  • selector: 対象とする要素を指定。DOM セレクタ(タグ名やクラス名など)
  • source: 保存された投稿コンテンツからどのようにブロックの属性値を取り出すかを定義
  • attribute: DOM の属性(href や src など)

以下は exampleText(文字列)と postid(配列)という2つの属性を定義する例です。

registerBlockType('my-blocks/sample-0x', {
  title: 'My First Block',
  category: 'layout',
  //exampleText と postid という2つの属性を定義
  attributes: {
    exampleText: {
      type: 'string',  //文字列
      default: ''  //初期値(空文字)
    },
    postid: {
      type: 'array'  //配列
      default: []  //初期値(空の配列)
    }
  },
  ・・・

※ ブロックに必要なデータは属性として定義することができます。

データをどのように構成するかは任意で、データを個別の属性としてそれぞれ定義することも、それらをオブジェクトにまとめて定義することもできます。

属性の値の取得

属性はブロックの edit と save 関数で引数として渡される props 経由で利用できます。

以下は定義した exampleText という属性の値を props 経由で取得して出力する例です。この場合、exampleText の default プロパティに初期値が設定されているので、その値が出力されます。

blocks.registerBlockType( 'my-blocks/sample-0x', {
    title: 'My First Block',
    icon: 'smiley',
    category: 'layout',
    //exampleText という属性を定義
    attributes: {
      exampleText: {
        type: 'string',
        default: '初期値の文字列'
      }
    },
    // 引数に props を受け取る
    edit: function( props ) {
      return el(
        'p',
        { className: props.className },
        // props 経由で属性を取得
        props.attributes.exampleText // '初期値の文字列'と出力される 
      );
    },
    ・・・
  }
);

以下はよく使われる ES6 の分割代入を使った例です。

edit: function( props ) {
  //分割代入
  const { attributes } = props;
  return el(
    'p',
    { className: props.className },
    attributes.exampleText
  );
},

属性の値の更新

属性を更新するには、props 経由で使用可能な setAttributes() メソッドを使用します。

setAttributes() は更新する属性のオブジェクトを受け取り、値を更新します。一度に更新できる属性は1つだけです。

setAttributes() はReact の setState() に対応するメソッドで同じように動作しますが、違いは、React の state がそのコンポーネントにローカルであるのに対して、attributes はコンポーネントの外部のデータとして保存されます。

以下は exampleText という属性の値を 'Updated!' に更新する例です。※ 実際には onChange などの何らかのアクションに伴い setAttributes() を使います。

attributes: {
  exampleText: {
    type: 'string',
    default: '初期値の文字列'
  }
},

edit: function( props ) {
  //exampleText の値を更新
  props.setAttributes({ exampleText: 'Updated!' });
  return el(
    'p',
    { className: props.className },
    props.attributes.exampleText 
  );
},

通常は分割代入で setAttributes() を props から変数に入れて以下のように使うことが多いです。

edit: function( props ) {
  //props から分割代入
  const { setAttributes } = props;
  setAttributes({ exampleText: 'Updated!' });
  return el(
    'p',
    { className: props.className },
    props.attributes.exampleText 
  );
},

※ 属性を更新する場合は、その属性の type プロパティで指定したデータ型で保存する必要があります。

カスタムテキスト入力のブロックを作成

エディター画面では TextControl コンポーネントを使ってテキスト入力(input)を表示して、ユーザが文字を入力できるようにします。

入力された値(文字列)を表示し、変更される度に入力された値を更新するため、ブロックにはそのための属性(attributes)を追加します。

以下がブロックのスクリプト(block_tc.js)を読み込むプラグインファイルです。

my-first-block-tc.php
<?php
/*
Plugin Name: My First Block TextControl
*/
defined( 'ABSPATH' ) || exit; 

function my_first_block_tc_enqueue() {
  //ブロック用のスクリプトを登録
  wp_register_script(
    'my-first-block-tc-script',
    plugins_url( 'block_tc.js', __FILE__ ),
    //依存スクリプト(TextControl には wp-components が必要)
    array( 'wp-blocks', 'wp-element', 'wp-components') 
  );

  register_block_type( 
    'my-blocks/my-first-block-textcontrol',
    array(
      //エディター用スクリプトにブロックのスクリプトを関連付け
      'editor_script' => 'my-first-block-tc-script',   
    ) 
  );
}
add_action( 'init', 'my_first_block_tc_enqueue' );   

以下がカスタムテキスト入力用ブロックのスクリプトです。

WordPress の wp.components パッケージの TextControl コンポーネントを使います。

今までの例では、el(element.createElement)の第1引数にはタグ名(p 要素の 'p')を指定していましたが、この例ではテキスト入力のコンポーネント TextControl を使うので、通常のタグの代わりにコンポーネント型(TextControl)を指定しています。TextControl は文字列ではなくコンポーネント型なので引用符では囲みません。

属性(attributes)として、入力された値を保持するための exampleText を設定します(type: 'string' で初期値は空文字 '')。

edit 関数内では props 経由で、onChange ハンドラの定義で使用する setAttributes() と入力の値(value)に設定する属性 attributes.exampleText を受け取ります。これらを使用する場合は、props.setAttributes()、props.attributes.exampleText とします。

TextControl の値 value プロパティには、属性の値 attributes.exampleText を設定します。入力される値が変更されると onChange に指定した関数が呼び出されこの値も更新されます。

React の基本的なフォームのコンポーネント(制御されたコンポーネント)に似ています(但し、WordPress では attributes を使いますが React では state を使います)。

onChange

onChange は TextControl コンポーネントのプロパティの1つで、入力された値が変更される度に発生するイベント(onChange)で実行する関数を指定します。このプロパティで指定する関数(この例では onChangeContent)は入力された値を引数に受け取ります。

onChangeContent では props 経由で取得した属性の値を更新する関数 setAttributes() を使って属性の値を引数に渡される入力された値(newText)で更新します。

block_tc.js
( function( blocks, element, components ) {
  var el = element.createElement;
  //TextControl コンポーネントは wp.components パッケージにあります
  var TextControl = components.TextControl;
  
  blocks.registerBlockType( 
    'my-blocks/my-first-block-textcontrol', 
    {
      title: 'My First Block TextControl',
      icon: 'smiley',
      category: 'layout',
      example: {},
      //exampleText 属性を定義(入力された値を保存)
      attributes: {
        exampleText: {
          type: 'string',
          default: ''
        }
      },
      edit: function( props ) {
        //onChange ハンドラ(コールバック関数)の定義
        function onChangeContent( newText ) {
          //exampleText の値を更新
          props.setAttributes( { exampleText: newText } );
        }
        return el(
          TextControl,
          {
            //onChange ハンドラ(コールバック関数)の指定
            onChange: onChangeContent,
            //exampleText 属性の値を value プロパティに設定
            value: props.attributes.exampleText
          }
        );
      },
      save: function() {
        // div 要素で Hello と表示(後で修正)
        return el(
          'div',
          {},
          'Hello!'
        );
      },
    }
  );
}(
  window.wp.blocks,
  window.wp.element,
  //テキスト入力のコンポーネント(TextControl)を使うので wp.components を追加
  window.wp.components
) );

上記のファイルを保存すると、エディターにはテキスト入力用の input 要素が表示され、文字を入力して変更及び保存することができます。

フロントエンド側の表示

フロントエンドで投稿を表示すると「Hello!」と表示されますが、これは save 関数に記述された内容が表示されているためです。

但し、内部的には変化があり、通常のソースには表示されませんが、作成したブロックのコメントには内部では以下のような JSON の属性の値が含まれています。

<!-- wp:my-blocks/my-first-block-textcontrol {"exampleText":"sample text"} -->
<div class="wp-block-my-blocks-my-first-block-textcontrol">Hello!</div>
<!-- /wp:my-blocks/my-first-block-textcontrol --> 

上記の表示はテンプレート(single.php)で echo get_the_content() を実行して、フロントエンド側のデベロッパーツール(F12キー)で確認するか、編集画面でコードエディターにして確認することができます。

フロントエンド側でも入力された値を表示

フロントエンド側で入力された値(属性の値)を表示するには、save 関数を以下のように書き換えます。

以下は save 関数で、入力された値(exampleText 属性の値)を div 要素に表示しています。

save: function( props ) {
  return el(
    'div',
    {},
    //子要素(テキスト)に入力されて値を設定
    props.attributes.exampleText
  );
},

※ この変更を行うと、以下のように既にこのブロックを追加した投稿に壊れたブロックが表示されます。

これは、エディターが現在定義しているものとは異なる save 関数の出力を検出するために発生します。 現在の壊れたブロックを一度削除して、再度ブロックを追加すれば機能します。

属性と edit 関数・save 関数

ブロックに必要なもが何かにより、どのような属性(attributes)を定義して保存するかを決定します。

edit 関数では、ユーザーが入力する方法をレンダリングし、現在の値が表示されるようにして、値が変更されるたびに更新するようにします。

上記の例の場合、現在の値は exampleText 属性の値で、onChange イベントを使って値が変更されるたびに、setAttributes() で値を更新します。

そして、save 関数では、保存した属性(attributes)を抽出し、必要に応じて出力をレンダリングします。

RichText コンポーネントを使う

WordPress の RichText コンポーネントは、テキストのフォーマットをサポートする拡張されたテキストエリアを提供します(RichText リファレンス)。

但し、 RichText の場合、いくつかの注意が必要な props があったり、save 関数で値を保持する方法にも違いがあるため、他のテキスト入力やテキストエリアのコンポーネントとは少し処理が異なります。

以下がブロックのスクリプト(block_rt.js)を読み込むプラグインファイルです。

my-first-block-rt.php
<?php
/*
Plugin Name: My First Block RichText
*/
defined( 'ABSPATH' ) || exit; 

function my_first_block_rt_enqueue() {
  //ブロック用のスクリプトを登録
  wp_register_script(
    'my-first-block-rt-script',
    plugins_url( 'block_rt.js', __FILE__ ),
    //依存に wp-editor を追加(v5.3 からは wp-block-editor の方が良さそう?)
    array( 'wp-blocks', 'wp-element', 'wp-editor') 
  );

  register_block_type( 
    'my-blocks/my-first-block-richtext',
    array(
      //エディター用スクリプトにブロックのスクリプトを関連付け
      'editor_script' => 'my-first-block-rt-script',   
    ) 
  );
}
add_action( 'init', 'my_first_block_rt_enqueue' );

以下は前述のカスタムテキスト入力とほぼ同様なシンプルな RichText コンポーネントを使った入力用ブロックのスクリプトです。前述のカスタムテキスト入力との違いは、save 関数で RichText.Content を使っていることです。

save 関数で RichText のコンテンツを正しく保存するためには、RichText.Content を使う必要があります。そしてそのプロパティ value に値(入力された値が保存されている myRichText 属性)を設定します。

block_rt.js
( function( blocks, element, blockEditor ) {
  var el = element.createElement;
  //RickTest コンポーネントは wp.blockEditor パッケージにあります
  var RichText = blockEditor.RichText ;
  
  blocks.registerBlockType( 
    'my-blocks/my-first-block-richtext', 
    {
      title: 'My First Block RichText',
      icon: 'smiley',
      category: 'layout',
      example: {},
      attributes: {
        //入力された値を保存する属性を設定
        myRichText: {
          type: 'string',
          default: ''
        }
      },

      edit: function( props ) {
        //onChange ハンドラ
        function onChangeContent( newText ) {
          //myRichText の値を更新
          props.setAttributes( { myRichText: newText } );
        }
        return el(
          RichText,
          {
            //イベントハンドラを設定
            onChange: onChangeContent,
            //value プロパティ(値)
            value: props.attributes.myRichText
          }
        );
      },
      save: function( props ) {
        return el( 
          //RichText のコンテンツを正しく保存するには RichText.Content を使用
          RichText.Content, 
          {
            //value プロパティに値を設定
            value: props.attributes.myRichText,
          } 
        );
      },
    }
  );
}(
  window.wp.blocks,
  window.wp.element,
  //RichText コンポーネントを使うので wp.blockEditor パッケージを追加
  window.wp.blockEditor
) );

上記のファイルを保存すると、エディターには RichText を使ったテキスト入力用のエリアが表示され、文字を入力して変更及び保存することができます。

また、前述の TextControl とは異なり、太字やイタリック、リンクの挿入などを含んだツールバーが表示されます。

save 関数による出力は入力された内容がそのまま出力され、HTML タグでは囲まれていません。※ RichText のプロパティ(tagName)を使って入力エリアの HTML 要素を指定することができます。

RichText のプロパティ

RichText コンポーネントでは、value や onChange 以外にもいくつかの プロパティ が用意されています。

例えば、placeholder プロパティを使えば、プレースホルダーテキストを表示することができますし、tagName プロパティを使えば、ブロックの入力エリアの HTML タグ(要素)を指定することができます。但し、インライン要素はサポートされていません。

placeholder プロパティと tagName プロパティ
edit: function( props ) {
  function onChangeContent( newText ) {
    props.setAttributes( { myRichText: newText } );
  }
  return el(
    RichText,
    {
      onChange: onChangeContent,
      value: props.attributes.myRichText,
      //プレースホルダーテキストを表示
      placeholder: 'タイトル(h3)を入力',
      // h3 要素として表示
      tagName: 'h3',

    }
  );
},
save: function( props ) {
  return el( 
    RichText.Content, 
    {
      value: props.attributes.myRichText,
      //  h3 要素として表示
      tagName: 'h3'
    } 
  );
},

デフォルトでは、Enter キーを押すと改行が挿入されますが、multiline プロパティを設定して Enter キーで新しい段落を作成することができます。

multiline プロパティ
edit: function( props ) {
  function onChangeContent( newText ) {
    props.setAttributes( { myRichText: newText } );
  }
  return el(
    RichText,
    {
      onChange: onChangeContent,
      value: props.attributes.myRichText,
      tagName: 'div',
      //true を設定して Enter キーで新しい段落(p 要素)を作成
      multiline: true  //または 'p'
    }
  );
},
save: function( props ) {
  return el( 
    RichText.Content, 
    {
      value: props.attributes.myRichText,
      tagName: 'div'
    } 
  );
},

multiline プロパティには boolean の他にタグ名を指定して、Enter キーを押すとその要素を子要素として生成することができます。例えば、以下は li を指定して、複数の li 要素を入力する例です。

multiline プロパティ(2)
edit: function( props ) {
  function onChangeContent( newText ) {
    props.setAttributes( { myRichText: newText } );
  }
  return el(
    RichText,
    {
      onChange: onChangeContent,
      value: props.attributes.myRichText,
      tagName: 'ol',
      //li を指定して Enter キーで新しい li 要素を作成
      multiline: 'li',
    }
  );
},
save: function( props ) {
  return el( 
    RichText.Content, 
    {
      value: props.attributes.myRichText,
      tagName: 'ol'
    } 
  );
},

source と selector

属性(attributes)の source プロパティは、保存された投稿から、指定されたブロックの属性値を抽出して WordPress が解釈できるようにします。

公式ページの「属性」には「source プロパティは保存された投稿コンテンツからどのようにブロックの属性値を取り出すかを定義します。」とあります。

ブロックが1つのコンポーネントから成る単純な場合はあまり問題はないかも知れませんが、ネストしたコンポーネントなど構造が複雑になると的確に source プロパティや selector プロパティを指定しないとうまく機能しない場合があります。

属性(attributes)の source プロパティを指定しない場合、属性は以下のようなブロックのコメントブロック(デリミタ)に JSON 形式保存され、ロード時に読み出されます。

source プロパティを設定すると、ブロックのデータをコメントブロックからではなく、属性から取得することを意味します。

例えば、以下の場合、データを selector で指定した div 要素の、source で指定した html から、type で指定した string 型として取得して、myRichText という属性に保存するというような意味になります。

myRichText: {
  type: 'string',  //属性値のデータの型
  default: '',
  source: 'html',  //どのように属性値を取り出すか
  selector: 'div'  //対象の要素
}
source プロパティ
保存された投稿コンテンツからどのようにブロックの属性値を取り出すかを定義します。以下の値を指定できます。
  • children(child node:子ノード)
  • html(内部の HTML)
  • text(内部のテキスト)
  • attribute(属性の値)
  • query(値の配列)
  • meta(投稿のメタ情報)
selector プロパティ

selector プロパティの指定がない場合、source プロパティの定義はブロックのルートノードに対して実行され、指定がある場合は、ブロック内に含まれる selector で指定された要素に対して実行されます。

selector プロパティは HTML タグのほか、クラスや id 属性などを使って指定できます(例 p.sample)。

source プロパティは、抽出する対象の HTML タグやクラスなどのセレクタを定義する selector プロパティ、及び type プロパティと一緒に使って対象を指定します。そのブロックがどのような構造かにより、指定する方法は異なってきます。

複数の RichText コンポーネントを使う

複数のコンポーネントを組み合わせてブロックを作成することもできます。その場合、それぞれのコンポーネントどとに独自の属性 attributes を設定します。

以下は見出しと段落を組み合わせた RichText コンポーネントを使ったブロックの例です(前述の例を書き換えているのでファイル名などは同じになっています)。

attributes には見出し用と段落用の属性をそれぞれ設定しています。

edit 関数と save 関数では、見出しと段落を div 要素でラップしています。element.createElement(el)の第3パラメータの children(子要素)に再度 element.createElement を使ってネストています(element.createElement でのネスト)。

また、source プロパティと selector プロパティを使って投稿コンテンツからどのようにブロックの属性値を取り出すかを定義するため、以下では save 及び edit 関数で見出し(h3 要素)と段落のまとまり(div 要素)にクラスを設定し、attributes のそれぞれの属性の selsector プロパティでそれらのクラスを使ってセレクタを指定しています。

タイトル用の属性(myRichHeading)では source に html を指定し、段落用の属性(myRichText)では source 属性に children(子ノード)を指定しています。

block_rt.js
( function( blocks, element, blockEditor ) {
  var el = element.createElement;
  //RickTest コンポーネントは wp.blockEditor パッケージにあります
  var RichText = blockEditor.RichText ;
  
  blocks.registerBlockType( 
    'my-blocks/my-first-block-richtextt', 
    {
      title: 'My First Block RichText',
      icon: 'smiley',
      category: 'layout',
      example: {},
      attributes: {
        //見出し用の属性
        myRichHeading: {
          //.myRichHeading クラスの h3 要素の HTML から string を抽出
          type: 'string',
          default: '',
          //内部の HTML
          source: 'html',
          //クラスを使ったセレクタを指定
          selector:'h3.myRichHeading'  //または '.myRichHeading' 
        },
        //段落用の属性
        myRichText: {
          //.myRichText クラスの div 要素の子ノードから array を抽出
          type: 'array',
          default: '',
          //子ノード
          source: 'children',
          //クラスを使ったセレクタを指定
          selector:'div.myRichText' //または '.myRichText' 
        }
      },
      
      edit: function( props ) {
        return el(
          'div', 
          {}, //空のプロパティ
          [
            el(
              RichText, 
              {
                tagName: 'h3',
                placeholder: 'タイトル(h3)を入力',
                //クラスを追加
                className: 'myRichHeading',
                value: props.attributes.myRichHeading,
                onChange: function( newHeading ) {
                  props.setAttributes( { myRichHeading: newHeading } );
                }
              }
            ),
            el(
              RichText, 
              {
                tagName: 'div',
                placeholder: '文章(段落)を入力',
                //クラスを追加
                className: 'myRichText',
                value: props.attributes.myRichText,
                multiline: 'p',
                onChange: function( newText ) {
                  props.setAttributes( { myRichText: newText } );
                }
              }
            )
          ]
        );
      },
      
      save: function( props ) {
        return el(
          'div', 
          {}, //空のプロパティ
          [
            el(
              RichText.Content, 
              {
                tagName: 'h3',
                //クラスを追加
                className: 'myRichHeading',
                value: props.attributes.myRichHeading
              }
            ),
            el(
              RichText.Content, 
              {
                tagName: 'div',
                //クラスを追加
                className: 'myRichText',
                value: props.attributes.myRichText
              }
            )
          ]
        );
      },
    }
  );
}(
  window.wp.blocks,
  window.wp.element,
  window.wp.blockEditor
) );

element.createElement でのネスト

//ネストなし
element.createElement(
  'p',  //タグ名やコンポーネント型(名)
  { style: blockStyle },  //プロパティ
  'Hello World!'  //子要素(この場合はテキスト)
);

//div 要素の子要素に p 要素
element.createElement(
  'div', 
  {},    //プロパティ(空の場合、null でも可)
  element.createElement(   //子要素 
    'p', 
    { style: blockStyle },  
    'Hello World!'  
  )
);

//div 要素の子要素に2つの p 要素(配列)
element.createElement(
  'div', 
  {},  
  [ //子要素(配列)
    element.createElement(
      'p', 
      { style: blockStyle },  
      'Hello World!'  
    ),
    element.createElement(
      'p', 
      { style: blockStyle },  
      'Hello World!'  
    )
  ]
);

スタイルを設定していなので、エディター画面では以下のような表示になります。

フロントエンド側では以下のような表示になります。

以下はフロントエンド側の出力の例です。

<div class="wp-block-my-blocks-my-first-block-richtext">
  <h3 class="myRichHeading">Sample Title</h3>
  <div class="myRichText">
    <p>sample text1</p>
    <p>sample text2</p>
  </div>
</div>

関連記事:Gutenberg ブロック作成 attributes の source と selector プロパティ

参考元:Create Custom Gutenberg Block – Part 4: Attributes

編集可能なブロックの作成

参考元:属性と編集可能フィールド

関連項目:RichText コンポーネントを使う

以下は編集領域(入力エリア)が1つのブロックを RichText コンポーネントを使って作成する例です。

プラグインのディレクトリ(my-first-block3)を作成しその中に以下の3つのファイルを作成します。

wp-content
└── plugins
    └── my-first-block3
        ├── block3.js  //ブロック用のスクリプト
        ├── my-first-block3.php  //プラグインファイル(block3.js の読み込み)
        └── style-03.css  //スタイルシート

以下がブロックのスクリプト(block3.js)を読み込むプラグインファイルです。register_block_type() を使ってスクリプトとスタイルをブロックに関連付けしています。

今までのブロックのスクリプト読み込みのプラグインファイルでは省略していましたが、WordPress/gutenberg-examples のサンプルファイルでは9〜12行目の Gutenberg が有効でない場合は何もしないための記述が記載されています(Gutenberg が有効でない場合に register_block_type を使用すると問題があるためかと思います)。

19行目の依存スクリプトのハンドル名(登録スクリプトハンドルの依存性配列)では、wp-block-editor を指定しています(WP v5.2 未満では代わりに wp-editor を指定します)。

my-first-block3.php
<?php
/*
Plugin Name: My First Block 3
*/
defined( 'ABSPATH' ) || exit; 

function my_first_block3_enqueue() {
  
  if ( ! function_exists( 'register_block_type' ) ) {
    // Gutenberg が有効でない場合は何もしない
    return;
  }
  
  //ブロック用のスクリプトを登録
  wp_register_script(
    'my-first-block3-script',
    plugins_url( 'block3.js', __FILE__ ),
    //wp-block-editor を追加( WP v5.2 未満では wp-editor)
    array( 'wp-blocks', 'wp-element', 'wp-block-editor' ) 
  );
  
  //ブロックのスタイルシート(エディター及びフロント)の登録
  wp_register_style(
    'my-first-block3-style',
    plugins_url( 'style-03.css', __FILE__ ),
    array(),
    filemtime( plugin_dir_path( __FILE__ ) . 'style-03.css' )
  );

  register_block_type( 
    'my-blocks/my-first-block3',
    array(
      //エディター用スクリプトにブロックのスクリプトを関連付け
      'editor_script' => 'my-first-block3-script',
      //エディター及びフロント用スタイルとして style-03.css を関連付け
      'style' => 'my-first-block3-style',  
    ) 
  );
}
add_action( 'init', 'my_first_block3_enqueue' );

enqueue_block_editor_assets / enqueue_block_assets

もし、enqueue_block_editor_assets を使う場合は、以下のように記述することができます。

my-first-block3.php
<?php
/*
Plugin Name: My First Block 3
*/
defined( 'ABSPATH' ) || exit; 

function my_first_block3_enqueue_script() {
  //ブロック用のスクリプトをエンキュー
  wp_enqueue_script(
    'my-first-block3-script',
    plugins_url( 'block3.js', __FILE__ ),
    array( 'wp-blocks', 'wp-element', 'wp-block-editor' ) 
  );
}
//ブロックのアセットをエディターでのみエンキューするためのフック
add_action( 'enqueue_block_editor_assets', 'my_first_block3_enqueue_script' );
 
function my_first_block_enqueue3_style() {
  //ブロックのスタイルシート(エディター及びフロント)をエンキュー
  wp_enqueue_style(
    'my-first-block3-style',
    plugins_url( 'style-03.css', __FILE__ ),
    array(),
    filemtime( plugin_dir_path( __FILE__ ) . 'style-03.css' )
  );
  
}
//エディターとフロントエンドの両方でブロックのアセットをエンキューするフック
add_action( 'enqueue_block_assets', 'my_first_block_enqueue3_style');

以下がブロックを定義するファイルです。

save 関数や edit 関数の el(element.createElement)の第1引数にはコンポーネント型(RichText)を指定しています。

属性(attributes)として、入力された値を保持及びどのようにブロックの属性値を取り出すかを定義するための myContent を設定します。

edit 関数内では props 経由で、onChange ハンドラで使用する「属性の値を更新する関数」 setAttributes() と入力の値(value)に設定する属性 attributes.myContent を受け取ります。

RichText の値 value には、属性の値 attributes.myContent を設定します。入力される値が変更されると onChange ハンドラが呼び出され、setAttributes() で属性の値(props.attributes.myContent)をハンドラの引数に渡される入力値(newContent)で更新します。

以下ではプレビュー表示用のプロパティ example も設定しています。但し、RichText のプロパティ placeholder を設定するとプレビューの値を上書きするようなので、コメントアウトしています。

エディター画面でもスタイルを適用するために edit 関数の RichText のプロパティ className を設定しています。save 関数ではクラス名が自動的に追加されます。

( function( blocks, element, blockEditor ) {
  var el = element.createElement;
  // RichText コンポーネント  
  var RichText = blockEditor.RichText ;
  //WP v5.2 未満の場合は以下。1行目の第3引数も editor へ
  //var RichText = editor.RichText ;

  blocks.registerBlockType(
    'my-blocks/my-first-block3', 
    {
      title: 'My First Block Sample 3',
      icon: 'welcome-write-blog',
      category: 'design',
      //attributes に属性 myContent を定義
      attributes: {
        myContent: {
          type: 'array',
          source: 'children',
          selector: 'p',
        },
      },
      //プレビュー表示用のプロパティ
      example: {
        attributes: {
          //プレビューに表示する文字列(定義された属性を使って指定)
          myContent: 'Hello World',
        },
      },
      edit: function( props ) {
        //onChange イベントハンドラ
        function onChangeContent( newContent ) {
          props.setAttributes( { myContent: newContent } );
        }
        return el(
          RichText,
          {
            //ブロックの入力エリアの HTML タグを指定
            tagName: 'p',
            //エディター画面でも自動生成されるクラスを出力(スタイル用)
            className: props.className,
            //placeholder: '文章を入力',  //プレビューを上書きするみたい
            onChange: onChangeContent,
            //値は属性 myContent の値
            value: props.attributes.myContent,
          }
        );
      },

      save: function( props ) {
        return el( 
          RichText.Content, 
          {
            tagName: 'p', 
            value: props.attributes.myContent,
          } 
        );
      },
    }
  );
}(
  window.wp.blocks,
  window.wp.element,
  window.wp.blockEditor
  //window.wp.editor //( WP v5.2 未満)
) );

※ 公式サイトのチュートリアル「属性と編集可能フィールド」では、「ES5 コードを使用して RichText コンポーネントを使用する場合は、wp_register_script 呼び出しの際の登録スクリプトハンドルの依存性配列に wp-editorを追加してください」となっていますが、ブロック用のスクリプトで wp.editor を使うと以下のような非推奨のメッセージがコンソールに表示されるので、代わりに wp.blockEditor を使用しています。

wp.editor.RichText.Content is deprecated. Please use wp.blockEditor.RichText.Content instead.
wp.editor.RichText is deprecated. Please use wp.blockEditor.RichText instead.

以下はエディター及びフロントエンド用のスタイルシートです。「スタイルシートを使ったスタイルの適用」の例と同様、単に文字色、背景色、ボーダーを設定しているだけです。

クラス名はブロック名 my-blocks/my-first-block3 の前に wp-block- を付け、スラッシュを - で置換して生成されます(自動的に付与されるクラス名)。

style-03.css
@charset "UTF-8";
.wp-block-my-blocks-my-first-block3  {
  color: #0E2C76;
  background: #BCCFF9;
  border: 2px solid #5A64B2;
  padding: 20px;
}

以下はエディターのスクリーンショットです。

以下はフロントエンド側です。同じスタイルが適用されています。

見出しを追加

以下のような見出し(固定の文字列)の下にメモ入力用のエリアがあるブロックに変更してみます。

以下がブロックのコードです。

attributes に myHeading という属性を追加して、見出しの文字列を設定しています(save 及び edit 関数の createElement に直接指定することもできます)。

見出し部分は h3 要素、メモ入力部分は div 要素(内部は Enterキーを押すと段落になるように multiline: 'p' を指定)で作成するので、それらを div 要素でラップします(createElement でのネスト)。

見出しとメモ入力部分にスタイルを適用するため、それぞれにクラス(memo-heading と memo-content)を指定して出力するようにしています。

edit 関数では、外側の div 要素にエディター画面でも自動生成されるクラスを出力するように className プロパティを指定しています(save 関数では自動的に付与されます)。

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

  blocks.registerBlockType(
    'my-blocks/my-first-block3', 
    {
      title: 'My First Block Sample 3',
      icon: 'welcome-write-blog',
      category: 'design',
      attributes: {
        myContent: {
          type: 'array',
          source: 'children',
          selector: '.memo-content', //メモ入力エリアのクラスを指定
        },
        //見出しに表示する文字を属性で設定
        myHeading: {
          type: 'string',
          default: 'Memo'
        }
      },
      example: {},
      edit: function( props ) {
        function onChangeContent( newContent ) {
          props.setAttributes( { myContent: newContent } );
        }
        return el(
          //div 要素で全体をラップ
          'div', 
          //外側の div 要素にエディターでも自動生成されるクラスを出力
          { className: props.className }, 
          //h3 と div 要素を子要素に(配列)
          [
            //見出しを追加(固定文字列)
            el(
              'h3',
              { className:'memo-heading' },
              //見出しの文字列は attribute の myHeading に設定
              props.attributes.myHeading 
            ),
            //メモ入力エリア
            el(
              RichText,
              {
                tagName: 'div',  //div に変更
                className: 'memo-content',
                placeholder: 'メモを入力',  
                multiline: 'p', //Enterキーで段落を生成するように追加
                onChange: onChangeContent,
                value: props.attributes.myContent,
              }
            )
          ]
        );
      },

      save: function( props ) {
        return el(
          'div', 
          {}, 
          [
            el(
              'h3', 
              {className:'memo-heading' },
              props.attributes.myHeading 
            ),
            el(
              RichText.Content, 
              {
                tagName: 'div', 
                className: 'memo-content',
                value: props.attributes.myContent,
              } 
            )
          ]
        );
      },
    }
  );
}(
  window.wp.blocks,
  window.wp.element,
  window.wp.blockEditor
) );

CSS を変更します。

style-03.css
@charset "UTF-8";
/* ブロック全体 */
.wp-block-my-blocks-my-first-block3  {
  border: 2px solid #5A64B2;
  background: #fefefe;
}

/* 見出し */
.wp-block-my-blocks-my-first-block3  .memo-heading {
  margin: 0;
  padding: 10px 20px;
  background: #BCCFF9;
  color: #0E2C76;
}

/* メモ入力エリア */
.wp-block-my-blocks-my-first-block3  .memo-content {
  padding: 20px;
}

エディターのみで使用するスタイルを追加

表示を確認してみると、この例の場合、フロントエンド側は問題ありませんが、エディター側が以下のように見出しの上下に余計なマージンが適用されています。調べてみると、これはテーマ(Twentytwenty)のスタイル(editor-style-block.css の .editor-styles-wrapper .wp-block h3)が適用されてしまっているようです。

以下のようにエディターとフロント共用のスタイル(style-03.css)にセレクタを追加しても適用されません。

/* エディター及びフロント用のスタイル */
.wp-block-my-blocks-my-first-block3  .memo-heading, 
.editor-styles-wrapper .wp-block h3 /* セレクタを追加しても適用されない */{
  margin: 0;
  padding: 10px 20px;
  background: #BCCFF9;
  color: #0E2C76;
}

この場合、エディターのみで使用するスタイルシートを作成し、該当するセレクタにスタイルを設定します。この例では editor-03.css という名前で以下のようなスタイルシートを作成して保存します。

@charset "UTF-8";

.editor-styles-wrapper .wp-block h3 {
  margin: 0;
}

ブロックのスクリプトを読み込むプラグインファイル(my-first-block3.php )で追加したスタイルを登録します(24〜30 と 40行目を追加)。

my-first-block3.php(抜粋)
function my_first_block3_enqueue() {
  
  if ( ! function_exists( 'register_block_type' ) ) {
    // Gutenberg is not active.
    return;
  }
  
  //ブロック用のスクリプトを登録
  wp_register_script(
    'my-first-block3-script',
    plugins_url( 'block3.js', __FILE__ ),
    //wp-block-editor を追加( WP v5.2 未満では wp-editor)
    array( 'wp-blocks', 'wp-element', 'wp-block-editor' ) 
  );
  
  //ブロックのスタイルシート(エディター及びフロント)の登録
  wp_register_style(
    'my-first-block3-style',
    plugins_url( 'style-03.css', __FILE__ ),
    array(),
    filemtime( plugin_dir_path( __FILE__ ) . 'style-03.css' )
  );
  
  //エディターのみで使用するスタイルを追加(★)
  wp_register_style(
    'my-first-block3-editor-style',
    plugins_url( 'editor-03.css', __FILE__ ),
    array(),
    filemtime( plugin_dir_path( __FILE__ ) . 'editor-03.css' )
  );

  register_block_type( 
    'my-blocks/my-first-block3',
    array(
      //エディター用スクリプトにブロックのスクリプトを関連付け(登録)
      'editor_script' => 'my-first-block3-script',
      //エディター及びフロント用スタイルとして style-03.css を関連付け
      'style' => 'my-first-block3-style', 
      //エディターのみで使用するスタイルとして editor-03.css を関連付け(★)
      'editor_style'  => 'my-first-block3-editor-style',
    ) 
  );
}
add_action( 'init', 'my_first_block3_enqueue' );

もし、enqueue_block_editor_assets フックを使っている場合は、エディターのみで使用するスタイルのエンキューを追加します(10〜15行目)。

my-first-block3.php(抜粋)
function my_first_block3_enqueue_script() {
  //ブロック用のスクリプトをエンキュー
  wp_enqueue_script(
    'my-first-block3-script',
    plugins_url( 'block3.js', __FILE__ ),
    array( 'wp-blocks', 'wp-element', 'wp-block-editor' ) 
  );
  
  //エディターのみで使用するスタイルをエンキュー(★)
  wp_enqueue_style(
    'my-first-block3-editor-style',
    plugins_url( 'style-03.css', __FILE__ ),
    array(),
    filemtime( plugin_dir_path( __FILE__ ) . 'style-03.css' )
  );
}
//ブロックのアセットをエディターでのみエンキューするためのフック
add_action( 'enqueue_block_editor_assets', 'my_first_block3_enqueue_script' );