WordPressブロック開発 非推奨としたバージョンのフォールバック機能を作る

カスタムブロックをバージョンアップした場合、ブロックが持つマークアップとデータベースに保存されたマークアップの差異により、フロントエンドに「想定しないコンテンツが含まれています」というエラーが表示されてします。

ブロックをバージョンアップしてもこのエラーが表示されないようにフォールバック機能を実装する方法を見ていきます。

ブロックの非推奨化

ブロック開発者がブロックのsave()の結果を書き換えると警告が表示される理由は、保存されているブロックのHTMLと一致しないためです。

save.jsとデータベースのコンテンツは一致している

// save.js
// データベース
これは本文です

ブロックの更新のため、save.jsのコンテンツを書き換えると、データベースの保存情報との間に差異が出る

// save.js
フロント:本日は晴天なり
// データベース
編集画面:これは本文です

フォールバックを用意していない場合、この段階で編集画面に警告が出ます。しかし、deprecated プロパティを記述していた場合は⋯

WordPress はdeprecated に定義された古いバージョンの save() をチェックする。保存済みのコンテンツがそのdeprecatedsave()と一致すれば、そのバージョンとしてブロックを認識する

// save.js
フロント:本日は晴天なり
// データベース
編集画面:これは本文です
// 旧ver の save
これは本文です

編集画面は古いsave()のロジックを使ってブロックをレンダリングする

// save.js
フロント:本日は晴天なり
// 旧ver の save
編集画面:これは本文です

投稿を保存すると、今度は現在の save() を使って新しい HTML が保存。このとき、古いバージョンから「新しいバージョン」にマイグレーションされる

// save.js
フロント:一富士二鷹
// データベース ← 新ver の save
編集画面:一富士二鷹

deprecated.jsは過去のバージョンのブロックの仕様を定義しておくファイル

deprecated.jsは、過去のバージョンのブロックの仕様を定義しておくファイル。

ブロックをバージョンアップして save() の出力が変わるたびに、以前のバージョンを deprecated.js に追加しておく必要がある。古い定義も保持しておくことで、WordPress が「これは旧バージョンだけど、知ってるため、旧バージョンのコンテンツを返そう」と判断しエラーを回避できる。

実装してみよう

初期ブロック作成

block.json

{
	"attributes": {
		"content": {
			"type": "string",
			"source": "html",
			"selector": "p"
		}
	},
}

edit.js

import { RichText, useBlockProps } from "@wordpress/block-editor";

export default function Edit({ attributes, setAttributes }) {
	return (
		<RichText
			{...useBlockProps()}
			tagName="p"
			value={attributes.content}
			onChange={(content) => setAttributes({ content })}
			placeholder="Enter some text..."
		/>
	);
}

save.js

import { RichText, useBlockProps } from "@wordpress/block-editor";

export default function save({ attributes }) {
	return (
		<RichText.Content
			{...useBlockProps.save()}
			tagName="p"
			value={attributes.content}
		/>
	);
}

完成した初期ブロックをブロックエディタに挿入してテキストを入力し保存する。

<p class="wp-block-create-block-deprecation-example">初期ブロック</p>

HTMLタグをh2に変更し、deprecated.js に v1 を保存

  1. deprecated.js に save.jsのコードをコピー
  2. block.json、edit.js、save.jsに記述したpをh2に変更
  3. index.js から deprecated.js を読み込み

deprecated.js

import { RichText, useBlockProps } from "@wordpress/block-editor";

const v1 = {
	// 初期ブロックのblock.jsonよりコピペ
	attributes: {
		content: {
			type: "string",
			source: "html",
			selector: "p",
		},
	},
	save({ attributes }) {
		return (
			// 初期ブロックのsave.jsよりコピペ
			<RichText.Content
				{...useBlockProps.save()}
				tagName="p"
				value={attributes.content}
			/>
		);
	},
};
export default [v1];

block.json

{
	"attributes": {
		"content": {
			"type": "string",
			"source": "html",
			"selector": "h2"
		}
	},
}

edit.js

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

import { useBlockProps, RichText } from "@wordpress/block-editor";

import "./editor.scss";

export default function Edit({ attributes, setAttributes }) {
	return (
		<RichText
			{...useBlockProps()}
			tagName="h2"
			value={attributes.content}
			onChange={(content) => setAttributes({ content })}
			placeholder="Enter some text..."
		/>
	);
}

save.js

import { RichText, useBlockProps } from "@wordpress/block-editor";

export default function save({ attributes }) {
	return (
		<RichText.Content
			{...useBlockProps.save()}
			tagName="h2"
			value={attributes.content}
		/>
	);
}

index.js

import { registerBlockType } from "@wordpress/blocks";

import "./style.scss";

import Edit from "./edit";
import save from "./save";
import metadata from "./block.json";
import deprecated from "./deprecated";

registerBlockType(metadata.name, {
	edit: Edit,
	save,
	deprecated,
});

クラス名 highlight を追加し deprecated.js に v2 を追加

deprecated.js

import { RichText, useBlockProps } from "@wordpress/block-editor";

const v1 = {
	attributes: {
		content: {
			type: "string",
			source: "html",
			selector: "p",
		},
	},
	save({ attributes }) {
		return (
			<RichText.Content
				{...useBlockProps.save()}
				tagName="p"
				value={attributes.content}
			/>
		);
	},
};

const v2 = {
	attributes: {
		content: {
			type: "string",
			source: "html",
			selector: "h2",
		},
	},
	save({ attributes }) {
		return (
			<RichText.Content
				{...useBlockProps.save({ className: "highlight" })}
				tagName="h2"
				value={attributes.content}
			/>
		);
	},
};

export default [v1, v2];

edit.js

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

import { useBlockProps, RichText } from "@wordpress/block-editor";

import "./editor.scss";

export default function Edit({ attributes, setAttributes }) {
	return (
		<RichText
			{...useBlockProps({ className: "highlight" })}
			tagName="h2"
			value={attributes.content}
			onChange={(content) => setAttributes({ content })}
			placeholder="Enter some text..."
		/>
	);
}

save.js

import { RichText, useBlockProps } from "@wordpress/block-editor";

export default function save({ attributes }) {
	return (
		<RichText.Content
			{...useBlockProps.save({ className: "highlight" })}
			tagName="h2"
			value={attributes.content}
		/>
	);
}

結果

<h2 class="wp-block-create-block-deprecation-example highlight">やあああ</h2>

属性名を変更する

save()の HTML 構造ではなくattributesの形や意味が変わったとき、古いバージョンの属性を新しい形式に自動変換するためmigrateを使用する。

// 旧verの属性名は text
"attributes": {
  "text": {
    "type": "string",
    "source": "html",
    "selector": "p"
  }
}
// 新verの属性名を coontent に変更したい
"attributes": {
  "content": {
    "type": "string",
    "source": "html",
    "selector": "p"
  }
}

新しい属性を追加したとき。初期値が必要

const v1 = {
  attributes: {
    content: {
      type: "string",
      source: "html",
      selector: "p",
    },
  },
  migrate(attributes) {
    return {
      content: attributes.content,
      align: "left", // ← 新しい属性
    };
  },
  save({ attributes }) {
    return <p style={{ textAlign: "left" }}>{attributes.content}</p>;
  },
};

簡単な例

block.json

{
	"attributes": {
		"content": {
			"type": "string",
			"source": "html",
			"selector": "p"
		}
	},
}

edit.js

import { RichText, useBlockProps } from "@wordpress/block-editor";

export default function Edit({ attributes, setAttributes }) {
	return (
		<RichText
			{...useBlockProps()}
			tagName="p"
			value={attributes.content}
			onChange={(content) => setAttributes({ content })}
			placeholder="Enter some text..."
		/>
	);
}

save.js

import { RichText, useBlockProps } from "@wordpress/block-editor";

export default function save({ attributes }) {
	return (
		<RichText.Content
			{...useBlockProps.save()}
			tagName="p"
			value={attributes.content}
		/>
	);
}

マークアップ

<p class="wp-block-create-block-deprecation-example">テスト</p>

属性名を変更

deprecated.js

import { RichText, useBlockProps } from "@wordpress/block-editor";

const v1 = {
	attributes: {
		content: {
			type: "string",
			source: "html",
			selector: "p",
		},
	},
	migrate(attributes) {
		return {
			content: attributes.text,
		};
	},
	save({ attributes }) {
		return (
			<RichText.Content
				{...useBlockProps.save()}
				tagName="p"
				value={attributes.content}
			/>
		);
	},
};

属性名を確認する

ブロックエディタに該当のブロックを挿入し、コンソールを表示する。該当のブロックを選択した状態で次のコードを実行する

// wp グローバル変数を読み込んでいるか確認
// objectが返ればOK
typeof wp 

// 現在選択しているブロックの属性がオブジェクトで表示
wp.data.select('core/block-editor').getSelectedBlock()

// すべてのブロックの情報を配列で表示
wp.data.select('core/block-editor').getBlocks()

参考:
https://developer.wordpress.org/block-editor/reference-guides/block-api/block-deprecation/
https://developer.wordpress.org/news/2023/03/block-deprecation-a-tutorial/