DigiPress

Highly Flexible WordPress Theme

メニュー

[WP]create-block でブロックの追加とダイナミックブロックを作成する方法

WordPress 公式パッケージである @wordpress/create-block の バージョン 4.0.0 にて、既存のプラグインに新たに単体のブロックのみを追加するオプションと、例えば「最近の投稿」のようにサーバーサイドからデータを受け取って動的なコンテンツを表示するようなダイナミックブロックの雛形を作成するオプションが追加されました。

create-block : 4.0.0 (2022-08-24)

新たなブロックを作成する場合、従来は各ファイルをコピペするなどしていた手作業がなくなるため、結構ありがたいアップデート内容です。

それでは実際にプラグインの作成からブロックの追加、ダイナミックブロックの作成までやってみましょう。


ローカル環境に WordPress がセットアップされている前提になります。
ローカル環境への WordPress のインストールは、LOCAL などを利用すると簡単に構築できます。
また、Node.js 14.0.0 以上、npm 6.14.4 以上の環境があることが前提です(npm は Node.js と同時にインストールされます)。

ブロックプラグインの作成

ターミナルを起動し、WordPress のプラグインディレクトリに移動しておきます。

cd {wordpressインストール先}/wp-content/plugins

create-block で適当な名前でプラグイン(+ ブロック)を作成します。

npx @wordpress/create-block my-blocks

プロンプトが戻ってきたら、プラグインの src ディレクトリまで移動して中身を確認してみます。

cd my-blocks/src
ls -l

ここで表示された以下の 6つのファイルが、作成された “my-blocks” ブロックのためのソースファイルです。

  • block.json
  • edit.js
  • editor.scss
  • index.js
  • save.js
  • style.scss

ディレクトリ単位でブロックを分ける

ここに「block-one」というディレクトリを作成し、これらのファイルを移動させて、さらにこれから追加するブロックも含めてまとめておくブロック専用ディレクトリ「blocks」を作成して「block-one」を入れておきます。

mkdir block-one
mv *.json *.js *.scss block-one
mkdir blocks
mv block-one blocks
cd blocks
ディレクトリ構成

コマンドが面倒な場合は、もちろん GUI からフォルダの作成とファイルの移動をしてもOKです。

複数のブロックを提供するプラグインとしたいので、プラグイン名とブロック名を分けておきます。
ブロック名(name)とタイトル(title)をプラグイン名(my-blocks)からブロック名(block-one)に変えておきます。

/src/blocks/block-one/block.json変更前
{
	"$schema": "https://schemas.wp.org/trunk/block.json",
	"apiVersion": 2,
	"name": "create-block/my-blocks",
	"version": "0.1.0",
	"title": "My Blocks",
	"category": "widgets",
	"icon": "smiley",
	"description": "Example static block scaffolded with Create Block tool.",
	"supports": {
		"html": false
	},
	"textdomain": "my-blocks",
	"editorScript": "file:./index.js",
	"editorStyle": "file:./index.css",
	"style": "file:./style-index.css"
}
/src/blocks/block-one/block.json変更後
{
	"$schema": "https://schemas.wp.org/trunk/block.json",
	"apiVersion": 2,
	"name": "create-block/block-one",
	"version": "0.1.0",
	"title": "Block One",
	"category": "widgets",
	"icon": "smiley",
	"description": "Example static block scaffolded with Create Block tool.",
	"supports": {
		"html": false
	},
	"textdomain": "my-blocks",
	"editorScript": "file:./index.js",
	"editorStyle": "file:./index.css",
	"style": "file:./style-index.css"
}
/src/blocks/block-one/edit.js, save.js変更前
{ __( 'My Blocks – hello from the editor!', 'my-blocks' ) }
/src/blocks/block-one/edit.js, save.js変更後
{ __( 'Block One – hello from the editor!', 'my-blocks' ) }

地味に面倒ですね、、「プラグイン」 = 「1ブロック」というプラグインなどほぼありえないため、create-block v.4.0.0 で多少マシになったとはいえ、この辺も複数ブロックを包括する前提でのプラグインのビルドを前提として、もう少し汎用性を考慮したジェネレーターになることを期待せざるを得ません。

追加ブロックの生成

ここで create-block v.4.0.0 の新しいオプション --no-plugin を利用して、今作った「my-blocks」プラグインに追加の単体ブロックのみを作成してみます。

カレントが「/src/blocks」ディレクトリであることを確認して --no-plugin パラメータを付けて「block-two」というディレクトリに追加のブロック用のファイルを出力します。

npx @wordpress/create-block block-two --no-plugin

以下のように出力されて「block-two」ディレクトリとその中にソースファイルが作成されていれば成功です。

Creating a new block in the block-two directory.

Creating a "block.json" file.

Done: Block "Block Two" bootstrapped in the block-twodirectory.

Code is Poetry

追加ブロック(Block Two)については、テキストドメイン名をプラグインのものに変えておきます。

/src/blocks/block-two/block.json変更前
"textdomain": "block-two",
/src/blocks/block-two/block.json変更後
"textdomain": "my-blocks",

ダイナミックブロックの生成

v.4.0.0 のもうひとつのオプション --variantdynamic を指定すると、記事一覧など WordPress からデータを抽出するような動的なコンテンツの表示を可能にするダイナミックブロックの雛形を追加ブロックとして既存プラグインに追加できます。

以下のコマンドで Gutenberg プロジェクトの公式リポジトリにあるテンプレートを利用したダイナミックブロック(Block Three)を作成します。

npx @wordpress/create-block block-three --no-plugin --template=@wordpress/create-block-tutorial-template --variant=dynamic

--template パラメータでローカルまたは公開上のテンプレートを指定できます。

「block-three」ディレクトリ内を確認すると、save.js がない代わりに、”template.php” という ダイナミックレンダリング用の PHP ファイルがあることがわかります。

“inidex.js” には、やはり静的ブロックには存在する save プロパティは不要なので存在していません。

index.js
/**
 * Internal dependencies
 */
import Edit from './edit';
import metadata from './block.json';

/**
 * Every block starts by registering a new block type definition.
 *
 * @see https://developer.wordpress.org/block-editor/developers/block-api/#registering-a-block
 */
registerBlockType( metadata.name, {
	/**
	 * Used to construct a preview for the block to be shown in the block inserter.
	 */
	example: {
		attributes: {
			message: 'Block Three',
		},
	},
	/**
	 * @see ./edit.js
	 */
	edit: Edit,
} );

フロントエンド用のスタイル(style.scss)を見てみると、何やら自前のフォントが指定されています。

style.scss変更前
@font-face {
	font-family: Gilbert;
	src: url(../assets/gilbert-color.otf);
	font-weight: 700;
}

.wp-block-create-block-block-three {
	font-family: Gilbert, sans-serif;
	font-size: 64px;
}

このテンプレートから作成したブロックのディレクトリ(block-three)を確認すると、たしかに「assets」というディレクトリがあり、この中に「gilbert-color.otf」というフォントデータがあります。

フォントデータはフォルダ(assets)ごとプラグインディレクトリのルートにコピーしておきます。
また、今回のサンプルでは、ビルド後は /my-blocks/build/blocks/block-three に style.css が出力されるため、CSS のフォントパスの読み込み先(src)も変更しておきます。

style.scss変更後
@font-face {
	font-family: Gilbert;
	src: url(../../../assets/gilbert-color.otf);
	font-weight: 700;
}

.wp-block-create-block-block-three {
	font-family: Gilbert, sans-serif;
	font-size: 64px;
}

“block.json” には、message というこのブロック用の変数が一つだけ定義されています。
とりあえず message に初期値をセットしておきます。

block.json変更前
"attributes": {
	"message": {
		"type": "string"
	}
},
block.json変更後
"attributes": {
	"message": {
		"type": "string",
		" default": "Hello, world!"
	}
},

エディター側の “edit.js” には、message 変数(テキスト)の値をユーザーが自由に変更するためのテキストコントロールがあります。

edit.js
export default function Edit( { attributes, setAttributes } ) {
	const blockProps = useBlockProps();
	return (
		<div { ...blockProps }>
			<TextControl
				value={ attributes.message }
				onChange={ ( val ) => setAttributes( { message: val } ) }
			/>
		</div>
	);
}

フロントサイドで表示される “template.php” は段落タグのみです。

template.php変更前
<p <?php echo get_block_wrapper_attributes(); ?>>
	<?php echo esc_html( $attributes['message'] ); ?>
</p>

template.php という名前を index.php に変えて、後述する register_block_type 関数に指定するコールバック関数を用意しておきます。

index.php元 template.php
function my_blocks_render_callback_block_three( $attributes ){

	$wrapper_attributes = get_block_wrapper_attributes();

	return sprintf(
		'<p %1$s>%2$s</p>',
		$wrapper_attributes,
		esc_html( $attributes['message'] )
	);
}

段落タグにこのブロックのラッパークラスを付けて保存されている message を表示するだけの内容です。

関数の命名規則は、my_blocks_render_callback_{$block_name} としておきます。

カスタムブロックの登録

プラグインディレクトリ直下の “my-blocks.php” にて register_block_type 関数によってビルド先(/build)に展開されるブロックファイルを対象に読み込まれていますが、複数のブロックを作りディレクトリ構造が変わっているため、以下のように修正しておきます。

my-blocks.php変更前
function create_block_my_blocks_block_init() {
	register_block_type( __DIR__ . '/build' );
}
add_action( 'init', 'create_block_my_blocks_block_init' );
my-blocks.php変更後
function create_block_my_blocks_block_init() {
    foreach ( glob( plugin_dir_path( __FILE__ ) . 'build/blocks/*' ) as $block ) {
        register_block_type( $block );
    }
}
add_action( 'init', 'create_block_my_blocks_block_init' );

register_block_type は block.json があるディレクトリを指定するだけで読み込んでくれます(block.json 省略可)。

さらに、ダイナミックブロックの場合は、register_block_type 関数の第2引数の配列で指定する render_callback にレンダリング用の関数を指定する必要があります。

スタティック(静的)ブロックと分けてブロックを登録するため、ダイナミックブロックのみに存在するレンダリング用の index.php の存在有無で分岐して読み込ませます。

my-blocks.phpダイナミックブロック対応
function create_block_my_blocks_block_init() {
    foreach ( glob( plugin_dir_path( __FILE__ ) . 'build/blocks/*' ) as $block ) {
        if ( file_exists( $block . '/index.php' ) ) {
            // Dynamic block
            require_once( $block . '/index.php' );

            register_block_type(
                $block,
                array(
                    'render_callback' => 'my_blocks_render_callback_' . str_replace( '-', '_', basename( $block ) ),
                )
            );

        } else {
            // Static block
            register_block_type( $block );
        }
    }
}
add_action( 'init', 'create_block_my_blocks_block_init' );

index.php にあるコールバック関数は、my_blocks_render_callback_{$block_name} としたため、ループ内での render_callback に指定する値は、'my_blocks_render_callback_' . str_replace( '-', '_', basename( $block ) ) として動的に変化させています。

glob 関数を利用して、/build/bocks ディレクトリの一覧(ブロック)を走査してブロックを登録しているため、スタティックブロック、ダイナミックブロック共に増えてもこのコードを修正する必要はありません。

ビルドして確認

ダイナミックブロックの場合、PHP ファイル(index.php)にて register_block_type 関数によってブロックをレンダリングするため、PHP ファイルはソースディレクトリ(/scr) から ビルドディレクトリ(/build) にそのままコピーさせます。

デフォルトでは PHP ファイルまでコピーされないので、プラグインディレクトリ直下に “webpack.config.js” という設定ファイルを作成し、以下を記述しておきます。

webpack.config.js
process.env.WP_COPY_PHP_FILES_TO_DIST = true;
const defaultConfig = require("@wordpress/scripts/config/webpack.config");

module.exports = defaultConfig;

process.env.WP_COPY_PHP_FILES_TO_DIST という環境変数を有効にすることで、ビルド時に *.php もそのままコピーされます。
その他はデフォルトの設定のまま継承しています。

ここまでできたら、後は一旦ビルドして3つのブロックが追加されているか確認してみます。
cd コマンドでプラグインのルート(/my-blocks) まで戻ってからビルドします。

cd ../../
npm run build

build ディレクトリが出力されてビルドに成功しました。
index.php もちゃんとコピーされています。

実際にブロックエディターでブロックを追加、表示できるか確認してみると、問題なく追加されています。

標準のダッシュアイコンのままなので見た目はめちゃくちゃですが、3つのブロックともエディター内に追加できました。

記事一覧を表示するダイナミックブロックを作る

テンプレートから追加したサンプルのダイナミックブロックは、ただテキストを表示するのみなので、ダイナミックブロックらしく WordPress から投稿データを受け取って整形表示する動的なコンテンツに変えてみます。

まず、「block-three」ディレクトリの block.json の attributes を以下に書き換えます。

block.jsonattributes
"attributes": {
	"numberOfItems": {
		"type": "number",
		"default": 3
	},
	"displayDate": {
		"type": "boolean",
		"default": true
	},
	"displayThumbnail": {
		"type": "boolean",
		"default": true
	}
},

抽出する投稿の件数(numberOfItems)、日付表示有無(displayDate)、サムネイル表示有無(displayThumbnail)の3つのパラメータを追加しました。

投稿データを取得

useSelect フックで getEntityRecords という関数を利用して、WordPress のコアデータから指定条件に合致する投稿データの一覧を取得します。

import { useSelect } from '@wordpress/data';

const posts = useSelect(
    ( select ) => {
        return select( 'core' ).getEntityRecords( 'postType', 'post', {
            'per_page': numberOfItems,
            '_embed': true
        });
    },
    [ numberOfItems ]
);

ここでは、投稿タイプ(postType)が postattributes に追加した numberOfItems パラメータの数だけ投稿データを取得しています。
_embedtrue にすることで、アイキャッチのデータも取得対象にしています。
numberOfItems が更新されるたびに posts 変数に入るデータが更新されます。

サイドバーコントロールの追加

attributes に追加した投稿の取得条件や表示条件のパラメータを設定するためのコントローラーを Edit 関数に追加します。

return (
    <>
        <InspectorControls>
            <PanelBody title={ __( 'Content Settings', 'my-blocks' ) }>
                <PanelRow>
                    <QueryControls
                        numberOfItems={ numberOfItems }
                        onNumberOfItemsChange={ ( value ) =>
                            setAttributes( { numberOfItems: value } )
                        }
                        minItems={ 1 }
                        maxItems={ 10 }
                    />
                </PanelRow>
                <PanelRow>
                    <ToggleControl
                        label={ __( 'Show Featured Image', 'my-blocks' ) }
                        checked={ displayThumbnail }
                        onChange={ () =>
                            setAttributes( { displayThumbnail: ! displayThumbnail } )
                        }
                    />
                </PanelRow>
                <PanelRow>
                    <ToggleControl
                        label={ __( 'Show Date', 'my-blocks' ) }
                        checked={ displayDate }
                        onChange={ () =>
                            setAttributes( { displayDate: ! displayDate } )
                        }
                    />
                </PanelRow>
            </PanelBody>
        </InspectorControls>

        <div { ...useBlockProps() }>
            ...
        </div>
    </>
)

QueryControls コンポーネントを利用すると、他にも投稿順序やカテゴリーの指定などの条件を追加できます。
後は、ToggleControl コンポーネントでサムネイルと日付の表示有無をトグルボタンで設定できるようにしています。

エディター側でレンダリングされるブロック

続けて、エディター側で表示するブロックのコンテンツ部分をコーディングしていきます。

edit.jsコンテンツ部分
return (
    <>
        <InspectorControls>
            ...
        </InspectorControls>

        <div { ...useBlockProps() }>
            <Disabled>
                <ul className='wp-block-create-block-block-three__post-items'>
                    { posts && posts.map(( post ) => {
                        return (
                            <li key={ post.id }>
                                {
                                    displayThumbnail &&
                                    post._embedded &&
                                    post._embedded['wp:featuredmedia'] &&
                                    post._embedded['wp:featuredmedia'][0] &&
                                    <a href={ post.link } className='wp-block-create-block-block-three__post-thumbnail-link'>
                                        <img
                                            className='wp-block-create-block-block-three__post-thumbnail'
                                            src={ post._embedded['wp:featuredmedia'][0].media_details.sizes.medium.source_url }
                                            alt={ post._embedded['wp:featuredmedia'][0].alt_text }
                                        />
                                    </a>
                                }
                                <div className='wp-block-create-block-block-three__text-contents'>
                                    <h3 className='wp-block-create-block-block-three__post-title'>
                                        <a href={ post.link }>
                                            { post.title.rendered ? (
                                                post.title.rendered
                                            ) : (
                                                __( 'No title', 'my-blocks' )
                                            )}
                                        </a>
                                    </h3>
                                    {
                                        displayDate && (
                                            <time
                                                className='wp-block-create-block-block-three__post-date'
                                                dateTime={ format( 'c', post.date_gmt ) }
                                            >
                                                { dateI18n(
                                                    __experimentalGetSettings().formats.date,
                                                    post.date_gmt
                                                )}
                                            </time>
                                        )
                                    }
                                </div>
                            </li>
                        )
                    })}
                </ul>
            </Disabled>
        </div>
    </>
);

posts に代入された投稿データを順にリストタグ(li)で括ります。内部はサムネイル、投稿タイトル、日付の順で出力しています。

edit.js 全体

edit.js をまとめると、以下のようになります。

edit.js
import { useSelect } from '@wordpress/data';

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

import {
    dateI18n,                   // 日付をフォーマットし、サイトのロケールに変換
    format,                     // 日付のフォーマット
    __experimentalGetSettings   // WordPress の一般設定の日付フォーマットにする
} from '@wordpress/date';

import {
    useBlockProps,
    InspectorControls,
} from '@wordpress/block-editor';

import {
    Disabled,
    PanelBody,
    PanelRow,
    QueryControls,
    RangeControl,
    ToggleControl,
} from '@wordpress/components';

export default function Edit( { attributes, setAttributes } ) {

    const {
        numberOfItems,
        displayDate,
        displayThumbnail
    } = attributes;

    const posts = useSelect(
        ( select ) => {
            return select( 'core' ).getEntityRecords( 'postType', 'post', {
                'per_page': numberOfItems,
                '_embed': true
            });
        },
        [ numberOfItems ]
    );

    const blockProps = useBlockProps();

    return (
        <>
            <InspectorControls>
                <PanelBody title={ __( 'Content Settings', 'my-blocks' ) }>
                    <PanelRow>
                        <QueryControls
                            numberOfItems={ numberOfItems }
                            onNumberOfItemsChange={ ( value ) =>
                                setAttributes( { numberOfItems: value } )
                            }
                            minItems={ 1 }
                            maxItems={ 10 }
                        />
                    </PanelRow>
                    <PanelRow>
                        <ToggleControl
                            label={ __( 'Show Featured Image', 'my-blocks' ) }
                            checked={ displayThumbnail }
                            onChange={ () =>
                                setAttributes( { displayThumbnail: ! displayThumbnail } )
                            }
                        />
                    </PanelRow>
                    <PanelRow>
                        <ToggleControl
                            label={ __( 'Show Date', 'my-blocks' ) }
                            checked={ displayDate }
                            onChange={ () =>
                                setAttributes( { displayDate: ! displayDate } )
                            }
                        />
                    </PanelRow>
                </PanelBody>
            </InspectorControls>

            <div { ...useBlockProps() }>
                <Disabled>
                    <ul className='wp-block-create-block-block-three__post-items'>
                        { posts && posts.map(( post ) => {
                            return (
                                <li key={ post.id }>
                                    {
                                        displayThumbnail &&
                                        post._embedded &&
                                        post._embedded['wp:featuredmedia'] &&
                                        post._embedded['wp:featuredmedia'][0] &&
                                        <a href={ post.link } className='wp-block-create-block-block-three__post-thumbnail-link'>
                                            <img
                                                className='wp-block-create-block-block-three__post-thumbnail'
                                                src={ post._embedded['wp:featuredmedia'][0].media_details.sizes.medium.source_url }
                                                alt={ post._embedded['wp:featuredmedia'][0].alt_text }
                                            />
                                        </a>
                                    }
                                    <div className='wp-block-create-block-block-three__text-contents'>
                                        <h3 className='wp-block-create-block-block-three__post-title'>
                                            <a href={ post.link }>
                                                { post.title.rendered ? (
                                                    post.title.rendered
                                                ) : (
                                                    __( 'Default title', 'my-blocks' )
                                                )}
                                            </a>
                                        </h3>
                                        {
                                            displayDate && (
                                                <time
                                                    className='wp-block-create-block-block-three__post-date'
                                                    dateTime={ format( 'c', post.date_gmt ) }
                                                >
                                                    { dateI18n(
                                                        __experimentalGetSettings().formats.date,
                                                        post.date_gmt
                                                    )}
                                                </time>
                                            )
                                        }
                                    </div>
                                </li>
                            )
                        })}
                    </ul>
                </Disabled>
            </div>
        </>
	);
}

index.php(フロントエンド)

実際に Web ページ側に表示されるブロックコンテンツは、index.php にて WordPress からブロックの attributes のパラメータに基づいた条件で動的にデータを受け取って表示しています。

edit.js のブロックエディター側でのレンダリングと同じ HTML 構造となるように、今度は PHP (index.php) でフロントエンド用のコールバック関数( my_blocks_render_callback_block_three )に、実際に表示されるコンテンツをコーディングします。

index.php
<?php
function my_blocks_render_callback_block_three( $attributes ){

	$wrapper_attributes = get_block_wrapper_attributes();

	$args = array(
		'numberposts'	=> $attributes['numberOfItems'],
	);

	$output = sprintf( '<div %1$s>', $wrapper_attributes );

	$my_posts = get_posts( $args );

	if( ! empty( $my_posts ) ){
		$output .= '<ul class="wp-block-create-block-block-three__post-items">';

		foreach ( $my_posts as $p ){

			$title = $p->post_title ? $p->post_title : __( 'No title', 'my-blocks' );
			$url = esc_url( get_permalink( $p->ID ) );
			$thumbnail = has_post_thumbnail( $p->ID ) ? get_the_post_thumbnail( $p->ID, 'medium' ) : '';

			$output .= '<li>';
			if( ! empty( $thumbnail ) && $attributes['displayThumbnail'] ){
				$output .= '<a href="' . $url . ' class="wp-block-create-block-block-three__post-thumbnail-link">' . $thumbnail . '</a>';
			}
			$output .= '<h3 class="wp-block-create-block-block-three__post-title">';
			$output .= '<a href="' . $url . '">' . $title . '</a>';
			$output .= '</h3>';
			if( $attributes['displayDate'] ){
				$output .= '<time class="wp-block-create-block-block-three__post-date" datetime="' . esc_attr( get_the_date( 'c', $p ) ) . '">' . esc_html( get_the_date( '', $p ) ) . '</time>';
			}
			$output .= '</li>';
		}
		$output .= '</ul>';
	} else {
		$output .= sprintf( '<p>%1$s</p>', __( 'Sorry. No posts matching your criteria!', 'my-blocks' ) );
	}

	$output .= '</div>';

	return $output;
}

エディター(バックエンド)と実際の表示(フロントエンド)の同一性を考慮するために、ダイナミックブロックでは JavaScript と PHP で手作業でわざわざ同じ構成にするコーディングが必要になるのは、現時点ではなかなか面倒ですね。

ServerSideRender を利用する場合

先述の通り、バックエンド(edit.js) とフロントエンド(index.php) の異なる言語間で常に同じ出力構造となるようにコーディングするのは、メンテナンス性を考慮しても結構面倒です。

そんなときは、ServerSideRender コンポーネントを利用すると、フロントエンド(index.php) の出力をエディター側でそのまま表示できるため、開発側としてはかなり楽に実装できます。

つまり、edit.js のブロック表示の以下の部分は、ServerSideRender を利用して以下のように置き換えれます。

edit.jsSeverSideRender なし
<div { ...useBlockProps() }>
    <Disabled>
        <ul className='wp-block-create-block-block-three__post-items'>
            { posts && posts.map(( post ) => {
                return (
                    <li key={ post.id }>
                        {
                            displayThumbnail &&
                            post._embedded &&
                            post._embedded['wp:featuredmedia'] &&
                            post._embedded['wp:featuredmedia'][0] &&
                            <a href={ post.link } className='wp-block-create-block-block-three__post-thumbnail-link'>
                                <img
                                    className='wp-block-create-block-block-three__post-thumbnail'
                                    src={ post._embedded['wp:featuredmedia'][0].media_details.sizes.medium.source_url }
                                    alt={ post._embedded['wp:featuredmedia'][0].alt_text }
                                />
                            </a>
                        }
                        <div className='wp-block-create-block-block-three__text-contents'>
                            <h3 className='wp-block-create-block-block-three__post-title'>
                                <a href={ post.link }>
                                    { post.title.rendered ? (
                                        post.title.rendered
                                    ) : (
                                        __( 'No title', 'my-blocks' )
                                    )}
                                </a>
                            </h3>
                            {
                                displayDate && (
                                    <time
                                        className='wp-block-create-block-block-three__post-date'
                                        dateTime={ format( 'c', post.date_gmt ) }
                                    >
                                        { dateI18n(
                                            __experimentalGetSettings().formats.date,
                                            post.date_gmt
                                        )}
                                    </time>
                                )
                            }
                        </div>
                    </li>
                )
            })}
        </ul>
    </Disabled>
</div>
edit.jsServerSideRender あり
<div { ...useBlockProps() }>
    <Disabled>
        <ServerSideRender
            block='create-block/block-three'
            attributes={ attributes }
        />
    </Disabled>
</div>

表示部分は ServerSideRender だけで済むので、かなりすっきりしますね。

ServerSideRender を利用する場合の edit.js 全体としては、以下のようになります。

edit.js
import { useSelect } from '@wordpress/data';

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

import {
    dateI18n,
    format,
    __experimentalGetSettings
} from '@wordpress/date';

import {
    useBlockProps,
    InspectorControls,
} from '@wordpress/block-editor';

import {
    Disabled,
    PanelBody,
    PanelRow,
    QueryControls,
    RangeControl,
    ToggleControl,
} from '@wordpress/components';

const { serverSideRender: ServerSideRender } = wp;

export default function Edit( { attributes, setAttributes } ) {

    const {
        numberOfItems,
        displayDate,
        displayThumbnail
    } = attributes;

    const posts = useSelect(
        ( select ) => {
            return select( 'core' ).getEntityRecords( 'postType', 'post', {
                'per_page': numberOfItems,
                '_embed': true
            });
        },
        [ numberOfItems ]
    );

    const blockProps = useBlockProps();

    return (
        <>
            <InspectorControls>
                <PanelBody title={ __( 'Content Settings', 'my-blocks' ) }>
                    <PanelRow>
                        <QueryControls
                            numberOfItems={ numberOfItems }
                            onNumberOfItemsChange={ ( value ) =>
                                setAttributes( { numberOfItems: value } )
                            }
                            minItems={ 1 }
                            maxItems={ 10 }
                        />
                    </PanelRow>
                    <PanelRow>
                        <ToggleControl
                            label={ __( 'Show Featured Image', 'my-blocks' ) }
                            checked={ displayThumbnail }
                            onChange={ () =>
                                setAttributes( { displayThumbnail: ! displayThumbnail } )
                            }
                        />
                    </PanelRow>
                    <PanelRow>
                        <ToggleControl
                            label={ __( 'Show Date', 'my-blocks' ) }
                            checked={ displayDate }
                            onChange={ () =>
                                setAttributes( { displayDate: ! displayDate } )
                            }
                        />
                    </PanelRow>
                </PanelBody>
            </InspectorControls>

            <div { ...useBlockProps() }>
                <Disabled>
                    <ServerSideRender
                        block='create-block/block-three'
                        attributes={ attributes }
                    />
                </Disabled>
            </div>
        </>
	);
}

ただし、ServerSideRender を利用する方法は、公式的には推奨されていないようです。

サーバー側レンダーはフォールバックです。常に JavaScript によるクライアントサイドレンダリングが好まれます。クライアントレンダリングは速く、エディターの操作性が高くなります).

ブロックエディター内でのライブレンダリング

スタイリング

せっかくなので投稿一覧を整形する CSS も適当にコーディングしておきます。
「block-three」ディレクトリの style.scss を以下に入れ替えます。

style.scss
.wp-block-create-block-block-three {
	font-family: system-ui;
}
.wp-block-create-block-block-three__post-items{
	display:flex;
	gap:20px;
	flex-wrap: wrap;
	margin:0;

	li{
		display:flex;
		flex-direction: column;
		gap: 10px;
		flex-basis: calc(33.33% - 40px / 3 );
	}
}
.wp-block-create-block-block-three__post-thumbnail{
	width:100%;
	height:auto;
}
.wp-block-create-block-block-three__post-title{
	font-size: 1.12rem;
	font-weight: 600;
	margin:0 0 0.2em;
}
.wp-block-create-block-block-three__post-date{
	font-size: 0.88rem;
}

style.scss は公開側(フロントエンド)とエディター側(バックエンド)に反映される共通のスタイルです。
編集上の便宜でエディター側だけに反映させたい場合は、editor.scss に必要なスタイリングをしておきます。

ビルドして確認

ここまで終わったら、カレントが /my-blocks (プラグインのルートディレクトリ) となっている状態で、改めてビルドしてみます。

npm run build

エディターに戻って「Block Three」ブロックを追加してみます。

エディター側

ちゃんと最新記事の一覧が表示されています。
続いてフロントエンド(index.php)の表示もチェックしてみます。

フロントエンド

エディターと同じ表示になりました!

より詳細なダイナミックブロックの作り方は、以下のドキュメントがとても参考になります。

Share / Subscribe
Facebook Likes
Tweets
Hatena Bookmarks
Pinterest
Pocket
Feedly
Send to LINE