Это PHP или JavaScript? А куда этот код добавить? Это нужно добавить в functions.php? Эти и другие вопросы я слышу, когда речь заходит о расширении функциональности Gutenberg. Будь то добавление или удаление стилей блоков, создание новых блоков или модификации интерфейса редактора.Документация для Gutenberg до сих пор находится в процессе написания (WP сообщество даже начало фандрайзинг для ее улучшения) и некоторые моменты тяжелы к восприятию. Поэтому в этой статье я расскажу как с нуля создать плагин для расширения функционала Gutenberg и какие инструменты использовать.Перед тем как мы перейдем к инструментам, хочу сделать небольшую ремарку. Gutenberg по своей сути — React приложение, поэтому понимание основ React, JSX и ESNext необходимо для для работы с редактором. Да, документация приводит примеры в ES5 и ESNext, но большинство инструментов заточено именно под ESNext и JSX. Да и разобраться и понять разницу не так уж и сложно.План действийМы создадим новый плагин для WordPress, настроим компиляцию React/ESNext → Vanilla JavaScript, и научимся:Добавлять стили блоковДобавлять вариации блоковРегистрировать новые блокиИнструментыДля создания плагина нам понадобится несколько инструментов:Локальный сервер для разаработки — я буду использовать VVV, но другие инструменты типа Local или Laravel Valet тоже подойдут.WP-CLI. Интерфейс для управления WordPress из терминала. В VVV и Local WP-CLI установлен из коробки.@wordpress/scripts — пакет скриптов для постройки JS файлов плагина.Создание плагинаДля создания плагина мы воспользуемся командой wp scaffold plugin которая доступна в WP-CLI. Для этого перейдем в папку wp-contet/plugins в терминале и запустим следующую команду:wp scaffold plugin demo --plugin_name="Demo" --plugin_description="Demo plugin for Gutenberg development" --plugin_author="Dmitry Mayorov" --plugin_author_uri="https://dmtrmrv.com" --activateЕсли команда выполнена успешно, в терминале вы увидите вот такое сообщение:Success: Created plugin files. Success: Created test files.… а в админке WordPress появится новый плагин:А вот так выглядит структура файлов:demo ├── .distignore ├── .editorconfig ├── .gitignore ├── .phpcs.xml.dist ├── .travis.yml ├── Gruntfile.js ├── bin │ └── install-wp-tests.sh ├── demo.php ├── package.json ├── phpunit.xml.dist ├── readme.txt └── tests ├── bootstrap.php └── test-sample.phpКак видно из сниппета выше, помимо основного файла плагина, wp scaffold plugin добавляет конфигурационные файлы, скрипты локализации, тестирования и настройки CI/CD. Подробнее о значении этих файлов можно прочитать здесь.Постройка JS файловДальше необходимо настроить скрипты постройки JavaScript файлов. Для этого, находясь в папке плагина установим пакет @wordpress/scripts:npm install @wordpress/scripts --save-devЕсли установка прошла успешно, то в разделе зависимостей в файле package.json помимо других зависимостей вы увидите следующее (версия может отличаться):"devDependencies": { "@wordpress/scripts": "^12.0.0", }После установки @wordpress/scripts необходимо добавить скрипты в package.json файл. Выглядеть это должно примерно так:"scripts": { "build": "wp-scripts build", "check-engines": "wp-scripts check-engines", "check-licenses": "wp-scripts check-licenses", "format:js": "wp-scripts format-js", "lint:css": "wp-scripts lint-style", "lint:js": "wp-scripts lint-js", "lint:md:docs": "wp-scripts lint-md-docs", "lint:md:js": "wp-scripts lint-md-js", "lint:pkg-json": "wp-scripts lint-pkg-json", "packages-update": "wp-scripts packages-update", "start": "wp-scripts start", "test:e2e": "wp-scripts test-e2e", "test:unit": "wp-scripts test-unit-js" }Подробнее о каждом из скриптов можно прочитать здесь.Стоит упомянуть, что команда wp plugin scaffold тоже устанавливает несколько Grunt скриптов по умолчанию, поэтому объект scripts в package.json не будет пустым:"scripts": { "start": "grunt default", "readme": "grunt readme", "i18n": "grunt i18n" }Видно что оба объекта имеют скрипт start. Чтобы избежать конфликта, мы переименуем Grunt скрипт в grunt-start и объединим все скрипты в один объект:"scripts": { "build": "wp-scripts build", "check-engines": "wp-scripts check-engines", "check-licenses": "wp-scripts check-licenses", "format:js": "wp-scripts format-js", "lint:css": "wp-scripts lint-style", "lint:js": "wp-scripts lint-js", "lint:md:docs": "wp-scripts lint-md-docs", "lint:md:js": "wp-scripts lint-md-js", "lint:pkg-json": "wp-scripts lint-pkg-json", "packages-update": "wp-scripts packages-update", "start": "wp-scripts start", "test:e2e": "wp-scripts test-e2e", "test:unit": "wp-scripts test-unit-js", "grunt-start": "grunt default", "readme": "grunt readme", "i18n": "grunt i18n" }Больше всего нас интересуют два скрипта: build - генерирует production версию файловstart - наблюдает за файлами и генерирует development версию файлов если видит изменения.Но куда и какие файлы вставлять чтобы начать работать с редактором? По умолчанию build и start скрипты будут обрабатывать файл src/index.js. Создадим этот файл и поместим в него что-нибудь вроде:console.log('We are live!');Важно чтобы директория src находилась на том же уровне что и package.json:├── package.json ├── src │ └── index.jsВернемся в консоль и запустим build скрипт:npm run buildЕсли скрипт выполнен успешно, то в директории плагина появится новая директория build:├── build │ ├── index.asset.php │ └── index.js ├── package.json ├── src │ └── index.jsДалее необходимо добавить скрипт build/index.js в редактор. Для этого в Gutenberg есть специальный хук: enqueue_block_editor_assets.Откроем файл demo.php и добавим следующий код:<?php /** * Plugin Name: Demo * Description: Demo plugin for Gutenberg development * Author: Dmitry Mayorov * Author URI: https://dmtrmrv.com * Text Domain: demo * Domain Path: /languages * Version: 0.1.0 * * @package Demo */ // Exit if accessed directly. if ( ! defined( 'ABSPATH' ) ) { exit; }; function demo_block_editor_assets(){ wp_enqueue_script( 'demo-editor-script', plugin_dir_url( __FILE__ ) . 'build/index.js', [ 'wp-blocks', 'wp-i18n', 'wp-element', 'wp-editor' ], '1.0.0' ); } add_action( 'enqueue_block_editor_assets', 'demo_block_editor_assets' );И если мы все сделали без ошибок, то в консоли редактора должно появится сообщение We are live!Если вы видите это сообщение, значит можно приступить к финальной части — непосредственно работе с редактором.Добавление стиля блокаЭто пример из официальной документации. В директории src создадим новую директорию styles/quote и поместим в нее новый файл index.js:├── src │ ├── index.js │ └── styles │ └── quote │ └── index.jsСо следующим содержимым:const { registerBlockStyle } = wp.blocks; registerBlockStyle( 'core/quote', { name: 'fancy-quote', label: 'Fancy Quote', } );Затем импортируем этот файл в src/index.js:import './styles/quote';Запустим npm run build и, если нигде нет ошибок, то в админке у блока Quote должен появиться новый стиль:Добавление вариации блокаВариации блоков модифицируют уже существующие блоки, но, в отличие от стилей блоков, позволяют заполнять атрибуты и создавать дочерние блоки. Я подробно описал эту возможность в статье на CSS-Tricks, поэтому не буду вдаваться в детали, a покажу как добавить вариацию с помощью нашего плагина.Я буду использовать тот же пример, что и в вышеупомянутой статье. В директории src создадим новую директорию variations/buttons и поместим в нее новый файл index.js:├── src │ ├── index.js │ ├── styles │ │ └── quote │ │ └── index.js │ └── variations │ └── buttons │ └── index.js… co следующим содержимым:const { registerBlockVariation } = wp.blocks; registerBlockVariation( 'core/buttons', [ { name: 'wide', title: 'Wide Buttons', attributes: { className: 'is-wide' }, }, { name: 'full', title: 'Full Buttons', attributes: { className: 'is-full' }, } ] );Затем импортируем этот файл в src/index.js:import './styles/quote'; import './variations/buttons';Вернемся в консоль и запустим build скрипт:npm run buildПосле его выполнения в админке должны появится две новые вариации:Добавление нового блокаВероятно вы уже догадались, что порядок действий для добавления нового блока примерно такой же. Я не буду подробно описывать, как создать новый блок, на эту тему написано много статей.В качестве примера мы создадим новый блок card состоящий из заголовка и параграфа. Для этого в директории src создадим новую директорию blocks/card и поместим в нее новый файл index.js:├── src │ ├── index.js │ ├── blocks │ │ └── card │ │ └── index.js │ ├── styles │ │ └── quote │ │ └── index.js │ └── variations │ └── buttons │ └── index.js… co следующим содержимым:/** * Card Block */ const { __ } = wp.i18n; const { registerBlockType } = wp.blocks; const { RichText } = wp.blockEditor; registerBlockType('demo/card', { title: __('Card', 'demo'), description: __('Display a card with heading and description', 'demo'), category: 'widgets', attributes: { title: { type: 'string', source: 'html', selector: '.demo-card__title', }, description: { type: 'string', source: 'html', selector: '.demo-card__content', }, }, supports: { className: false, }, edit: (props) => { // Destructure props and attributes. const { attributes: { title, description, }, setAttributes, } = props; return ( <div className="demo-card"> <div className="demo-card__inner"> <RichText allowedFormats={[]} className="demo-card__title" multiline={false} onChange={(value) => setAttributes({ title: value })} placeholder="Add card title" tagName="h2" value={title} /> <RichText allowedFormats={[]} className="demo-card__content" multiline={'p'} onChange={(value) => setAttributes({ description: value })} placeholder="Add card content" tagName="div" value={description} /> </div> </div> ); }, save: (props) => { // Destructure attributes. const { attributes: { title, description, } } = props; return ( <div className="demo-card"> <div className="demo-card__inner"> <h2 className="demo-card__title" dangerouslySetInnerHTML={{ __html: title }} /> <div className="demo-card__content" dangerouslySetInnerHTML={{ __html: description }} /> </div> </div> ); }, });Запустим build скрипт:npm run build.. после его выполнения в админке должен появится новый блок:O структуре файловКак вы возможно заметили, мы организовали разные типы модификаций(стили, вариации, блоки) в разные директории. Если в будущем потребуется добавить, например, новый блок, то он пойдет в директорию blocks:├── src │ ├── blocks │ │ └── card │ │ └── index.js │ │ └── new-block │ │ └── index.jsТакой же порядок для стилей и вариаций. Далее мы просто импортируем их в главный файл src/index.js. Таким образом файлы плагина остаются организованными.Несколько слов о CSSЯ намеренно не стал добавлять в плагин стили, потому что cделать это можно различными способами в зависимости от контекста.Можно поместить CSS файлы относящиеся ка каждому блоку в те же директории где находится блок и настроить webpack конфиг таким образом, что он будет их собирать в один файл. Можно создать один Sass файл и настроить компиляцию в CSS c помощью Gulp или Grunt.Или не делать ничего из вышеперечисленного и написать CSS вручную (да, так тоже можно) если стилей немного.Также нужно понимать куда добавлять стили — на фронтенд, в админку или и туда и туда? Будут это одинаковые стили или нет?Эта статья не про CSS, поэтому я покажу как подключить cтили в редактор и на фронтенд a выбор технологий оставлю за вами. Для добавления стилей мы можем воспользоваться двумя хуками:enqueue_block_editor_assets – используется для добавления скриптов и стилей только в адмику редактораenqueue_block_assets – используется для добавления скриптов и стилей на фронтенд и в адмику редактора.Для примера, добавим стили к блоку из примера выше. Для этого добавим новый файл demo.css в корневую директорию плагина..demo-card { padding: 1.5rem; border: 1px solid #000; }И подключим этот стиль с помощью enqueue_block_assets:function demo_block_assets(){ wp_enqueue_style( 'demo-editor-style', plugin_dir_url( __FILE__ ) . 'demo.css', [], '1.0.0' ); } add_action( 'enqueue_block_assets', 'demo_block_assets' );Таким образом целиком demo.php будет выглядеть примерно вот так:<?php /** * Plugin Name: Demo * Description: Demo plugin for Gutenberg development * Author: Dmitry Mayorov * Author URI: https://dmtrmrv.com * Text Domain: demo * Domain Path: /languages * Version: 0.1.0 * * @package Demo */ // Exit if accessed directly. if ( ! defined( 'ABSPATH' ) ) { exit; }; function demo_block_editor_assets(){ wp_enqueue_script( 'demo-editor-script', plugin_dir_url( __FILE__ ) . 'build/index.js', [ 'wp-blocks', 'wp-i18n', 'wp-element', 'wp-editor' ], '1.0.0' ); } add_action( 'enqueue_block_editor_assets', 'demo_block_editor_assets' ); function demo_block_assets(){ wp_enqueue_style( 'demo-editor-style', plugin_dir_url( __FILE__ ) . 'demo.css', [], '1.0.0' ); } add_action( 'enqueue_block_assets', 'demo_block_assets' );Вместо итогаНадеюсь, что примеры из статьи помогут вам быстрее разобраться с тем как писать расширения для нового редактора WordPress. Для меня ключевым моментом было понять как работают cкрипты из пакета @wordpress/scripts. Остальное было довольно просто благодаря опыту работы с WordPress и React.Если вам интересно протестировать плагин из статьи — исходный код можно найти на Github.А если вы уже писали расширения для Gutenberg и использовали другие инструменты, поделитесь в комментариях.