WPのブロック作成メモ

調べたり試したことの個人的なメモとして。

前提

  • @wordpress/create-blockを使って構築する

メモ

以下は調べたことや試したことなど。個人の見解だったり、間違っている可能性もあります。

レファレンスサイト

1プラグインに1ブロック

@wordpress/create-blockといいますかblock.jsonを使う場合、ブロックは1つしか作れない模様です。

つまり、ブロックの数だけプラグインを作る必要があります。

1プラグインに複数のブロックを作る方法

実際に試してはいませんので動作するのかわかりませんが、以下のページで複数作ることができそうな情報がありました。

ただし、1目のリンク先では実際に動かしている人もいる感じですが、コンソールに多少のエラーが表示されるようです。
また、結構変更しないといけない部分が出てくるようなので、この点からも複数のブロックを一纏めにすることは本来想定されていない印象は受けます。

node_modules重複問題

ローカルでの開発時に困る可能性として、@wordpress/create-blockで作成した数だけnode_modulesも増えることが考えられます。

いろいろ考えましたが、個人的には以下の方法を採用しました。

  • @wordpress/create-blockで複数プラグインを作成した場合、一つをのぞいてnode_modulesは全て消す
  • 作業するプラグインにnode_modulesを移動させて使う

必要な時に必要な場所にのみ設置するという方針です。手作業が必要なので手間はかかりますが、ブロックを増やすたびに380MB程度のnode_modulesを増やしていくのは無駄が多すぎると思えました。

なおWP-CLIならどうにかできるかと思ったのですが、以下のように書かれていて微妙な感じでした。

非推奨: ブロックのひな形の生成に WP-CLI や create-guten-block を使うことは推奨されません。

公式のブロック生成スクリプトは新しい @wordpress/create-block パッケージです。このパッケージは新しいブロックディレクトリガイドラインに従い、適切にブロック、環境、プロジェクトの標準セットを作成します。

調査を進めていないので実際にはできるのかもしれませんが、ブロック作成用ツールとしては@wordpress/create-blockを使うのが良いようです。

get_stylesheet_directory_uri()などの値をブロック内で使う

把握しているのは上記2つですが、2つ目の記事に書かれているように値を取得するだけなら、1つ目のwp_localize_script()を使う方法を選ぶのが良いと思います。
ただし少々融通の効かない使い方にはなってしまうようですが。

  1. jquery-coreなど管理画面で読み込まれている可能性が非序に高いJSに対して、wp_localize_script()を用いる
    すでにwp_localize_script()で値が設定されていた場合、追加されるだけなので問題は出ないはず)
  2. プラグイン内になんでも良いのでJSを読み込ませて、そのJSに対してwp_localize_script()を用いる
    (中身は空でもいい/srcディレクトリには入れない)

wp_localize_script()wp_enqueue_script()で追加したファイルに対して補助的に用いられるため、既存か独自追加のJSを利用しなければなりません。同種のことができると思われるwp_add_inline_script()でも同様です。
例としては以下のようになります。


function my_enqueue_scripts(){
  wp_localize_script( 'jquery-core', 'my_site_data', [
		'templateDirectoryUri' => get_template_directory_uri(),
		'stylesheetDirectoryUri' => get_stylesheet_directory_uri()
	] );
}
add_action( 'admin_enqueue_scripts', 'my_enqueue_scripts' );

上記でグローバル変数として利用できるようになるため、index.jsなりedit.jsなりで以下のように記述すれば値が利用できます。


const stylesheetDirectoryUri = my_site_data.stylesheetDirectoryUri;

block.jsonのattributes

日本語訳されたハンドブックでは上記が該当します。ただし英語のハンドブックも同様ですが正直なところ読むだけではよくわかりません。
重要な箇所としては以下が該当します。

属性ソースが指定されていない場合、属性はブロックのコメント区切り文字に保存されます(そしてブロックのコメント区切り文字から読み取られます)。
属性ソースオブジェクトで指定されたキーには、適切と思われる名前が付けられます。属性ソース定義の結果は、各キーに値として割り当てられます。
セレクター引数が指定されていない場合、ソース定義はブロックのルートノードに対して実行されます。セレクター引数が指定されている場合、ブロック内に含まれている指定された要素に対して実行されます。

多少の具体例としては以下のようになります。

  • attributes"type":"string"だけ記述した場合、コメント部分(ハイフォンなどはサニタイズされる)と出力部分(=出力記述があった場合)の2箇所に値が記載される
  • 出力箇所に合わせたtypeselectorを記述すると、コメントには記載されず出力記述箇所にのみ記載される
  • typeselectorが出力箇所に合っていないと、コメントに記載される上、エディタ画面でリロードすると壊れる

「全てコメントで良い」という場合は気にしないでよいと思いますが、無駄にコメントに記載したくないという場合はしっかりと合わせて書く必要があります。

では何をどう書けば良いのかという問題になりますが、説明が難しいと言いますか、書くとハンドブックと大差ない内容となるので、個人的にはGithub上のコアブロックを参考にすることをお勧めします。

特に分かりやすいと思ったのは以下の画像ブロックのblock.jsonです。

block.jsonを見る

{
	"apiVersion": 2,
	"name": "core/image",
	"title": "Image",
	"category": "media",
	"description": "Insert an image to make a visual statement.",
	"keywords": [ "img", "photo", "picture" ],
	"textdomain": "default",
	"attributes": {
		"align": {
			"type": "string"
		},
		"url": {
			"type": "string",
			"source": "attribute",
			"selector": "img",
			"attribute": "src"
		},
		"alt": {
			"type": "string",
			"source": "attribute",
			"selector": "img",
			"attribute": "alt",
			"default": ""
		},
		"caption": {
			"type": "string",
			"source": "html",
			"selector": "figcaption"
		},
		"title": {
			"type": "string",
			"source": "attribute",
			"selector": "img",
			"attribute": "title"
		},
		"href": {
			"type": "string",
			"source": "attribute",
			"selector": "figure > a",
			"attribute": "href"
		},
		"rel": {
			"type": "string",
			"source": "attribute",
			"selector": "figure > a",
			"attribute": "rel"
		},
		"linkClass": {
			"type": "string",
			"source": "attribute",
			"selector": "figure > a",
			"attribute": "class"
		},
		"id": {
			"type": "number"
		},
		"width": {
			"type": "number"
		},
		"height": {
			"type": "number"
		},
		"sizeSlug": {
			"type": "string"
		},
		"linkDestination": {
			"type": "string"
		},
		"linkTarget": {
			"type": "string",
			"source": "attribute",
			"selector": "figure > a",
			"attribute": "target"
		}
	},
	"supports": {
		"anchor": true,
		"color": {
			"__experimentalDuotone": "img",
			"text": false,
			"background": false
		},
		"__experimentalBorder": {
			"radius": true
		}
	},
	"styles": [
		{
			"name": "default",
			"label": "Default",
			"isDefault": true
		},
		{ "name": "rounded", "label": "Rounded" }
	],
	"editorStyle": "wp-block-image-editor",
	"style": "wp-block-image"
}

上記のatributesを参考にして実際にimgタグを作成すると、"type": "string"のみ時と適切に記述した時の違いを確認しながら全体のイメージが掴みやすいと思います。

onloadをedit.jsやsave.jsで使う


import domReady from '@wordpress/dom-ready';
export default function Edit(){
  // 省略
  // 画像のwidthとheghtを取得してattributesのwidthとheightに入れる用のコード
  domReady(function(){
    const image = new Image();
    image.onload = function(){
      attributes.width = image.width;
      attributes.height = image.height;
    };
    image.src = attributes.srcValue;
  });
  //省略
}

上記は試作したコードの一部ですが、domReadyを使えばDOM構築後に実行できます。

ただ、Lintを通すと以下の警告が出るため、エラーを消すにはターミナルなどでコードを実行する必要があります(実行せずとも動きはしますが)。

‘@wordpress/dom-ready’ should be listed in the project’s dependencies. Run ‘npm i -S @wordpress/dom-ready’ to add it


$ npm i -S @wordpress/dom-ready

useEffect()

TOPレベルであればuseEffect()も使えると思います。
ブロックエディタの例としては以下のような投稿がありました。


edit( { setAttributes } ) {
  // get the blocks in the post.
  const blocks = useSelect( ( select) => select( 'core/block-editor' ).getBlocks(), [] );
  // memoize filtering, so that blocks are only filtered when they change.
  const headings = useMemo( () => filterBlocksToHeadings( blocks ), [ blocks ] );
  // use an effect to setAttributes, so that it is only called when `headings` changes.
  useEffect( () => setAttributes( { headings } ), [ headings ] );

  // rest of edit function
  // ...
}

試作したコードは少し用途が偏っていますが、初回にデフォルトの画像を表示させるように作った以下のコードのような場面で使えるかと思います。


useEffect( () => {
  if ( ! attributes.src ) {
    setAttributes( { src: default.png' } );
  }
}, [] );

以下を参考にしました。

もしも副作用とそのクリーンアップを 1 度だけ(マウント時とアンマウント時にのみ)実行したいという場合、空の配列 ([]) を第 2 引数として渡すことができます。こうすることで、あなたの副作用は props や state の値のいずれにも依存していないため再実行する必要が一切ない、ということを React に伝えることができます。これは特別なケースとして処理されているわけではなく、依存配列を普通に処理すればそうなるというだけの話です。

meta用ブロック

  • register_block_type_from_metadata()を記載しているPHPファイルなどに、register_post_meta()でキーを設定しておく
  • register_post_meta()には複数のキーをセットできない(未確認/arrayならいける?)
  • 該当ブロックを入れた投稿画面を表示させた状態でコンソールにwp.data.select( 'core/editor' ).getCurrentPost()[ 'meta' ]と書けば、metaが設定できているかを確認できる。だだし更新ボタンを押さないと値は確認できない。

setMeta()だけでは目的を達成できない場合は、適宜setAttributes()も絡める必要があるかもしれません。

例えばmetaに画像のhrefを入れた場合、その画像をエディタで表示して確認したい場合にはmetaの値を取得して表示するよりも、setMeta()と同時にsetAttributes()で値を保存してそちらから取り出す方が意図通りにできる可能性があります。

[追記:2021.12.9]

メタ用ブロックに関する記事を作成しました。

リロード時にブロックが壊れる


Content generated by `save` function:
//`save`関数によって生成されたコンテンツ

Content retrieved from post body:
//投稿本文から取得したコンテンツ

ブロックを設置して「更新」ボタンを押した後、上記のようにエラーが出てブロックが壊れる場合があります。
理由はいくつかあるようですが、遭遇して難儀したのが以下の理由による破損です。

  • attibutesを利用しているpタグやspnaタグが複数ある
  • selectorpspanだけを記載していると、正しく情報が読み取られず値が上書きされる

例えば以下のようなblock.jsonedit.jsを作ったと仮定します。
この状態では前述と同様の問題が起きます。


{
  //省略
  "attributes": {
    "text1": {
      "type": "string",
      "selector": "p",
      "source": "text",
      "default": "テキスト1"
    },
    "text2": {
      "type": "string",
      "selector": "p",
      "source": "text",
      "default": "テキスト2"
    }
  },
  //省略
}

function edit( { attributes, setAttributes } ) {
  const { text1, text2 } = attributes;
  return(
    <>
      <RichText.Content
        tagName={ 'p' }
        onChange={ ( value ) => setAttributes( { text1: value } ) }
        value={ text1 }
        className="text1"
      />
      <RichText.Content
        tagName={ 'p' }
        onChange={ ( value ) => setAttributes( { text2: value } ) }
        value={ text2 }
        className="text2"
      />
    </>
  );
}

解決のためには、以下のようにblock.jsselectorclassNameを追加して、どのpタグがどの値を持っているのかを特定できるように設定する必要があります。


{
  //省略
  "attributes": {
    "text1": {
      "type": "string",
      "selector": "p.text1",
      "source": "text",
      "default": "テキスト1"
    },
    "text2": {
      "type": "string",
      "selector": "p.text2",
      "source": "text",
      "default": "テキスト2"
    }
  },
  //省略
}

この件は以下のページの情報が参考になりました。説明としてもわかりやすい書き方だと思います。

When registering a block in JS I forgot to add attributes with selectors. When added, block start working.

チェックボックスなどによる要素の表示非表示

  • 表示非表示の対象要素に対してblock.json"source": "html"などで設定すると、更新後のリロードで表示していない方の値が消えてしまう。
  • attributeは使わずにtypeのみにしてコメントに値を保存する形にすれば、更新後のリロードでも値は残る

チェックボックスと再利用ブロック

調査が浅いですが、チェックボックスがついたブロックを再利用ブロックに登録した場合、記事投稿画面では以下のような問題が起きます。

  • チェックボックスを変更して「更新」を押した後、チェックボックスを変更しても「更新」ボタンが押せなくなる

調べた範囲での対応策は以下の通り。

  • チェックボックスを変更して「更新」を押した後、リロードする
  • 「すべての再利用ブロックを管理」の画面に移動して、チェックボックスを変更する

再利用ブロックの利用意図を踏まえれば、「すべての再利用ブロックを管理」の画面に移動して操作するのが適切かなと思いました。

InnerBlocksの並び方向指定

By default, InnerBlocks expects its blocks to be shown in a vertical list. A valid use-case is to style InnerBlocks to appear horizontally. When blocks are styled in such a way, the orientation prop can be used to indicate a horizontal layout:


<InnerBlocks orientation="horizontal" />

ブロックは基本的に上から下へと縦並びが意図されているので、InnerBlocksを横に並べるとドラッグによる位置変更が正常に動かない可能性があるようです。
そのためorientation="horizontal"を設定して、横方向に並ぶこと明示的する必要がある。ということかと思います(動作未確認)。

ブロックに使用可能なアイコン

2つ目のページに関しては以下参照。

block.jsonに使えるアイコン

An icon property should be specified to make it easier to identify a block. These can be any of WordPress’ Dashicons (slug serving also as a fallback in non-js contexts).

block.jsonに使えるのは、ハンドブックの上記の記述からDashiconsのアイコンになる模様です。独自にSVGによる追加も可能とのことですが、前述のStorybookにあるアイコンは使えません。

コアのブロックにスタイルを追加

以下のページのコードでテーブルブロックのなどで選べる「デフォルト」「ストライプ」のようなスタイルを追加することができます。


register_block_style(
  'core/table',
  array(
    'name'  => 'my-table-style',
    'label' => 'My Table Style',
  )
);

以下も参考に。

ツールバーのドロップダウンに任意のボタンを追加する

JSのファイルをテーマに読みこませることで実現できるため、ブロックのためのプラグインなどは不要です。

選択中のブロックのattributesを取得/確認する


wp.data.select( 'core/block-editor' ).getSelectedBlock().attributes;

選択しているブロックのattributesを取得するコード。
投稿画面でブロックを選択状態にした上で、このコードをそのまま開発者ツールのコンソールに書くと、該当のブロックに設定されたattributesが表示されます。

style-index.css

使い方次第で邪魔にも便利にもなるという印象なので、テーマの構造によって判断が必要かもしれません。

style-index.cssの削除

srcディレクトリ内のstyle.scssは、ビルド時にstyle-index.cssとして出力されます。

しかし読み込みファイル数の削減のためにテーマ側のCSSに記述するなどの理由がある場合、style-index.cssが読み込まれては意味がありません。
対策としてblock.jsonの一部を変更すれば削除できます。


{
  //省略
    "textdomain": "n-separator",
    "editorScript": "file:./build/index.js",
    "editorStyle": "file:./build/index.css",
    "style": "file:./build/style-index.css"
}
//上記を以下のように変更
{
  //省略
    "textdomain": "n-separator",
    "editorScript": "file:./build/index.js",
    "editorStyle": "file:./build/index.css"
}
//または "style": "" のように値を消す

style-index.cssをページ下部にインラインで出力

上記で書かれているshould_load_separate_core_block_assetsを設定することでコアのブロックのCSSを、ページ下部にインラインで出力させることができますが、試したところ独自ブロックでも同様に下部に出力させることができていました。


add_filter( 'should_load_separate_core_block_assets', '__return_true' );

上記のようにshould_load_separate_core_block_assetsを設定し、下記のように対象とするblock.jsonstyleハンドルを記載するだけでOKです。
@wordpress/create-blockを使っていればデフォルトで書かれているはずです)


//省略
  "editorScript": "file:./build/index.js",
  "editorStyle": "file:./build/index.css",
  "style": "file:./build/style-index.css"
}

上記の状態でbuildしてサイトに反映すると、以下のような形でfooter閉じタグの下あたりに出力されます。


<style id='create-block-ブロック名-style-inline-css'>
.example{color:red}
</style>

トラブルシューティング

慣れたと思ってもミスはするので一応記録に残しておきたいと思います。

リロードで入力した内容が消えたり他の箇所の値と同じになる

  • edit.jsRichTextを複数用いてattributesに値を保存して用いる
  • 値を入力して更新ボタンを押した段階では、attributesに値があることは確認できている
  • リロードすると値が消えてページにも何も表示されないか、他の箇所に入力した内容と同じものが表示される

上記の前提と問題が起こった場合、原因としてあり得そうなのは以下の3つ。

  • block.jsonで設定したattributessourceが適切ではない
    (属性として出力するにのhtmlで設定しているなど)
  • block.jsonで設定したattributesselectorが適切ではない
    (セレクタとしてdivしか書かれておらず、div.hogeのようにタグを特定する記述になっていないなど)
  • save.jstagName={ 'div' }のような書き方になっていない
    (edit.jsと同じtagName="div"でない)

結び

ブロック関連は記事が散逸してきましたが、取り組むたびに期間が少し開くことが多いため、残すべきメモもまとめにくく現状のようになっています。

フルサイト編集導入時やそれ以降でまたいろいろ変わるでしょうから、この状態もまだ続きそうです。

2人がこの記事を評価

役に立ったよという方は上の「記事を評価する」ボタンをクリックしてもらえると嬉しいです。

連投防止のためにCookie使用。SNSへの投稿など他サービスとの連動は一切ありません。

コメント欄