前回ではスロットを紹介しました。
今回はスロットを裏で支えるプラグインについても掘り下げていこうと思います。
プラグインに追加すること前提で提供されているコンポーネントたちです。
2023/02/26時点。
- MainDashboardButton
- PluginBlockSettingsMenuItem
- PluginDocumentSettingPanel
- PluginMoreMenuItem
- PluginPostPublishPanel
- PluginPostStatusInfo
- PluginPrePublishPanel
- PluginSidebar
- PluginSidebarMoreMenuItem
この中からPluginBlockSettingsMenuItem
, PluginDocumentSettingPanel
, PluginMoreMenuItem
を抜粋して試してみましょう。
このうち多くのコンポーネントは@wordpress/edit-post
にあります。
またプラグインは@wordpress/plugins
にあります。
なので先に型定義をインストールします。
npm install --save-dev @types/wordpress__edit-post
npm install --save-dev @types/wordpress__plugins
スロットを表示。
これらのコンポーネントはプラグインに追加する前提なのですが、まだプラグインを説明してないのでバッドな方法ではありますがブロックに直接書いています。
参考程度で決して実戦で使用しないでください。
なぜプラグインが必要なのかを理解する前座のようなものだと思ってください。
Bad Sample Code!
import React from 'react';
import KurageExampleBlockProps from './props';
import './editor.scss';
import { useBlockProps } from '@wordpress/block-editor';
import { BlockEditProps } from '@wordpress/blocks';
import { PluginBlockSettingsMenuItem, PluginDocumentSettingPanel, PluginMoreMenuItem } from '@wordpress/edit-post';
import { wordpress, alignNone, addCard } from '@wordpress/icons';
export default (props: BlockEditProps<KurageExampleBlockProps>) =>
{
return (
<div {...useBlockProps()}>
<h2>スロットスロットランランンラン</h2>
<PluginBlockSettingsMenuItem
icon={ wordpress }
label="ツールバーのメニューに表示されまっす!"
onClick={e => {}} />
<PluginDocumentSettingPanel
icon={ alignNone }
title="投稿サイドバー!"
>
右側の投稿サイドバーに表示されまっす!
</PluginDocumentSettingPanel>
<PluginMoreMenuItem
icon={ addCard}
onClick={() => alert('その他メニュー!')}
>
右上に表示されているメニューでっす!
</PluginMoreMenuItem>
</div>
);
}
PluginBlockSettingsMenuItem
ツールバーのメニューに追加される
PluginDocumentSettingPanel
編集ページの右上のメニューに表示される
PluginMoreMenuItem
ブロックを選択するとブロックサイドバーが現れます。
ブロック以外の場所をクリックしてブロックのフォーカスを外すと投稿サイドバーが表示されます。
そこに追加されます。
なぜいけない?
何故バッドなコードなのかというと前回説明した通り、ブロックを複数追加すると複数のFill
が発生してしまうことになります。
どうなるか実験してみましょう。
同じブロックを3つに追加します。
とりあえずそのうちのPluginDocumentSettingPanel
に注目してみます。
ブロックと同じく三つ分追加されてしまいます。
他のスロットも同様の結果になります。
そこでプラグインの出番です。
プラグインに登録
そこでプラグインが登場します。
import React from 'react';
import KurageExampleBlockProps from './props';
import './editor.scss';
import { useBlockProps } from '@wordpress/block-editor';
import { BlockEditProps } from '@wordpress/blocks';
import { PluginBlockSettingsMenuItem, PluginDocumentSettingPanel, PluginMoreMenuItem } from '@wordpress/edit-post';
import { wordpress, alignNone, addCard } from '@wordpress/icons';
import { registerPlugin } from '@wordpress/plugins';
const PluginBlockSettingsMenuItemRender = () =>
{
return (
<PluginBlockSettingsMenuItem
allowedBlocks={ ['create-block/kurage-worker'] }
icon={ wordpress }
label="ツールバーのメニューに表示されまっす!"
onClick={e => { alert('うなぎたべたい・・・') }} />
)
}
const PluginDocumentSettingPanelRender = () =>
{
return (
<PluginDocumentSettingPanel
icon={ alignNone }
title="投稿サイドバー!"
>
右側の投稿サイドバーに表示されまっす!!
</PluginDocumentSettingPanel>
)
}
const PluginMoreMenuItemRender = () =>
{
return (
<PluginMoreMenuItem
icon={ addCard}
onClick={() => alert('かつ丼たべたい・・・')}
>
右上に表示されているメニューでっす!
</PluginMoreMenuItem>
)
}
// @ts-expect-error
registerPlugin('kurage-slot-slot-1', {
render: PluginBlockSettingsMenuItemRender
});
// @ts-expect-error
registerPlugin('kurage-slot-slot-2', {
render: PluginDocumentSettingPanelRender
});
// @ts-expect-error
registerPlugin('kurage-slot-slot-3', {
render: PluginMoreMenuItemRender
});
export default (props: BlockEditProps<KurageExampleBlockProps>) =>
{
return (
<div {...useBlockProps()}>
<h2>スロットスロットランランンラン</h2>
</div>
);
}
どうも現時点で型定義が信用出来ないのでエラーは握りつぶしてます。
エラーを握りつぶすためコメントで@ts-expect-error
を指定してます。
registerPlugin()
でプラグインを登録します。
一つ目の引数はプラグイン名
で重複は避けてください。
二つ目の引数でicon
, render
, scope
などを設定しますが今回はrender
だけ見ていきます。
このrender
にコンポーネントを渡すことで前回の問題を回避できます。
allowedBlocks={ ['create-block/kurage-worker'] }
PluginBlockSettingsMenuItem
のallowedBlocks
はどのブロックに追加するかです。
今回は自分の作ったブロック(create-block/kurage-worker)のツールバーにだけ追加するという意味です。
これを指定しないと他人の作ったブロックや元から用意されているブロックのツールバーにまで追加されてしまいます。
どこに配置された?
このrender
で指定したコンポーネントはある場所(プラグインエリア)に追加されます。
一応それらしきものはあるっぽい。
さて、プラグインの使い方は分かりました。
でもプラグインの正体がよくが分かりません。
ただ事前に言っておきますが、
多分ブロック開発者にとってプラグインは不要な知識だと思います。
ここから先はちょっと無駄な知識になる可能性があります。
プラグイン実装
プラグインは@wordpress/plugins
パッケージで実装されています。
非常に単純なパッケージなのでコードを読んだ方が早いかもしれません。
詳細を一切考えず大体の構造を頭の中でイメージすると
プラグインを登録するregisterPlugin()
、プラグインを取得するgetPlugins()
やgetPlugin()
があり、現在登録されているプラグインをレンダリングするコンポーネントPluginArea
が存在します。
めっちゃ適当ですが図解で見てみましょう!
以下はスロットをプラグインに登録し、そのプラグインを取得する例です。
import React from 'react';
import KurageExampleBlockProps from './props';
import './editor.scss';
import { useBlockProps } from '@wordpress/block-editor';
import { BlockEditProps } from '@wordpress/blocks';
import { wordpress, alignNone, addCard, download, external } from '@wordpress/icons';
import { getPlugin, getPlugins, registerPlugin } from '@wordpress/plugins';
registerPlugin('kurage-slot-slot-1', {
// @ts-ignore
icon: wordpress,
render: () => <p>Slot 1</p>,
scope: 's-b'
});
registerPlugin('kurage-slot-slot-2', {
// @ts-ignore
icon: alignNone,
render: () => <p>Slot 2</p>,
scope: 's-a'
});
registerPlugin('kurage-slot-slot-3', {
// @ts-ignore
icon: addCard,
render: () => <p>Slot 3</p>,
scope: 's-a'
});
registerPlugin('kurage-slot-slot-4', {
// @ts-ignore
icon: download,
render: () => <p>Slot 4</p>,
scope: 's-b'
});
registerPlugin('kurage-slot-slot-5', {
// @ts-ignore
icon: external,
render: () => <p>Slot 5</p>,
scope: 's-c'
});
console.log("getPlugins")
// @ts-ignore
const plugins = getPlugins('s-b');
console.log(plugins);
console.log("getPlugin")
const plugin = getPlugin('kurage-slot-slot-3');
console.log(plugin);
export default (props: BlockEditProps<KurageExampleBlockProps>) =>
{
return (
<div {...useBlockProps()}>
<h2 className="xx">スロットスロットランランンラン</h2>
</div>
);
}
registerPlugin()
のオプションでscope
を指定すると、プラグインはそのスコープに属します。
getPlugin()
もgetPlugins()
も追加したプラグインを取得します。
getPlugins()
はそのスコープ(scope
で指定した名前)に属するプラグインを複数取得します。
getPlugin()
はプラグイン名(一意である必要あり)から一つ取得します。
getPlugins()
にs-b
を渡すと、プラグインのscope
でs-b
を指定したkurage-slot-slot-1
とkurage-slot-slot-4
が取得されます。
またプラグインのスコープは無名
であることが出来るようです。
無名にするとgetPlugins()
はscope
を指定してないプラグインを取得します。
現在プラグインは内部的にグローバルなリテラルオブジェクトとして保持されているもようです。
プラグイン名がそのプロパティ名になるので重複するとおかしなことになります。
次にプラグインのレンダリングです。
プラグインに設定されたrender
をレンダリングする場所はPluginArea
です。
import { PluginArea } from '@wordpress/plugins';
const PluginArea2 = PluginArea as any;
export default (props: BlockEditProps<KurageExampleBlockProps>) =>
{
return (
<div {...useBlockProps()}>
<h2>スロットスロットランランンラン</h2>
<p>スコープ s-a</p>
<PluginArea2 scope="s-a" />
<p>スコープ s-b</p>
<PluginArea2 scope="s-b" />
</div>
);
}
ちょっと型定義がアレなのでPluginArea
をany
に変換してます。
あれあれ?
表示されてませんね!
でもDOMを見てみるとちゃんと追加されています。
ただし、display: none
が設定されているため画面からは見えません。
無名のプラグインエリアはどこへ?
PluginArea
はscope
を省略出来ます。
無名のプラグインエリアになります。
既にワードプレス本体によってコンポーネント上位層に配置されてるはず。
テキストサーチしたところあった。
間違ったことを書いてるかもしれませんので眉唾程度で考えてください。
さすがにこれが正しいのかを調べるのは面倒なのでしません。
ブラウザではどう表示されているでしょう?
// @ts-ignore
registerPlugin('kurage-slot-slot-1', {
render: () => <p className="marker">Slot 1</p>
});
export default (props: BlockEditProps<KurageExampleBlockProps>) =>
{
return (
<div {...useBlockProps()}>
<h2>スロットスロットランランンラン</h2>
</div>
);
}
slot
を指定せずにプラグインを追加。
DOMツリーをセレクタ(p.marker
)でたどると見つかりました。
一応Chromeの開発者ツールからCSSのdisplay: none
は削除しました。
ちょっと話はそれますが、よく見ると上に.popover-slot
を持ったdiv
がいますね。
本家ドキュメントにpopover
やtooltip
の解説にスロットを参照するよう書かれてましたが、全然詳しいこと書かれて無かった。
仕方なくソースコードを読んでいるうちにここまでたどり着いたんですね(苦労した!)。
なぜ?
なぜプラグインエリアはnone
なのか?
多分、多分だけど、プラグイン自体はただのFill
の配置場所としてあるんではないかなと。
一旦Fill
を非表示のプラグインエリアに配置して、Scope
が存在したらそっちにDOMを表示すると。
ただの場所提供みたいな。
あとFill
についてですが、ソースコードを眺めるとレンダリングにはcreatePortal()
が使われています。
第二引数がスロットのノードになっており、対象のスロットが存在しなかったらそもそもレンダリングされません。
つまり、
- 無名のプラグインエリアを配置
- プラグインを登録する
- プラグインエリアに配置されるが非表示
- もしプラグインが
Fill
の場合、対象のスロットが無ければレンダリングされない - ただ対象のスロットがあればそちらに表示される
もう文書書く能力無さ過ぎて何言ってるのかわからないと思いますが何となくのイメージです。
これがスロットとプラグインの合わせ技みたいな。
間違ってたら本当にごめんなさい、先に誤っておきます。
スロットとの融合
プラグインの仕組みは把握した。
後はスロットが存在しない時のFill
はレンダリングされないことを確かめてみます。
複製されてしまうブロック内にスロット配置するのはどうかとは思いますけど実験してみましょう。
スロットがある場合とない場合でFill
がどうなるのか?
対応するスロットが存在する場合
registerPlugin('kurage-slot-slot-1', {
render: () => <MyCustomBlock><h2 className="marker">うなぎぱい!</h2></MyCustomBlock>
});
export default (props: BlockEditProps<KurageExampleBlockProps>) =>
{
return (
<div {...useBlockProps()}>
<h2>スロットスロットランランンラン</h2>
<div style={{borderWidth: 1, borderColor: 'red', borderStyle: 'solid', padding: 5}}>
<MyCustomBlock.Slot />
</div>
</div>
);
}
対応するスロットがあるのでそちらに移転してます。
.marker
をもったh2
が確認出来ます。
対応するスロットが存在しない場合
// @ts-ignore
registerPlugin('kurage-slot-slot-1', {
render: () => <MyCustomBlock><h2 className="marker">うなぎぱい!</h2></MyCustomBlock>
});
export default (props: BlockEditProps<KurageExampleBlockProps>) =>
{
return (
<div {...useBlockProps()}>
<h2>スロットスロットランランンラン</h2>
<div style={{borderWidth: 1, borderColor: 'red', borderStyle: 'solid', padding: 5}}>
Remove MyCustomBlock.Slot
</div>
</div>
);
}
コンポーネントは無名プラグインエリアにあるものの、
DOM検索で.marker
は見つからずDOMは生成されてないっぽい。
次回は高階コンポーネントについてです。