1. ホーム
  2. WordPress

【WordPress】wp_register_block_types_from_metadata_collection() で複数のカスタムブロックを登録する方法

Share

カスタムブロックを作り始めると、「お知らせ枠」「料金表」「FAQ」のように、1つのプラグインの中にブロックが少しずつ増えていきます。ブロックが1つなら register_block_type() を1回呼べば済みますが、数が増えると登録の書き方を考え直したくなります。WordPress 6.7 と 6.8 では、この「複数ブロックの登録」をまとめて効率よく行うための関数が追加されました。この記事では、wp_register_block_types_from_metadata_collection() を中心に、@wordpress/scripts で生成する blocks-manifest.php の役割と、古いバージョンにも対応する書き方を解説します。

従来の書き方:register_block_type() を1つずつ呼ぶ

まずは比較のために、これまでの書き方を見ておきます。register_block_type() に block.json があるディレクトリを渡すと、そのブロックが登録されます。ブロックが3つあれば、3回呼ぶことになります。

my-blocks.php
function my_blocks_init() {
    register_block_type( __DIR__ . '/build/blocks/notice' );
    register_block_type( __DIR__ . '/build/blocks/pricing' );
    register_block_type( __DIR__ . '/build/blocks/faq' );
}
add_action( 'init', 'my_blocks_init' );

ブロックを追加するたびにこの行を書き足すのが面倒なので、glob() でディレクトリを探してループさせる書き方もよく使われてきました。

my-blocks.php
function my_blocks_init() {
    // build/blocks/ 直下の block.json をすべて探して登録する
    foreach ( glob( __DIR__ . '/build/blocks/*/block.json' ) as $block_json ) {
        register_block_type( dirname( $block_json ) );
    }
}
add_action( 'init', 'my_blocks_init' );

どちらの書き方でも動作はします。ただし、ページを表示するたびに(init は毎リクエストで実行されます)ブロックの数だけ block.json を読み込み、JSON を解析する処理が走ります。ブロックが数十個ある大きなプラグインでは、これが無視できない負荷になります。ここを改善するために用意されたのが、次に紹介する「ブロックメタデータコレクション」の仕組みです。

ブロックメタデータコレクションとは

考え方はシンプルで、すべてのブロックの block.json の中身を、あらかじめ1つの PHP ファイル(マニフェストファイル)にまとめておきます。WordPress はそのファイルを1回 require するだけで、全ブロックの情報を受け取れます。PHP ファイルなので OPcache(PHP のコンパイル結果のキャッシュ)が効き、JSON ファイルを毎回読み込んで解析するよりも速くなります。

このマニフェストファイルは手で書くものではなく、@wordpress/scripts のビルド時に自動生成します。ファイル名は既定で blocks-manifest.php です。関連する関数とコマンドを整理すると次のようになります。

名前種類役割
wp_register_block_metadata_collection()PHP 関数(WordPress 6.7〜)マニフェストファイルを WordPress に登録する。ブロック自体の登録は行わない
wp_register_block_types_from_metadata_collection()PHP 関数(WordPress 6.8〜)マニフェストの登録と、そこに含まれる全ブロックの登録を1回で行う
wp-scripts build --blocks-manifestビルドオプション(@wordpress/scripts 30.14.0〜)通常のビルドと同時に blocks-manifest.php を生成する。start でも使える
wp-scripts build-blocks-manifestコマンド(@wordpress/scripts 30.3.0〜)blocks-manifest.php の生成だけを単独で行う

複数ブロック用のディレクトリ構成

ここでは、プラグインの src/blocks/ の下にブロックごとのフォルダーを作る構成で進めます。各フォルダーに block.json と、エディター用のスクリプトを置きます。

ディレクトリ構成
my-blocks/
├── my-blocks.php
├── package.json
├── src/
│   └── blocks/
│       ├── notice/
│       │   ├── block.json
│       │   ├── index.js
│       │   └── edit.js
│       ├── pricing/
│       │   ├── block.json
│       │   └── index.js
│       └── faq/
│           ├── block.json
│           └── index.js
└── build/            ← wp-scripts build で生成される
    ├── blocks-manifest.php
    └── blocks/
        ├── notice/
        ├── pricing/
        └── faq/

wp-scripts build は src フォルダー以下を探して block.json を見つけ、その中の editorScript や viewScript に書かれたファイルをビルドの入口(エントリーポイント)として扱います。出力先は src からの相対パスがそのまま保たれるため、src/blocks/notice/ のファイルは build/blocks/notice/ に出力されます。block.json 自体も同じ場所にコピーされます。

一方で、--blocks-manifest で生成されるマニフェストは build/ の直下に置かれます。この「ブロックは build/blocks/ の下、マニフェストは build/ の直下」という位置関係は、あとで PHP 側にパスを書くときに大事になります。

各ブロックの block.json は、たとえば次のように書きます。name はプラグイン内で重ならないようにします。

src/blocks/notice/block.json
{
    "$schema": "https://schemas.wp.org/trunk/block.json",
    "apiVersion": 3,
    "name": "my-blocks/notice",
    "title": "お知らせ枠",
    "category": "text",
    "icon": "info",
    "textdomain": "my-blocks",
    "editorScript": "file:./index.js"
}
src/blocks/notice/index.js
import { registerBlockType } from '@wordpress/blocks';
import metadata from './block.json';
import Edit from './edit';

registerBlockType( metadata.name, {
    edit: Edit,
    save: () => null, // ここでは save を省略した最小構成にしている
} );

blocks-manifest.php を生成する

–blocks-manifest オプションを付ける

いちばん手軽なのは、package.json の build と start のスクリプトに --blocks-manifest を付ける方法です。ビルドが終わるたびにマニフェストも作り直されるので、生成し忘れが起きません。

package.json
{
    "name": "my-blocks",
    "scripts": {
        "build": "wp-scripts build --blocks-manifest",
        "start": "wp-scripts start --blocks-manifest"
    },
    "devDependencies": {
        "@wordpress/scripts": "^30.14.0"
    }
}

npm run build を実行すると、ビルド結果の出力先(既定は build)の中を探して見つかったすべての block.json を1つの配列にまとめ、build/blocks-manifest.php として書き出します。中身はおおよそ次のような形です。

build/blocks-manifest.php
<?php
// This file is generated. Do not modify it manually.
return array(
	'faq' => array(
		'apiVersion' => 3,
		'name' => 'my-blocks/faq',
		'title' => 'FAQ',
		// ...block.json の残りの内容
	),
	'notice' => array(
		'apiVersion' => 3,
		'name' => 'my-blocks/notice',
		'title' => 'お知らせ枠',
		// ...
	),
	'pricing' => array(
		// ...
	)
);

配列のキーには、ブロック名ではなく block.json が入っているフォルダーの名前が使われます。build/blocks/notice/block.json ならキーは notice です。WordPress はこのキーとコレクションのパスをつなげて block.json の場所を組み立てるので、この仕様は後半のトラブルの話にも関わってきます。

build-blocks-manifest コマンドで単独生成する

ビルドとは別にマニフェストだけを作りたい場合は、build-blocks-manifest コマンドを使います。--input で block.json を探すフォルダー(既定は build)、--output で出力するファイル(既定は --input のフォルダー直下の blocks-manifest.php)を指定できます。

package.json
{
    "scripts": {
        "build": "wp-scripts build && wp-scripts build-blocks-manifest --input=build/blocks --output=build/blocks-manifest.php"
    }
}

このコマンドを使う場合は、ビルドのあとに必ず実行するようにスクリプトへ組み込んでおくのが安全です。ブロックを追加したのにマニフェストを作り直していないと、新しいブロックが登録されないためです。

WordPress 6.8 以降の登録方法

マニフェストが用意できたら、PHP 側は wp_register_block_types_from_metadata_collection() を1回呼ぶだけです。

my-blocks.php
<?php
/**
 * Plugin Name:       My Blocks
 * Requires at least: 6.8
 */

function my_blocks_init() {
    wp_register_block_types_from_metadata_collection(
        __DIR__ . '/build/blocks',              // 各ブロックのフォルダーが並ぶ場所
        __DIR__ . '/build/blocks-manifest.php'  // マニフェストファイル
    );
}
add_action( 'init', 'my_blocks_init' );

引数は次の2つです。

引数説明
$path(必須)コレクションの基準となる絶対パス。この直下に、マニフェストのキーと同じ名前のブロックフォルダーが並んでいる必要がある
$manifest(任意)マニフェストファイルの絶対パス。指定すると、内部で wp_register_block_metadata_collection() を呼んでコレクションを登録する

この関数の中身は、マニフェストを登録したあと、マニフェストのキーから {$path}/{キー}/block.json というパスを組み立て、1つずつ register_block_type_from_metadata() に渡す、という処理です。登録自体は1ブロックずつ行われていますが、block.json の読み込みと解析は行われず、マニフェストの配列が使われます。

上の例で第1引数を __DIR__ . '/build' ではなく __DIR__ . '/build/blocks' にしているのは、先ほどのディレクトリ構成のためです。マニフェストのキーは notice なので、基準を build にすると build/notice/block.json を探しに行ってしまいます。ブロックを src/ 直下(src/notice/ など)に置く構成であれば、基準は __DIR__ . '/build' になります。

WordPress 6.7 では wp_register_block_metadata_collection() を使う

WordPress 6.7 には wp_register_block_types_from_metadata_collection() がまだありません。代わりに使えるのが wp_register_block_metadata_collection() です。この関数はマニフェストを「登録するだけ」で、ブロックそのものは登録しません。そのため、ブロックの登録は従来どおり register_block_type() で行います。

my-blocks.php
function my_blocks_init() {
    wp_register_block_metadata_collection(
        __DIR__ . '/build/blocks',
        __DIR__ . '/build/blocks-manifest.php'
    );

    // ブロックの登録は自分で行う
    $manifest_data = require __DIR__ . '/build/blocks-manifest.php';
    foreach ( array_keys( $manifest_data ) as $block_type ) {
        register_block_type( __DIR__ . "/build/blocks/{$block_type}" );
    }
}
add_action( 'init', 'my_blocks_init' );

コレクションを先に登録しておくと、register_block_type() にそのコレクション内のフォルダーが渡されたとき、WordPress は block.json を読まずにマニフェストのデータを使います。書く量は従来とあまり変わりませんが、JSON の読み込みと解析を省ける点が違います。ループするキーも glob() で探さず、マニフェストから取り出しています。

6.7 未満にも対応する書き方

配布するプラグインなどで古いバージョンもサポートしたい場合は、function_exists() で関数があるかどうかを確かめて分岐します。Make WordPress Core の 6.8 向けの記事でも、同じ形のコードが紹介されています。

my-blocks.php
function my_blocks_init() {
    $blocks_dir = __DIR__ . '/build/blocks';
    $manifest   = __DIR__ . '/build/blocks-manifest.php';

    // WordPress 6.8 以降
    if ( function_exists( 'wp_register_block_types_from_metadata_collection' ) ) {
        wp_register_block_types_from_metadata_collection( $blocks_dir, $manifest );
        return;
    }

    // WordPress 6.7 ならマニフェストだけ登録しておく
    if ( function_exists( 'wp_register_block_metadata_collection' ) ) {
        wp_register_block_metadata_collection( $blocks_dir, $manifest );
    }

    // 6.7 以前はブロックを自分で1つずつ登録する
    $manifest_data = require $manifest;
    foreach ( array_keys( $manifest_data ) as $block_type ) {
        register_block_type( "{$blocks_dir}/{$block_type}" );
    }
}
add_action( 'init', 'my_blocks_init' );

6.6 以前では最後のループだけが実行され、従来どおり各ブロックの block.json が読み込まれます。マニフェストは単に「フォルダー名の一覧」として使われる形です。どのバージョンでも同じ build/ の中身で動くので、ビルドの手順を分ける必要はありません。

ブロックが登録されない・古い内容のままになるとき

ブロックが1つも挿入候補に出てこない

まず疑うのは第1引数のパスです。前述のとおり、WordPress は「第1引数のパス+マニフェストのキー+/block.json」でファイルの場所を決めます。src/blocks/ の下にブロックを置いているのに第1引数を build にしていると、存在しない build/notice/block.json を探すことになり、ブロックは登録されません。build フォルダーを開いて、block.json が実際にどこにあるかを確認し、その一つ上のフォルダーを第1引数に指定してください。

マニフェストファイル自体が存在しない場合もあります。--blocks-manifest を付けずにビルドした、あるいはビルド前のソースをそのままアップロードした、といったケースです。WP_DEBUG を有効にしていれば、「The specified manifest file does not exist.」という通知が出るので原因を見分けられます。

プラグインのフォルダーそのものを第1引数にしている

コレクションのパスには制限があり、wp-content/plugins や wp-content/themes、wp-includes といったルートのフォルダーそのものや、それより上のフォルダーは指定できません。指定すると「Block metadata collections cannot be registered as one of the following directories or their parent directories」という通知が出て、登録が失敗します。__DIR__ . '/build/blocks' のように、自分のプラグインの中のフォルダーを指定していれば問題になりません。

block.json を変更したのに反映されない

マニフェストを使っている間、WordPress は block.json ではなくマニフェストの内容を見ます。build 内の block.json を直接書き換えたり、src の block.json を変更したあとにマニフェストを作り直していなかったりすると、変更は反映されません。開発中は npm run start(--blocks-manifest 付き)を動かしておけば、ファイルの変更に合わせてマニフェストも更新されます。build-blocks-manifest を別に実行する構成の場合は、その実行を忘れていないか確認してください。

同じ名前のフォルダーが2つある

マニフェストのキーはフォルダー名だけで作られるため、src/blocks/card/ と src/widgets/card/ のように、別の場所に同じ名前のフォルダーがあるとキーがぶつかり、片方の情報で上書きされてしまいます。ブロックのフォルダーは1か所にまとめ、名前を重複させないようにしておくと安心です。

まとめ

複数のカスタムブロックを持つプラグインでは、wp-scripts build --blocks-manifest(または build-blocks-manifest コマンド)で全ブロックの block.json を blocks-manifest.php にまとめ、WordPress 6.8 以降なら wp_register_block_types_from_metadata_collection() を1回呼ぶだけで全ブロックを登録できます。6.7 では wp_register_block_metadata_collection() でマニフェストを登録したうえで register_block_type() をループし、それより前のバージョンも対象にするなら function_exists() で分岐させます。第1引数には「ブロックのフォルダーが並んでいる場所」を渡すことがポイントで、src/blocks/ の下にブロックを置くなら build/blocks を指定します。block.json を変えたらマニフェストも作り直す、という点もあわせて覚えておいてください。

参考ページ