JINMUSOFTWARE

WordPressはじめてのブロック開発

Tutorial

本ページはWordPressの公式チュートリアルに基づいてブロックの開発手順を紹介します。

「チュートリアル: はじめてのブロック作成」とは?

WordPress公式のチュートリアルのことです。

ブロック開発のステップを一通り学ぶことができます。

でも、ちょっと長くて難しいです。

チュートリアルの作成物

開発するブロックの要件は以下になります。

  • 著作マークを表示する。
  • 現在の西暦を表示する。
  • オプションで開始年を設定できる。

よくページのフッターに表記されていることが多いですね。

copyright-date-block
copyright-date-block

チュートリアルでは、プラグインとしてブロックを開発します。

ちなみに、当サイトjinmusoftware.comのフッターはPHPテンプレートで実現しています。テーマとして開発されています。

このチュートリアルを始める前に「クイックスタートガイド」の解説も参考にしてください。完成済みのブロックを確認できます。

WordPressブロック開発環境の準備

  • Windows11 24H2
  • npm 11.3.0
  • wp-env 10.25.0
  • docker
  • vscode
  • git

npm: Node.jsをインストールしてください。npmが付属されています。

wp-env: npmコマンドでインストールできます。これはWordPressの実行環境です。

npm -g i @wordpress/env

空プロジェクトを作成する

まずは空のプロジェクトを作成してみましょう。

空といってもブロックの下地が準備されます。

WordPress実行環境で初期ブロックの動作確認もしてみましょう。

プロジェクト作成

チュートリアルでは次のコマンドを実行してプロジェクト作成しています。

npx @wordpress/create-block@latest copyright-date-block --variant=dynamic

プロジェクトディレクトリが作成され、その中にファイルが作成されます。

次の補足は読み飛ばしても大丈夫です。

補足1:オプション「–variant=dynamic」について

コマンドには「–variant=dynamic」というオプションが付いていますね。

公式チュートリアルでは以下に様に説明されています。引用します。

コマンドに –variant=dynamic フラグが指定されていることにお気づきかもしれません。これは create-block に対して動的にレンダーされるブロックのひな形を作成するよう伝えます。このチュートリアルの後半では、動的レンダリングと静的レンダリングについて学び、静的レンダリングをこのブロックに追加します。

初心者の私にはさっぱりわかりませんでした。

適当に要約すると、ブロックの種類として、動的生成ブロックと静的生成ブロックの2つがあります。

dynamicオプションを指定すると動的レンダリングをサポートするブロックの開発に合わせたファイルが用意されます。具体的にはrender.phpが生成され、block.jsonにrender.phpが定義されます。

というわけで、このチュートリアルは動的レンダリングをサポートするブロックを作成してきます。

補足2:「.wp-env.json」ファイルは準備されません

後で気づくことになるのですが、WordPress実行環境である「wp-env」を起動すると警告が発生します。その説明になります。

create-blockコマンドを実行すると、プロジェクトディレクトリ(プラグインディレクトリ)が作成され、各種ファイルがスキャフォールドされます。

このとき、「.wp-env.json」というファイルは準備されません。以前はあったようです。今後どうなるかわかりません。

さて、無いとどうなるのでしょうか?

この「.wp-env.json」ファイルが無い状態で、「wp-env(WordPress実行環境)」を立ち上げると警告が発生します。ただし、エラーでは無いのでWordPressは実行できます。

気になるようであれば自分でファイルを作成して準備します。もしくは、create-blockコマンドの「–wp-env」オプションを使用して準備することも可能です。

npx @wordpress/create-block@latest copyright-date-block --variant=dynamic --wp-env

カレントディレクトリ移動

カレントディレクトリを先ほど作成したプロジェクトディレクトリに移動します。

紹介したコマンドでプロジェクトを作成した場合は「copyright-date-block」という名前のディレクトリが作成されているはずです。移動しましょう。

cd copyright-date-block

docker起動

wp-envの起動に必要です。立ち上げておきましょう。結構忘れる作業です。

WordPressの起動

カレントディレクトリを移動しましたね?

では、次のコマンドでwp-envを起動してください。

wp-env start
ターミナル出力
> wp-env start

‼ Warning: could not find a .wp-env.json configuration file and could not determine if 'C:\htdocs\wordpress\try1\copyright-date-block' is a WordPress installation, a plugin, or a theme.
WordPress development site started at http://localhost:8888
WordPress test site started at http://localhost:8889
MySQL is listening on port 52037
MySQL for automated testing is listening on port 52339

 √ Done! (in 169s 859ms)

停止するときは以下のコマンドです。

wp-env stop

以下URLにアクセスしてログインしてください。

http://localhost:8888/wp-admin

  • username: admin
  • password: password

初期Blockの確認

WordPressは起動できましたか?

新規プロジェクトにはシンプルなブロックがプラグインとして生成されています。

確認してみましょう。プラグインにあります。

Plugin list
Plugin list

すでに有効化されていますね。

このブロックを使ってみましょう。

ブロックエディタを開いてブロックを追加してみましょう。Widgetsのカテゴリに入っています。

In widget
In widget

これがブロックの初期デザインです。

Copyright Data Block
Copyright Data Block

サイドバーには、スマイルなアイコンと説明文がありますね。

エディタ上では悪目立ちするスタイルと適当な文字列が表示されていますね。

まとめ

  • npmコマンドで開発環境をスキャフォールドできる。
  • ブロックはプラグインとして開発する。
  • WordPress実行環境は何でも良い。今回はwp-envを採用。
  • ブロックは動的レンダリングと静的レンダリングがある。

プロジェクトを作成して初期デザインのブロックの動作確認をしました。

お疲れ様でした。

静的レンダリングと動的レンダリング

スキャフォールドされたファイルを確認してみましょう。

srcディレクトリには概ね以下のようなファイルが生成されています。

  • block.json – ブロックの情報。タイトル、カテゴリとか。
  • edit.js – ブロックエディタで表示されるブロックの定義と属性の定義。
  • editor.scss – ブロックエディタで表示されるブロックのスタイル。
  • index.js – エントリーポイント。依存関係を読み込んでブロックの登録処理。
  • render.php – 動的レンダリング用
  • save.js – 静的レンダリング用
  • style.scss – フロントエンド用CSS
  • view.js – フロントエンド用JavaScript

レンダリングに関するファイルを確認します。

edit.js

edit.jsはブロックエディタで表示されるブロックを定義します。

サイドバーに表示されるブロックの属性も定義します。

render.php

動的レンダリング用。

毎回HTMLを生成してクライアントに表示します。

save.js

静的レンダリング用。

ブロックエディタで編集保存時にHTMLを生成してWordPressのデーターベースに保存します。クライアントには保存されたHTMLを表示します。

先ほどのプロジェクトは、動的レンダリング用にプロジェクトを作成したのでこのファイルは作成されていません。

レンダリング方法の選択

動的レンダリングにするか、静的レンダリングにするかは要件しだいです。大抵どちらかを選択するようです。

このチュートリアルでは、先に動的レンダリングを実装し、バックアップとして静的レンダリングを実装しています。

まとめ

ブロックの開発において、ブロックの表示を担当するファイルは3つ存在するようです。

  • edit.js …投稿画面用(編集画面)
  • render.php …動的レンダリングでユーザに表示される
  • save.js …静的レンダリングでユーザに表示される

☕☕

ブロックのつくりこみ

さて、ブロック開発を進めていきましょう。以下の様なメニューになっています。

  • ビルド準備
  • ブロック情報修正
  • 不要スタイル削除
  • ブロックサポート追加
  • カスタムアイコンの追加
  • HTML作成

ビルド準備

これからはファイルを修正するのでビルドする必要があります。

次のコマンドを実行しておきましょう。

ファイルを監視し変更を検知すればビルドします。

npm run start

Ctrl+Cで停止します。

本番環境用のビルドは次のコマンドです。

npm run build

ブロック開発者が修正すべきファイルはsrcディレクトリに格納されています。

srcディレクトリ内のファイルはビルドされてbuildディレクトリに保存されます。

ブロック情報修正

ブロックの情報は、「block.json」ファイルで定義されています。

デフォルトのアイコンを削除し、説明文を修正します。

  • 「”icon” : “smiley”」の行を削除
  • 「”description”: “~~~”」の説明文を変更。例えば、”著作権マークと年の表示”

ビルド確認

「npm run start」を実行していますので、ファイルを編集して保存すると自動でビルドされます。

ビルド結果はbuildディレクトリに保存されます。ファイルが更新さていることに気づくでしょう。

不要スタイル削除

このブロックはとてもシンプルなためにスタイルもほとんど必要ありません。

block.jsonファイルから以下のキーを削除します。

  • editorStyleを削除
  • styleを削除
  • viewScriptを削除

不要なimport文と不要なスタイル用のファイルを削除しましょう。

  • edit.jsの”import ‘./editor.scss’;”文を削除
  • index.jsの”import ‘./style.scss’;”文を削除
  • editor.scssファイルの削除
  • style.scssファイルの削除
  • view.jsファイルの削除

ブロックサポートの追加

現在のブロックの属性は初期状態です。かわいいスマイルマークとデフォルトの説明文のみです。

ブロックサポートを有効にしてWordPressの強力なサポートを受けましょう。

段落ブロックの様にテキストの色やサイズ変更など基礎的な機能が簡単に追加することが可能です。

では、block.jsonに以下のコードを参考に編集してください。

“supports”に”color”と”typography”を追加します。

block.json
"supports": {
  "html": false,
  "color": {
    "background": false,
    "text": true
  },
  "typography": {
    "fontSize": true
  }
},
JSON5

これで、テキストの色、フォントサイズの変更が有効になりました。

確認

CSSを削除しましたのでブロックのスタイルが消えました。

CSSを削除した状態
CSSを削除した状態

サイド―バーにブロックの属性が表示されています。

WordPressのサポートを受けて、文字色、文字サイズを調整できるようになりました。

アイコンも削除したので、「レゴ?」のようなアイコンになりました。

WordPressのブロックサポート受けたUI
WordPressのブロックサポート受けたUI

これら属性は変更し保存することが可能です。

カスタムアイコンの設定

先ほどデフォルトのアイコンを削除しました。

今度はカスタムアイコンを追加してみましょう。

index.jsファイルにあるregisterBlockType関数にカスタムアイコンを設定します。

ブロックのアイコンサイズは24×24と決まっているようです。

index.js
// まず変数calendarIconにhtmlを保存します。

const calendarIcon = (
  <svg
    viewBox="0 0 24 24"
    xmlns="http://www.w3.org/2000/svg"
    aria-hidden="true"
    focusable="false"
  >
    <path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm.5 16c0 .3-.2.5-.5.5H5c-.3 0-.5-.2-.5-.5V7h15v12zM9 10H7v2h2v-2zm0 4H7v2h2v-2zm4-4h-2v2h2v-2zm4 0h-2v2h2v-2zm-4 4h-2v2h2v-2zm4 0h-2v2h2v-2z"></path>
  </svg>
);

registerBlockTypeに設定します。「icon: calendarIcon,」と、追加します。

index.js
registerBlockType(metadata.name, {
  icon: calendarIcon, // add
  edit: Edit,
});

ブロックのアイコンがカレンダーに変わります。

Calendar Icon
Calendar Icon

HTML作成

出力するHTMLを作成します。ブロックの見た目の構造になります。

ブロックエディタ用とクライアント用のHTMLを作成する必要があります。

どちらも同じような出力結果になるようにします。

我々が開発しているこのブロックの目的は、著作権マークの表示と西暦の表示でしたね。

edit.js

まずはエディタ用のブロックのデザインを作成します。

edit.jsを開き、Edit()関数を修正します。

次のコードを追加します。

edit.js
const currentYear = new Date().getFullYear().toString();

return (
  <p { ...useBlockProps() }>(c) { currentYear }</p>
);

これでエディタ上では、Cマークと現在の年が表示されます。

しかし、クライアント側からブラウザで確認すると初期ブロックのままです。こちらのURLから確認できますね。

http://localhost:8888/

render.php

クライアントに見せるHTMLも同じように修正する必要があります。

このブロックは動的レンダリングを採用していますので、render.phpを修正します。

PHPです。頑張りましょう!

render.php
<?php
// ~~~
?>
<p <?php echo get_block_wrapper_attributes(); ?>>(c) <?php echo date( "Y" ); ?></p>

ブラウザで確認してみましょう。

エディタ側とクライアント側で同じ表示であることを確認します。

こちらはブラウザからのキャプチャ画面です。

動的レンダリングされたHTML
動的レンダリングされたHTML

2行目はテキスト色とサイズを変更してみました。バッチリですね!

Code

code
files
src/copyright-date-block
  block.json
  edit.js
  index.js
  render.php
block.json
{
	"$schema": "https://schemas.wp.org/trunk/block.json",
	"apiVersion": 3,
	"name": "create-block/copyright-date-block",
	"version": "0.1.0",
	"title": "Copyright Date Block",
	"category": "widgets",

	"description": "著作権マークと年の表示",
	"example": {},
	"supports": {
		"html": false,
		"color": {
			"background": false,
			"text": true
		},
		"typography": {
			"fontSize": true
		}
	},
	"textdomain": "copyright-date-block",

	"editorScript": "file:./index.js",
	"render": "file:./render.php"
}
edit.js
import { __ } from '@wordpress/i18n';

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

export default function Edit() {
  const currentYear = new Date().getFullYear().toString();

  return (
    <p {...useBlockProps()}>(c) {currentYear}</p>
  );
}
index.js
import { registerBlockType } from '@wordpress/blocks';

import Edit from './edit';
import metadata from './block.json';

// まず変数calendarIconにhtmlを保存します。
const calendarIcon = (
  <svg
    viewBox="0 0 24 24"
    xmlns="http://www.w3.org/2000/svg"
    aria-hidden="true"
    focusable="false"
  >
    <path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm.5 16c0 .3-.2.5-.5.5H5c-.3 0-.5-.2-.5-.5V7h15v12zM9 10H7v2h2v-2zm0 4H7v2h2v-2zm4-4h-2v2h2v-2zm4 0h-2v2h2v-2zm-4 4h-2v2h2v-2zm4 0h-2v2h2v-2z"></path>
  </svg>
);

registerBlockType(metadata.name, {
  icon: calendarIcon,
  edit: Edit,
});
render.php
<?php
//
?>
<p <?php echo get_block_wrapper_attributes(); ?>>(c) <?php echo date( "Y" ); ?></p>

☕☕☕

属性の追加、開始年の追加

ブロックは属性を持つことができます。

ブロックエディタでブロックを選択すると右側のサイドバーにブロックの属性が表示されます。

今回はその属性を追加していきましょう。

ここからも難しいです。がんばりましょう!


現状のブロックは著作権マークと現在の年を表示します。

開始年を追加できるようにしてみましょう。

開始年はブロックの属性として入力できるようにします。

以下メニューです。

  • 使用する属性の宣言
  • ブロック属性用UIの作成
  • edit用ブロックの修正
  • render.phpの修正

使用する属性の宣言

使用する属性はblock.jsonで定義します。

属性名と型を定義します。

  • showStartingYear – boolean – 開始年を表示するかどうか
  • startingYear – string – 開始年

block.json:

block.json
"attributes": {
  "showStartingYear": {
    "type": "boolean"
  },
  "startingYear": {
    "type": "string"
  }
},

これら属性値はWordPressのデーターベースに保存されます。

ブロック属性用UIの作成

サイドバーにはブロックの属性が表示されます。こちらのUIを作りこんでいきましょう。

edit.jsのEdit()関数で「InspectorControls」タグを使用します。

  • InspectorControls

InspectorControlsタグ内に各コンポーネントを定義してUIを構築していきます。

今回は次のコンポーネントを使用します。

  • PanelBody
  • TextControl
  • ToggleControl

属性値の取得と更新の宣言です。

  • Edit()関数に引数を追加 : { attributes, setAttributes }
  • 変数宣言:const { showStartingYear, startingYear } = attributes;
edit.js
// ~~~

import { InspectorControls, useBlockProps } from '@wordpress/block-editor';
import { PanelBody, TextControl, ToggleControl } from '@wordpress/components';

// ~~~

export default function Edit({ attributes, setAttributes }) {
  const { showStartingYear, startingYear } = attributes;
  const currentYear = new Date().getFullYear().toString();

  return (
    <>
      <InspectorControls>
        <PanelBody title={__('Settings', 'copyright-date-block')}>

          <ToggleControl
            checked={!!showStartingYear}
            label={__('Show starting year','copyright-date-block')}
            onChange={() =>
              setAttributes({
                showStartingYear: !showStartingYear,
              })
            }
          />

          {showStartingYear && (
            <TextControl
              __nextHasNoMarginBottom
              __next40pxDefaultSize
              label={__('Starting year','copyright-date-block')}
              value={startingYear || ''}
              onChange={(value) =>
                setAttributes({ startingYear: value })
              }
            />
          )}
        </PanelBody>
      </InspectorControls>

      <p {...useBlockProps()}>(c) {currentYear}</p>
    </>
  );
}

この時点で属性値が保存されることを確認できます。属性値を適当に設定して開きなおしてみましょう。

edit用ブロックの修正

次に属性値をブロックに反映させます。

表示する文字列を作成します。

edit.js
let displayDate;

if ( showStartingYear && startingYear ) {
  displayDate = startingYear + '–' + currentYear;
} else {
  displayDate = currentYear;
}

表示する変数を変更します。

edit.js
<p { ...useBlockProps() }>(c) { displayDate }</p>

render.php修正

エディター側は完成しました。フロントエンド側も修正しましょう。

render.php
<?php
$current_year = date( "Y" );
 
if ( ! empty( $attributes['startingYear'] ) && ! empty( $attributes['showStartingYear'] ) ) {
  $display_date = $attributes['startingYear'] . '–' . $current_year;
} else {
  $display_date = $current_year;
}
?>
<p <?php echo get_block_wrapper_attributes(); ?>>
  (c) <?php echo esc_html( $display_date ); ?>
</p>

ブラウザで確認しましょう。ビルドはしていますか?

開始年も表示されました。

render.phpの結果
render.phpの結果

ブロック属性UIには、トグルSWとテキストを実装することができました。

ブロック属性のUI
ブロック属性のUI

動的ブロックの実装が完了しました。

お疲れさまでした。

☕☕☕☕

静的レンダリング追加

静的レンダリングは、「save.js」が担当します。

静的レンダリングでは、ブロックエディタで編集し保存をするとsave()関数が出力するHTMLがWordPressのデータベースに保存されます。

クライアント側には、この保存されたHTMLを表示させます。

このチュートリアルではすでにrender.phpで動的レンダリングのサポートを実装しました。

動的レンダリングに静的レンダリングを追加する意味は、対象のプラグイン削除された場合でも静的レンダリングによってフロントエンドに表示し続けることが可能になります。

以下メニューです

  • save関数の追加
  • Validationエラー
  • save.jsの修正
  • 動作確認(静的レンダリング)
  • プラグインを無効化してみる

save関数の追加

src/copyright-date-blockディレクトリに”save.js”を新規作成します。

動作確認の為に適当なHTMLを定義します。

save.js:

save.js
// save.js

import { useBlockProps } from '@wordpress/block-editor';
 
export default function save() {
  return (
    <p { ...useBlockProps.save() }>
      { 'Copyright Date Block – hello from the saved content!' }
    </p>
  );
}

ファイルを新規作成しました。

index.jsで登録する必要があります。import文も忘れずに追加してくださいね。

index.js
// ~~~

import save from './save'; // add
 
// ~~~

registerBlockType( metadata.name, {
  icon: calendarIcon,
  edit: Edit,
  save, // add
});

Validationエラー

エディタでブロックを確認すると、ブロックにエラーが出ています。

Validation Error
Validation Error

とりあえずRecoveryを押してエラーを解消しましょう。ページの保存もお忘れなく。

なぜエラーが発生したのでしょうか?

それは、データベースに保存されているHTMLとsave.jsが生成したHTMLに差異があることが原因です。

ブロックのロード時にチェックされます。

save()関数を編集して出力されるHTMLが更新され、編集ページを開く度このエラーが発生します。

save.jsの修正

では、save()関数が出力するHTMLを正しいものに修正していきましょう。

render.php、edit.jsと同じ結果になるようにするのです。

save.js
import { useBlockProps } from '@wordpress/block-editor';

export default function save( { attributes } ) {
  const { showStartingYear, startingYear } = attributes;
  const currentYear = new Date().getFullYear().toString();

  let displayDate;

  if ( showStartingYear && startingYear ) {
    displayDate = startingYear + '–' + currentYear;
  } else {
    displayDate = currentYear;
  }

  return (
    <p { ...useBlockProps.save() }>© { displayDate }</p>
  );
}

save.jsを編集し、ブロックをロードする度、Validationエラーが出ますけど解除して進んでいきましょう!

出力結果を確認したいですね。

動作確認(静的レンダリング)

静的レンダリングの動作確認をしてみましょう。

現在、動的レンダリングを採用していますので、クライアント側にはrender.phpの結果が表示されいます。

block.jsonにある、「”render”: “file:./render.php”,」行を削除して、動的レンダリングのサポートを解除します。

jsonファイルなのでコメントアウトはできませんね。削除しましょう。

動作結果の比較を簡単にするため、出力するHTMLも少し修正します。

block.json
"render": "file:./render.php", <-- 削除
save.js
// (save) と追加
return (
    <p {...useBlockProps.save()}>(c) {displayDate}(save)</p>
);
render.php
// 変数「display_date」作成の為のif文の後に追加
$display_date .= '(render)';

結果次のようになります。

静的レンダリングの結果
静的レンダリングの結果

静的レンダリングが問題なく動作していることを確認できました。

プラグインを無効化してみる

プラグインを無効化するとどうなるのでしょうか?

まずは、動的レンダリングを戻します。block.jsonに”render”キーを戻してください。

プラグインを停止します。

Edit画面は以下のようになりました。

プラグインを停止した場合
プラグインを停止した場合

クライアント側

静的レンダリングの結果
静的レンダリングの結果

プラグインを無効化した場合、静的レンダリングによる保存されたHTMLが表示され続けるようです。

バックアップの意味はあるようですね。

エディター上では問題があることを示してくれます。「keep as HTML」を押すとカスタムHTMLブロックとして保存されます。

静的レンダリングが無い場合でプラグインを無効化した場合は?

index.jsのregisterBlockType()関数のメソッドの引数より”save”を削除してみます。

静的レンダリングされたHTMLが保存されていない場合はクライアントに何も表示されません。

静的レンダリングされたHTMLが保存されている場合はクライアントに表示されます。このチュートリアルの流れで動作確認している場合は静的レンダリングが残存していると思われますので、一度ブロック削除してから確認してみてください。

code
save.js
import { useBlockProps } from '@wordpress/block-editor';

export default function save({ attributes }) {
    const { showStartingYear, startingYear } = attributes;
    const currentYear = new Date().getFullYear().toString();

    let displayDate;

    if (showStartingYear && startingYear) {
        displayDate = startingYear + '–' + currentYear;
    } else {
        displayDate = currentYear;
    }

    return (
        <p {...useBlockProps.save()}>(c) {displayDate}(save)</p>
    );
}
index.js
import { registerBlockType } from '@wordpress/blocks';

import Edit from './edit';
import metadata from './block.json';
import save from './save'; // add

// まず変数calendarIconにhtmlを保存します。
const calendarIcon = (
  <svg
    viewBox="0 0 24 24"
    xmlns="http://www.w3.org/2000/svg"
    aria-hidden="true"
    focusable="false"
  >
    <path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm.5 16c0 .3-.2.5-.5.5H5c-.3 0-.5-.2-.5-.5V7h15v12zM9 10H7v2h2v-2zm0 4H7v2h2v-2zm4-4h-2v2h2v-2zm4 0h-2v2h2v-2zm-4 4h-2v2h2v-2zm4 0h-2v2h2v-2z"></path>
  </svg>
);

registerBlockType(metadata.name, {
  icon: calendarIcon,
  edit: Edit,
  save, // add
});
render.php
<?php
$current_year = date( "Y" );
 
if ( ! empty( $attributes['startingYear'] ) && ! empty( $attributes['showStartingYear'] ) ) {
  $display_date = $attributes['startingYear'] . '-' . $current_year;
} else {
  $display_date = $current_year;
}

$display_date .= '(render)'; // add

?>
<p <?php echo get_block_wrapper_attributes(); ?>>
  (c) <?php echo esc_html( $display_date ); ?>
</p>

memo: HTML用文字列の末尾に「(save)」「(render)」の処理がありますが適当に削除してください。

☕☕☕☕☕

年またぎバグの対応

年をまたいで編集画面を開くとValidationエラーが発生します。

save()関数には、西暦年に依存した副作用があるので、年が変わると、save()関数の出力が変わります。

静的レンダリングとして保存されているHTMLとsave()関数が出力するHTMLに差異が見つかるとValidationエラーが発生します。

save()関数の中では動的な値を使用することをやめて、引数を基にHTMLを構築する方針を取ります。

新しい属性値の追加

年を保存する属性値が必要になります。追加しましょう。

  • fallbackCurrentYear – string – ブロック追加、編集したときの年を保存する

fallbackCurrentYearを追加します。

block.json
"attributes": {
  "fallbackCurrentYear": {
    "type": "string"
  },
  "showStartingYear": {
    "type": "boolean"
  },
  "startingYear": {
    "type": "string"
  }
},

save.jsの修正

動的に年を取得することを止めます。

save.js
// const currentYear = new Date().getFullYear().toString(); // 廃止

年情報は、属性値”fallbackCurrentYear”から取得します。

”fallbackCurrentYear”の初期化はedit.jsに任せます。

save.js
export default function save( { attributes } ) {

  const { fallbackCurrentYear, showStartingYear, startingYear } = attributes;
  // const currentYear = new Date().getFullYear().toString(); // 廃止

  // 入力値チェック。値がまだない場合はnullを返す
  if ( ! fallbackCurrentYear ) {
    return null;
  }

  let displayDate;
 
  if ( showStartingYear && startingYear ) {
    displayDate = startingYear + '–' + fallbackCurrentYear;
  } else {
    displayDate = fallbackCurrentYear;
  }

  return (
    <p { ...useBlockProps.save() }>(c) { displayDate }</p>
  );
}

edit.jsの修正

edit.jsでは、属性fallbackCurrentYearの初期化を行います。

現在の年を属性値”fallbackCurrentYear”に保存します。

edit.js
import { useEffect } from 'react';

// ~~~

export default function Edit( { attributes, setAttributes } ) {
  const { fallbackCurrentYear, showStartingYear, startingYear } = attributes;

  // 現在年の取得
  const currentYear = new Date().getFullYear().toString();

  // fallbackCurrentYear に現在年を設定する
  useEffect( () => {
    if ( currentYear !== fallbackCurrentYear ) {
      setAttributes( { fallbackCurrentYear: currentYear } );
    }
  }, [ currentYear, fallbackCurrentYear, setAttributes ] );

  let displayDate;

  // 開始年の追加
  if (showStartingYear && startingYear) {
    displayDate = startingYear + '–' + currentYear;
  } else {
    displayDate = currentYear;
  }

  return ( ~~~ );
}

静的レンダリングと動的レンダリングの共存

静的レンダリングと動的レンダリングの共存方法を考えます。

静的レンダリングと動的レンダリングの結果が同じであれば、わざわざページを動的に生成する必要はありません。

レンダリング結果が同じ場合は、保存さているHTMLを表示すればいいのです。

render.phpでは、保存されているHTMLを取得して表示することが可能です。

比較結果に差異が生じた場合は動的レンダリングとします。

確認すべきは、保存されている属性”fallbackCurrentYear”と現在年です。

それでは、「render.php」を最適化していきましょう。

render.php
<?php

// 現在の年を取得
$current_year = date( "Y" );

// 保存された年を安全に取得し現在年と比較
if ( isset( $attributes['fallbackCurrentYear'] ) && $attributes['fallbackCurrentYear'] === $current_year ) {
  // 年に差異は無いので保存ページをそのまま表示
  $block_content = $content;

} else {
  // 開始年を含めるか判断して文字列を作成
  if ( ! empty( $attributes['startingYear'] ) && ! empty( $attributes['showStartingYear'] ) ) {
    $display_date = $attributes['startingYear'] . '-' . $current_year;
  } else {
    $display_date = $current_year;
  }
  
  $display_date .= '(render)';

  $block_content = '<p ' . get_block_wrapper_attributes() . '>(c) ' . esc_html( $display_date ) . '</p>';
}

echo wp_kses_post( $block_content );

完了です。

結果、レンダリングが切り替わっているように見えます。

が、しかし、render.phpは常に動作しています。

現在の年とfallbackCurrentYearの比較結果が同じ場合、動的生成を行わず、静的レンダリングされたHTMLを取得し表示しているです。

確認

年またぎの確認したいですね。年が変わるまでは待てそうにもありません。

10分単位にしてみましょう。

WordPressの設定でタイムゾーンを「東京」に設定します。

edit.js
  const now = new Date();
  const year = now.getFullYear();
  const month = now.getMonth() + 1;
  const day = now.getDate();
  const hours = now.getHours();
  const minutes10 = Math.floor(now.getMinutes() / 10) * 10; // 10分単位に切り捨て
  const minutes2digit = ("10" + minutes10).slice(-2); // 2桁にする
  const datetime = year + '-' + month + '-' + day + ' ' + hours + ':' + minutes2digit;
  const currentYear = datetime;

  // const currentYear = new Date().getFullYear().toString();
render.php
$timezone = wp_timezone();
$now = new DateTime('now', $timezone);
$minutes = (int)$now->format('i');
$rounded_minutes = floor($minutes / 10) * 10; // 10分単位に切り捨て
$rounded_minutes_2digit = sprintf('%02d', $rounded_minutes); // 2桁にフォーマット
$current_year = $now->format('Y-n-j G:') . $rounded_minutes_2digit;

edit画面です。分まで表示可能になりました。

edit画面
edit画面

すぐにクライアント側を確認してみると、静的レンダリングで保存されたHTMLが表示されています。

クライアント画面
クライアント画面

10分後…動的レンダリングの文字列に変わりました。

クライアント画面(年越しと想定)
クライアント画面(年越した場合)

10分経過すると動的レンダリングの表示に切り替わりました。

また10分後にエディターを開いてもValidationエラーは発生しません。問題はなさそうです。

しかし、保存ボタンがアクティブになっていることに気づきます。

日付は更新されています。画面の変更が検知されて更新を促されているのです。

WordPressの開発も楽しそうですね!

以上です。

Code

code
block.json
{
	"$schema": "https://schemas.wp.org/trunk/block.json",
	"apiVersion": 3,
	"name": "create-block/copyright-date-block",
	"version": "0.1.0",
	"title": "Copyright Date Block",
	"category": "widgets",
	"description": "著作権マークと年の表示",
	"example": {},
	"supports": {
		"html": false,
		"color": {
			"background": false,
			"text": true
		},
		"typography": {
			"fontSize": true
		}
	},
	"textdomain": "copyright-date-block",
	"editorScript": "file:./index.js",
	"render": "file:./render.php",
	"attributes": {
		"fallbackCurrentYear": {
			"type": "string"
		},
		"showStartingYear": {
			"type": "boolean"
		},
		"startingYear": {
			"type": "string"
		}
	}
}
edit.js
import { __ } from '@wordpress/i18n';
import { InspectorControls, useBlockProps } from '@wordpress/block-editor';
import { PanelBody, TextControl, ToggleControl } from '@wordpress/components';
import { useEffect } from 'react';

export default function Edit({ attributes, setAttributes }) {
  const { fallbackCurrentYear, showStartingYear, startingYear } = attributes;

  // 現在の日付日時を取得
  const now = new Date();
  const year = now.getFullYear();
  const month = now.getMonth() + 1;
  const day = now.getDate();
  const hours = now.getHours();
  // const minutes = now.getMinutes();
  // const minutes30 = now.getMinutes() < 30 ? '00' : '30';
  const minutes10 = Math.floor(now.getMinutes() / 10) * 10; // 10分単位に切り捨て
  const minutes2digit = ("10" + minutes10).slice(-2); // 2桁にする
  const datetime = year + '-' + month + '-' + day + ' ' + hours + ':' + minutes2digit;

  // 現在年の取得(廃止)
  // const currentYear = new Date().getFullYear().toString();
  const currentYear = datetime;

  // fallbackCurrentYear に現在年を設定する
  useEffect(() => {
    if (currentYear !== fallbackCurrentYear) {
      setAttributes({ fallbackCurrentYear: currentYear });
    }
  }, [currentYear, fallbackCurrentYear, setAttributes]);

  let displayDate;

  // 開始年の追加
  if (showStartingYear && startingYear) {
    displayDate = startingYear + '-' + currentYear;
  } else {
    displayDate = currentYear;
  }

  return (
    <>
      <InspectorControls>
        <PanelBody title={__('Settings', 'copyright-date-block')}>

          <ToggleControl
            checked={!!showStartingYear}
            label={__('Show starting year', 'copyright-date-block')}
            onChange={() =>
              setAttributes({
                showStartingYear: !showStartingYear,
              })
            }
          />

          {showStartingYear && (
            <TextControl
              __nextHasNoMarginBottom
              __next40pxDefaultSize
              label={__('Starting year', 'copyright-date-block')}
              value={startingYear || ''}
              onChange={(value) =>
                setAttributes({ startingYear: value })
              }
            />
          )}
        </PanelBody>
      </InspectorControls>

      <p {...useBlockProps()}>(c) {displayDate}</p>
    </>
  );
}
index.js
import { registerBlockType } from '@wordpress/blocks';

import Edit from './edit';
import metadata from './block.json';
import save from './save';

// まず変数calendarIconにhtmlを保存します。
const calendarIcon = (
  <svg
    viewBox="0 0 24 24"
    xmlns="http://www.w3.org/2000/svg"
    aria-hidden="true"
    focusable="false"
  >
    <path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm.5 16c0 .3-.2.5-.5.5H5c-.3 0-.5-.2-.5-.5V7h15v12zM9 10H7v2h2v-2zm0 4H7v2h2v-2zm4-4h-2v2h2v-2zm4 0h-2v2h2v-2zm-4 4h-2v2h2v-2zm4 0h-2v2h2v-2z"></path>
  </svg>
);

registerBlockType(metadata.name, {
  icon: calendarIcon,
  edit: Edit,
  save,
});
render.php
<?php

$timezone = wp_timezone();
$now = new DateTime('now', $timezone);
$minutes = (int)$now->format('i');
// $rounded_minutes = ($minutes < 30) ? '00' : '30';
$rounded_minutes = floor($minutes / 10) * 10; // 10分単位に切り捨て
$rounded_minutes_2digit = sprintf('%02d', $rounded_minutes); // 2桁にフォーマット

// 切り捨てた分をセット
// こちらの手法も面白いかも(未検証)
// $now->setTime((int)$now->format('H'), $roundedMinutes);
// echo $now->format('Y-n-j H:i'); // 例: 2025-6-21 21:20

$current_year = $now->format('Y-n-j G:') . $rounded_minutes_2digit;

// 現在の年を取得
// $current_year = date( "Y" );

// 保存された年を安全に取得し現在年と比較
if ( isset( $attributes['fallbackCurrentYear'] ) && $attributes['fallbackCurrentYear'] === $current_year ) {
  // 年に差異は無いので保存ページをそのまま表示
  $block_content = $content;

} else {
  // 開始年を含めるか判断して文字列を作成
  if ( ! empty( $attributes['startingYear'] ) && ! empty( $attributes['showStartingYear'] ) ) {
    $display_date = $attributes['startingYear'] . '-' . $current_year;
  } else {
    $display_date = $current_year;
  }

  // DEBUG
  $display_date .= '(render)';

  $block_content = '<p ' . get_block_wrapper_attributes() . '>(c) ' . esc_html( $display_date ) . '</p>';
}

echo wp_kses_post( $block_content );
save.js
import { useBlockProps } from '@wordpress/block-editor';

export default function save({ attributes }) {
    const { fallbackCurrentYear, showStartingYear, startingYear } = attributes;
    // const currentYear = new Date().getFullYear().toString(); // 廃止

    // 入力値チェック。値がまだない場合はnullを返す
    if (!fallbackCurrentYear) {
        return null;
    }

    let displayDate;

    if (showStartingYear && startingYear) {
        displayDate = startingYear + '-' + fallbackCurrentYear;
    } else {
        displayDate = fallbackCurrentYear;
    }

    return (
        <p {...useBlockProps.save()}>(c) {displayDate}(save)</p>
    );
}

WordPressさんありがとう。面白かったよ。

原作者さんありがとう。

翻訳者さんありがとう。

Link

チュートリアル: はじめてのブロック作成(公式)

Tutorial: Build your first block(公式)

The WordPress Gutenberg project – UI componentsのAPI …例えばPanelコンポーネントの説明などある

gutenberg components – GitHub …各componentにあるReadme.mdが参考になる

gutenberg block-editor components – GitHub

dashicons …smileyアイコンとか。開くとランダムで表示される。

icons libray …カレンダーアイコンとか。

関連記事

WordPressのブロック開発のクイックスタートガイド