はじめに

多くのパッケージとコンポーネントを持つGutenbergのコードベースは、最初は圧倒されるかもしれません。しかし、その核心はブロックの管理と編集にあります。したがって、エディタに取り組む場合、ブロック編集が基本的にどのように機能するかを理解することが不可欠です。

このガイドでは、WordPress内で完全に機能するカスタムブロックエディタの「インスタンス」を構築する方法を説明します。その過程で、ブロックエディタがどのように機能するかを理解するために、主要なパッケージとコンポーネントを紹介します。

この記事の終わりまでには、ブロックエディタの内部動作をしっかりと理解し、自分自身のブロックエディタインスタンスを作成する準備が整います。

このガイドで使用されるコードは、付随するWordPressプラグインからダウンロードできます。このプラグインのデモコードは、重要なリソースです。

コード構文

このガイドのコードスニペットは、JSX構文を使用しています。ただし、好みに応じてプレーンJavaScriptを使用することもできます。しかし、JSXに慣れると、多くの開発者はそれを読み書きするのが簡単だと感じるため、Block Editor Handbookのすべてのコード例はこの構文を使用しています。

構築するもの

このガイドを通じて、(ほぼ)完全に機能するブロックエディタインスタンスを作成します。結果は次のようになります:

カスタムWordPress管理ページ内に例のブロックで埋め込まれたスタンドアロンエディタインスタンス

見た目は似ていますが、このエディタはWordPressで投稿やページを作成する際に馴染みのあるブロックエディタとは異なります。代わりに、「ブロックエディタ」と呼ばれるカスタムWordPress管理ページ内に存在する完全にカスタムのインスタンスになります。

このエディタには次の機能があります:

  • すべてのコアブロックを追加および編集する機能。
  • 視覚的スタイルとメイン/サイドバーのレイアウトに慣れ親しんだもの。
  • ページのリロード間での基本的なブロックの永続性。

プラグインのセットアップと構成

カスタムエディタはWordPressプラグインとして構築されます。シンプルに保つために、プラグインはStandalone Block Editor Demoと名付けられます。なぜなら、それが行うことだからです。

プラグインのファイル構造は次のようになります:

alt text

ここでは、何が起こっているのかの簡単な概要を示します:

  • plugin.php – コメントメタデータを持つ標準的なプラグイン「エントリ」ファイルで、init.phpが必要です。
  • init.php – メインプラグインロジックの初期化を処理します。
  • src/ (ディレクトリ) – ここにJavaScriptとCSSのソースファイルが格納されます。これらのファイルはプラグインによって直接キューに追加されません。
  • webpack.config.js – カスタムCSSスタイルを許可するために、@wordpress/scripts npmパッケージによって提供されるデフォルトを拡張するカスタムWebpack設定です。

上記に示されていない唯一の項目はbuild/ディレクトリで、ここに@wordpress/scriptsによって出力されたコンパイル済みJSおよびCSSファイルがあります。これらのファイルはプラグインによって別々にキューに追加されます。

このガイドを通じて、ファイル名の参照は各コードスニペットの先頭にコメントとして配置されるので、追跡できます。

基本的なファイル構造が整ったので、必要なパッケージを見てみましょう。

エディタの「コア」

WordPressエディタは多くの動く部分で構成されていますが、そのコアは@wordpress/block-editorパッケージであり、これは自身のREADMEファイルで最もよく要約されます:

このモジュールは、スタンドアロンのブロックエディタを作成して使用することを可能にします。
完璧です。これはカスタムブロックエディタインスタンスを作成するために使用する主要なパッケージです。しかしまず、エディタのためのホームを作成する必要があります。

カスタム「ブロックエディタ」ページの作成

WordPress管理内にカスタムブロックエディタインスタンスを収容するカスタムページを作成することから始めましょう。

WordPressでカスタム管理ページを作成するプロセスにすでに慣れている場合は、先に進むことをお勧めします。

ページの登録

これを行うには、標準のWordPress add_menu_page()ヘルパーを使用してカスタム管理ページを登録する必要があります:

  1. // File: init.php
  2. add_menu_page(
  3. 'Standalone Block Editor', // Visible page name
  4. 'Block Editor', // Menu label
  5. 'edit_posts', // Required capability
  6. 'getdavesbe', // Hook/slug of page
  7. 'getdave_sbe_render_block_editor', // Function to render the page
  8. 'dashicons-welcome-widgets-menus' // Custom icon
  9. );
  1. <a name="adding-the-target-html"></a>
  2. ### ターゲットHTMLの追加
  3. ブロックエディタはReact駆動のアプリケーションであるため、JavaScriptがブロックエディタをレンダリングできるカスタムページにいくつかのHTMLを出力する必要があります。
  4. 上記のステップで参照された`````getdave_sbe_render_block_editor`````関数を使用しましょう。
  5. ``````bash
  6. // File: init.php
  7. function getdave_sbe_render_block_editor() {
  8. ?>
  9. <div
  10. id="getdave-sbe-block-editor"
  11. class="getdave-sbe-block-editor"
  12. >
  13. Loading Editor...
  14. </div>
  15. <?php
  16. }
  17. `

この関数は基本的なプレースホルダーHTMLを出力します。id属性getdave-sbe-block-editorに注意してください。これはすぐに使用されます。

JavaScriptとCSSのキュー追加

ターゲットHTMLが整ったので、カスタム管理ページで実行されるJavaScriptとCSSをキューに追加できます。

これを行うには、admin_enqueue_scriptsにフックしましょう。

まず、カスタムコードがカスタム管理ページでのみ実行されることを確認する必要があります。したがって、コールバック関数の先頭で、ページがページの識別子と一致しない場合は早期に終了します:

  1. // File: init.php
  2. function getdave_sbe_block_editor_init( $hook ) {
  3. // Exit if not the correct page.
  4. if ( 'toplevel_page_getdavesbe' !== $hook ) {
  5. return;
  6. }
  7. }
  8. add_action( 'admin_enqueue_scripts', 'getdave_sbe_block_editor_init' );

これが整ったら、標準のWordPress wp_enqueue_script()関数を使用してメインJavaScriptファイルを安全に登録できます:

  1. // File: init.php
  2. wp_enqueue_script( $script_handle, $script_url, $script_asset['dependencies'], $script_asset['version'] );

時間とスペースを節約するために、$script_変数の割り当ては省略されています。これらをこちらで確認できます

スクリプト依存関係の第3引数$script_asset['dependencies']に注意してください。これらの依存関係は、@wordpress/dependency-extraction-webpack-pluginを使用して動的に生成され、

WordPressが提供するスクリプトがビルドされたバンドルに含まれないことを保証します。

また、カスタムCSSスタイルとWordPressのデフォルトフォーマットライブラリの両方を登録して、いくつかの素晴らしいデフォルトスタイリングを活用する必要があります:

  1. // File: init.php
  2. // Enqueue default editor styles.
  3. wp_enqueue_style( 'wp-format-library' );
  4. // Enqueue custom styles.
  5. wp_enqueue_style(
  6. 'getdave-sbe-styles', // Handle
  7. plugins_url( 'build/index.css', __FILE__ ), // Block editor CSS
  8. array( 'wp-edit-blocks' ), // Dependency to include the CSS after it
  9. filemtime( __DIR__ . '/build/index.css' )
  10. );

エディタ設定のインライン化

@wordpress/block-editorパッケージを見てみると、エディタのデフォルト設定を構成するために設定オブジェクトを受け入れることがわかります。これらはサーバー側で利用可能であるため、JavaScript内で使用できるように公開する必要があります。

これを行うために、グローバルwindow.getdaveSbeSettingsオブジェクトに割り当てられた設定オブジェクトをJSONとしてインライン化することにしましょう:

  1. // File: init.php
  2. // Get custom editor settings.
  3. $settings = getdave_sbe_get_block_editor_settings();
  4. // Inline all settings.
  5. wp_add_inline_script( $script_handle, 'window.getdaveSbeSettings = ' . wp_json_encode( $settings ) . ';' );

カスタムブロックエディタの登録とレンダリング

上記のPHPが管理ページを作成するために整ったので、今やJavaScriptを使用してページのHTMLにブロックエディタをレンダリングする準備が整いました。

まず、メインのsrc/index.jsファイルを開きます。次に、必要なJavaScriptパッケージをプルインし、CSSスタイルをインポートします。Sassを使用する場合は、デフォルトの@wordpress/scripts Webpack設定を拡張する必要があります。

  1. // File: src/index.js
  2. // External dependencies.
  3. import { createRoot } from 'react-dom';
  4. // WordPress dependencies.
  5. import domReady from '@wordpress/dom-ready';
  6. import { registerCoreBlocks } from '@wordpress/block-library';
  7. // Internal dependencies.
  8. import Editor from './editor';
  9. import './styles.scss';

次に、DOMが準備できたら、次のことを行う関数を実行する必要があります:

  • window.getdaveSbeSettingsからエディタ設定を取得します(以前にPHPからインライン化されたもの)。
  • registerCoreBlocksを使用してすべてのコアWordPressブロックを登録します。
  • カスタム管理ページの待機中の<div><Editor>コンポーネントをレンダリングします。
  1. domReady( function () {
  2. const root = createRoot( document.getElementById( 'getdave-sbe-block-editor' ) );
  3. const settings = window.getdaveSbeSettings || {};
  4. registerCoreBlocks();
  5. root.render(
  6. <Editor settings={ settings } />
  7. );
  8. } );

PHPからエディタをレンダリングすることは、不要なJSグローバルを作成せずに可能です。これについての例は、GutenbergプラグインのEdit Siteパッケージをチェックしてください。

コンポーネントのレビュー

上記のコードで使用され、付随するプラグインsrc/editor.jsに存在する<Editor>コンポーネントを詳しく見てみましょう。

その名前にもかかわらず、これは実際のブロックエディタのコアではありません。むしろ、カスタムエディタの主要なボディを形成するコンポーネントを含むラッパーコンポーネントです。

依存関係

  1. ``````bash
  2. // File: src/editor.js
  3. import Notices from 'components/notices';
  4. import Header from 'components/header';
  5. import Sidebar from 'components/sidebar';
  6. import BlockEditor from 'components/block-editor';
  7. `

これらの中で最も重要なのは、内部コンポーネントBlockEditorSidebarであり、これらはすぐに説明されます。

残りのコンポーネントは、主にエディタのレイアウトと周囲のユーザーインターフェース(UI)を形成する静的要素で構成されています。これらの要素には、ヘッダーや通知エリアなどが含まれます。

エディタのレンダリング

これらのコンポーネントが利用可能になったので、<Editor>コンポーネントを定義できます。

  1. // File: src/editor.js
  2. function Editor( { settings } ) {
  3. return (
  4. <DropZoneProvider>
  5. <div className="getdavesbe-block-editor-layout">
  6. <Notices />
  7. <Header />
  8. <Sidebar />
  9. <BlockEditor settings={ settings } />
  10. </div>
  11. </DropZoneProvider>
  12. );
  13. }

このプロセスでは、エディタのレイアウトのコアがスキャフォールドされ、特定の機能をコンポーネント階層全体で利用可能にするいくつかの特殊なコンテキストプロバイダーが作成されます。

これらを詳しく見てみましょう:

  • <DropZoneProvider>ドラッグアンドドロップ機能のためのドロップゾーンを使用可能にします
  • <Notices>core/noticesストアにメッセージが送信された場合にレンダリングされる「スナックバー」通知を提供します
  • <Header> – エディタUIの上部に「スタンドアロンブロックエディタ」という静的タイトルをレンダリングします
  • <BlockEditor> – カスタムブロックエディタコンポーネント

キーボードナビゲーション

この基本的なコンポーネント構造が整ったので、残る唯一のことは、レイアウト内の異なる「領域」間でキーボードナビゲーションを提供するために、navigateRegions HOCで全てをラップすることです。

  1. // File: src/editor.js
  2. export default navigateRegions( Editor );

カスタム

今やコアレイアウトとコンポーネントが整いました。ブロックエディタ自体のカスタム実装を探求する時が来ました。

このコンポーネントは<BlockEditor>と呼ばれ、ここで魔法が起こります。

  1. ``````bash
  2. // File: src/components/block-editor/index.js
  3. return (
  4. <div className="getdavesbe-block-editor">
  5. <BlockEditorProvider
  6. value={ blocks }
  7. onInput={ updateBlocks }
  8. onChange={ persistBlocks }
  9. settings={ settings }
  10. >
  11. <Sidebar.InspectorFill>
  12. <BlockInspector />
  13. </Sidebar.InspectorFill>
  14. <BlockCanvas height="400px" />
  15. </BlockEditorProvider>
  16. </div>
  17. );
  18. `

重要なコンポーネントは<BlockEditorProvider><BlockList>です。これらを詳しく見てみましょう。

コンポーネントの理解

<BlockEditorProvider>は階層内で最も重要なコンポーネントの1つです。新しいブロックエディタのための新しいブロック編集コンテキストを確立します。

その結果、これはこのプロジェクトの全体的な目標にとって基本的です。

  1. ``````bash
  2. // File: src/components/block-editor/index.js
  3. <BlockEditorProvider
  4. value={ blocks } // Array of block objects
  5. onInput={ updateBlocks } // Handler to manage Block updates
  6. onChange={ persistBlocks } // Handler to manage Block updates/persistence
  7. settings={ settings } // Editor "settings" object
  8. />
  9. `

BlockEditorプロパティ

  1. 内部的には、`````registry`````([`````withRegistryProvider````` HOC](https://github.com/WordPress/gutenberg/blob/e38dbe958c04d8089695eb686d4f5caff2707505/packages/block-editor/src/components/provider/index.js#L158)を介して)にサブスクライブすることによってこれを行い、ブロック変更イベントをリッスンし、ブロックの変更が永続的であったかどうかを判断し、その後、適切な`````onChange|Input`````ハンドラを呼び出します。
  2. このシンプルなプロジェクトの目的のために、これらの機能により、次のことが可能になります:
  3. - 現在のブロックの配列を`````blocks`````として状態に保存します。
  4. - `````blocks`````を呼び出すことによって`````onInput`````の状態をメモリ内で更新します。
  5. - `````localStorage`````にブロックの基本的な永続性を`````onChange`````を使用して処理します。これは[ブロックの更新が「コミットされた」と見なされるときに発火します](https://github.com/WordPress/gutenberg/tree/HEAD/packages/block-editor/src/components/provider#onchange)。
  6. また、コンポーネントが`````settings`````プロパティを受け入れることを思い出す価値があります。ここに、`````init.php`````内でJSONとしてインライン化されたエディタ設定を追加します。これらの設定を使用して、カスタムカラー、利用可能な画像サイズ、[その他多くの機能](https://github.com/WordPress/gutenberg/tree/4c472c3443513d070a50ba1e96f3a476861447b3/packages/block-editor#SETTINGS_DEFAULTS)を構成できます。
  7. <a name="understanding-the-blocklist-component"></a>
  8. ### <BlockList>コンポーネントの理解
  9. `````<BlockEditorProvider>`````と並んで、次に興味深いコンポーネントは[`````<BlockList>`````](https://github.com/WordPress/gutenberg/blob/e38dbe958c04d8089695eb686d4f5caff2707505/packages/block-editor/src/components/block-list/index.js)です。
  10. これは、**エディタにブロックのリストをレンダリングする**という役割を持つ最も重要なコンポーネントの1つです。
  11. これは、`````<BlockEditorProvider>`````の子として配置されることによって部分的に実現され、エディタ内の現在のブロックの状態に関するすべての情報に完全にアクセスできます。
  12. #### BlockListはどのように機能しますか?
  13. `````<BlockList>`````は、ブロックのリストをレンダリングするために、いくつかの他の低レベルのコンポーネントに依存しています。
  14. これらのコンポーネントの階層は次のように*近似*できます:
  15. ``````bash
  16. // Pseudo code for example purposes only.
  17. <BlockList>
  18. /* renders a list of blocks from the rootClientId. */
  19. <BlockListBlock>
  20. /* renders a single block from the BlockList. */
  21. <BlockEdit>
  22. /* renders the standard editable area of a block. */
  23. <Component /> /* renders the block UI as defined by its `edit()` implementation.
  24. */
  25. </BlockEdit>
  26. </BlockListBlock>
  27. </BlockList>
  28. `

これがブロックのリストをレンダリングするためにどのように機能するかの概要です:

  1. <a name="reviewing-the-sidebar"></a>
  2. ## サイドバーのレビュー
  3. `````<BlockEditor>`````のレンダリング内にも、`````<Sidebar>`````コンポーネントがあります。
  4. ``````bash
  5. // File: src/components/block-editor/index.js
  6. return (
  7. <div className="getdavesbe-block-editor">
  8. <BlockEditorProvider>
  9. <Sidebar.InspectorFill> /* <-- SIDEBAR */
  10. <BlockInspector />
  11. </Sidebar.InspectorFill>
  12. <BlockCanvas height="400px" />
  13. </BlockEditorProvider>
  14. </div>
  15. );
  16. `

これは、<BlockInspector>コンポーネントを介して高度なブロック設定を表示するために部分的に使用されます。

  1. <Sidebar.InspectorFill>
  2. <BlockInspector />
  3. </Sidebar.InspectorFill>

しかし、鋭い目を持つ読者の中には、<Editor>src/editor.js)コンポーネントのレイアウト内に<Sidebar>コンポーネントが存在することにすでに気づいている方もいるでしょう:

  1. // File: src/editor.js
  2. <Notices />
  3. <Header />
  4. <Sidebar /> // <-- What's this?
  5. <BlockEditor settings={ settings } />
  1. Slot/Fillを利用して`````Fill``````````<Sidebar.InspectorFill>`````)を公開し、その後`````<BlockEditor>`````コンポーネント内でインポートされてレンダリングされます(上記を参照)。
  2. これが整ったら、`````<BlockInspector />``````````Sidebar.InspectorFill`````の子としてレンダリングできます。これにより、`````<BlockInspector>``````````<BlockEditorProvider>`````Reactコンテキスト内に保持しつつ、DOM内の別の場所(つまり、`````<Sidebar>`````)にレンダリングできるようになります。
  3. これは過度に複雑に思えるかもしれませんが、`````<BlockInspector>`````が現在のブロックに関する情報にアクセスできるようにするために必要です。Slot/Fillがなければ、このセットアップを達成するのは非常に困難です。
  4. これで、カスタム`````<BlockEditor>`````のレンダリングが完了しました。
  5. [`````<BlockInspector>`````](https://github.com/WordPress/gutenberg/blob/def076809d25e2ad680beda8b9205ab9dea45a0f/packages/block-editor/src/components/block-inspector/index.js)
  6. 自体は、[`````<InspectorControls>`````](https://github.com/WordPress/gutenberg/tree/HEAD/packages/block-editor/src/components/inspector-controls)のために`````Slot`````をレンダリングします。これにより、ブロックの`````edit()`````定義内に`````<InspectorControls>>`````コンポーネントをレンダリングし、エディタのサイドバー内に表示できるようになります。このコンポーネントをさらに詳しく探求することをお勧めします。
  7. <a name="block-persistence"></a>
  8. ## ブロックの永続性
  9. カスタムブロックエディタを作成する旅は長い道のりでした。しかし、触れるべき重要な領域が1つ残っています - ブロックの永続性。言い換えれば、ページのリフレッシュ間でブロックが保存され、利用可能であることです。
  10. ![alt text](https://cdn.hedaai.com/projects/wordpress/c2767c2d0327d2c7063a919c04e1a1a8.gif)
  11. これは単なる*実験*であるため、このガイドではブラウザの`````localStorage````` APIを利用してブロックデータの保存を処理することにしました。実際のシナリオでは、より信頼性が高く堅牢なシステム(例えば、データベース)を選択することになるでしょう。
  12. とはいえ、ブロックを保存する方法を詳しく見てみましょう。
  13. <a name="storing-blocks-in-state"></a>
  14. ### 状態にブロックを保存する
  15. `````src/components/block-editor/index.js`````ファイルを見てみると、ブロックを配列として保存するための状態が作成されていることに気づくでしょう:
  16. ``````bash
  17. // File: src/components/block-editor/index.js
  18. const [ blocks, updateBlocks ] = useState( [] );
  19. `

前述のように、blocksは「制御された」コンポーネント<BlockEditorProvider>にそのvalueプロパティとして渡されます。この「水和」により、初期のブロックセットが与えられます。同様に、updateBlocksセッターはonInputコールバックに接続され、<BlockEditorProvider>内でのブロックの変更と同期されることを保証します。

ブロックデータの保存

今、onChangeハンドラに目を向けると、次のように定義された関数persistBlocks()に接続されていることに気づくでしょう:

  1. // File: src/components/block-editor/index.js
  2. function persistBlocks( newBlocks ) {
  3. updateBlocks( newBlocks );
  4. window.localStorage.setItem( 'getdavesbeBlocks', serialize( newBlocks ) );
  5. }

この関数は、「コミットされた」ブロック変更の配列を受け入れ、状態セッターupdateBlocksを呼び出します。また、ブロックをキーgetdavesbeBlocksの下でLocalStorageに保存します。これを実現するために、ブロックデータはGutenberg「ブロック文法」形式にシリアライズされ、文字列として安全に保存できます。

DeveloperToolsを開いてLocalStorageを検査すると、エディタ内で変更が発生するたびにシリアライズされたブロックデータが保存され、更新されているのがわかります。以下はその形式の例です:

  1. <!-- wp:heading -->
  2. <h2>An experiment with a standalone Block Editor in the WordPress admin</h2>
  3. <!-- /wp:heading -->
  4. <!-- wp:paragraph -->
  5. <p>This is an experiment to discover how easy (or otherwise) it is to create a standalone instance of the Block Editor in the WordPress admin.</p>
  6. <!-- /wp:paragraph -->

以前のブロックデータの取得

永続性が整っているのは良いことですが、そのデータが取得され、ページの完全なリロード時にエディタ内で復元される場合にのみ役立ちます。

データにアクセスすることは副作用であるため、これを処理するためにuseEffectフックを使用する必要があります。

  1. // File: src/components/block-editor/index.js
  2. useEffect( () => {
  3. const storedBlocks = window.localStorage.getItem( 'getdavesbeBlocks' );
  4. if ( storedBlocks && storedBlocks.length ) {
  5. updateBlocks( () => parse( storedBlocks ) );
  6. createInfoNotice( 'Blocks loaded', {
  7. type: 'snackbar',
  8. isDismissible: true,
  9. } );
  10. }
  11. }, [] );

このハンドラは:

  • ローカルストレージからシリアライズされたブロックデータを取得します。
  • シリアライズされたブロックをparse()ユーティリティを使用してJavaScriptオブジェクトに戻します。
  • 状態セッターupdateBlocksを呼び出し、blocksの値をLocalStorageから取得したブロックを反映するように更新します。

これらの操作の結果、制御された<BlockEditorProvider>コンポーネントがLocalStorageから復元されたブロックで更新され、エディタがこれらのブロックを表示します。

最後に、ブロックが復元されたことを示す通知を生成したいと思います。これは「スナックバー」通知として<Notice>コンポーネントに表示されます。

まとめ

このガイドを完了したことをおめでとうございます。これで、ブロックエディタがどのように機能するかについての理解が深まったはずです。

あなたが構築したカスタムブロックエディタの完全なコードはGitHubで入手可能です。ダウンロードして自分で試してみてください。実験して、さらに進めてみてください。