19 ноября 2018 года выходит WordPress 5. Этот основной выпуск полностью меняет интерфейс для написания страниц и сообщений в блогах. Редактор TinyMCE заменяется блоками - собственным конструктором страниц WordPress, если хотите. Его называют редактором Гутенберга.
Гутенберг многое изменит. Сторонние конструкторы страниц, которые существовали годами, могут больше не работать или могут стать ненужными. Гутенберг является спорным и важным объектом для WordPress. Вы можете узнать больше о Гутенберге и его последствиях в этом посте от Delicious Brains.
Гутенберг тоже написан на React. Это одновременно и благословение, и проклятие для разработчиков. С одной стороны, это новая парадигма, которую нужно изучить и принять для разработчиков плагинов. С другой стороны, React съедает фронтенд-разработку. Так что это возможность узнать это.
В этом посте я расскажу, как я адаптировал Плагин Amilia Store для поддержки шорткодов и блоков с минимальными изменениями кода. Полный код плагина доступен в SVN репозитории плагинов WordPress.
Шорткод
Плагин Amilia Store добавляет дополнительные кнопки на панель инструментов TinyMCE. Эти кнопки используются для вставки шорткодов. На опубликованной странице шорткоды отображают HTML на внутренней стороне PHP. Например, шорткод Amilia Store используется для встраивания магазина клиентов на их веб-сайт. В редакторе кнопка TinyMCE открывает всплывающее окно для настройки встраивания. На опубликованной странице мы видим магазин, встроенный в iframe.
С Gutenberg синие кнопки Amilia в TinyMCE исчезнут. Их придется заменить на блоки.
Стратегия конверсии
Чтобы не переписывать много кода, я решил применить следующие стратегии:
- Преобразуйте один из шорткодов (встраивание iframe) в качестве доказательства концепции.
- Шорткод iframe станет блоком Embed.
- Всплывающее окно шорткода будет преобразовано в элементы управления инспектора для блока. Это означает, что пользователь будет настраивать iframe с боковой панели. Для этого мне нужно будет написать новый код.
- Разметка iframe по-прежнему будет генерироваться в PHP с использованием динамического блока. Шорткод и блок будут разделять эту функциональность. Нет необходимости переписывать этот код. Это самая большая экономия времени.
- Я буду использовать ES5 и избегать ES6, webpack, babel и JSX. Это сокращает время обучения и домашние задачи, на которые мне нужно было бы потратить время для поддержки транспиляции, сборки и публикации. Я могу сохранить небольшое количество файлов и использовать тот же рабочий процесс, что и раньше (фиксация в SVN).
Стратегии кратко представлены на этой диаграмме:
В итоге я хочу, чтобы мой шорткод и блок Гутенберга сосуществовали и совместно использовали код. Я хочу избежать дублирования кода. В то же время я не хочу, чтобы пользовательский интерфейс страдал. Поэтому мне нужно написать новый код для редактора блоков.
Модификации кода плагина
В существующем коде плагина мало что изменилось. В amilia-store.php мне пришлось добавить дополнительное включение для загрузки кода блока.
... // Shortcodes include "shortcodes/amilia-store-iframe.php"; include "shortcodes/amilia-store-table.php"; include "shortcodes/amilia-store-calendar.php"; include "shortcodes/amilia-store-standings.php"; // Gutenberg blocks include "blocks/amilia-store-iframe.php";
Блок-шаблон
Для регистрации нового блока требуется некоторый шаблон. К счастью, WP-CLI можно использовать для генерации блоков Гутенберга для вас в вашем существующем плагине. Я использовал его, и это сэкономило мне много времени. После некоторой очистки это мой PHP-файл для регистрации блока amilia-store-iframe.php:
<?php function amilia_store_iframe_block_init() { if ( ! function_exists( 'register_block_type' ) ) { return; } $dir = dirname( __FILE__ ); $index_js = 'amilia-store-iframe.js'; wp_register_script( 'amilia-store-iframe-block-editor', plugins_url( $index_js, __FILE__ ), array( 'wp-blocks', 'wp-i18n', 'wp-element', ), filemtime( "$dir/$index_js" ) ); register_block_type( 'amilia-store/amilia-store-iframe', array( 'editor_script' => 'amilia-store-iframe-block-editor', 'attributes' => array( 'url' => array('type' => 'string'), 'color' => array('type' => 'string') ), 'render_callback' => 'amilia_store_iframe_shortcode_handler' ) ); } add_action( 'init', 'amilia_store_iframe_block_init' );
Вызов функции register_block_type
был изменен для указания сохранения attributes
вместе с функцией render_callback
. Это внутренняя функция рендеринга, такая же, как и для шорткода. При вызове ему будут переданы атрибуты. К счастью, функции рендеринга блоков и шорткодов для динамических блоков имеют одинаковую сигнатуру.
Мне также пришлось удалить шаблон, который регистрировал CSS и JS сайта, поскольку обработчик шорткода уже позаботился об этом.
Пользовательский интерфейс редактора блоков
Вот как выглядит редактор:
Пользовательский интерфейс довольно приятный. Мы добавляем блок, а затем можем быстро изменить его атрибуты через боковую панель. Затем мы нажимаем предварительный просмотр, чтобы увидеть, как это выглядит.
Примечание. Вы можете отобразить блок на стороне сервера в редакторе с помощью компонента ServerSideRender.
Блокировать JavaScript
Код блока - это тот, который использует React. В моем случае он написан на традиционном ES5, чтобы избежать необходимости устанавливать веб-пакет и запускать Babel для транспиляции. Код ES5 может интерпретироваться любым современным браузером. Полный код представлен в конце этого раздела для ознакомления.
Внедрение зависимости
В блок вводится переменная wp
, которая содержит все необходимые зависимости. Первые строки блока обычно извлекают эти зависимости и делают их доступными в переменных с ограниченной областью видимости.
(function(wp) { var registerBlockType = wp.blocks.registerBlockType; var InspectorControls = wp.editor.InspectorControls; var PanelBody = wp.components.PanelBody; var TextControl = wp.components.TextControl; var ColorPicker = wp.components.ColorPicker; var ColorPalette = wp.components.ColorPalette; var SelectControl = wp.components.SelectControl; var Dashicon = wp.components.Dashicon; var el = wp.element.createElement; var withState = wp.compose.withState; var __ = wp.i18n.__; ...
Кроме того, блочные компоненты также получают свои зависимости через знаменитый аргумент props
. Вы найдете там attributes
, а также вспомогательный метод setAttributes
для их сохранения.
function AmiliaControl(props) { var attributes = props.attributes; var setAttributes = props.setAttributes; ...
Заблокировать регистрацию
Блок Гутенберга необходимо зарегистрировать с помощью функции registerBlockType
. В этой функции вы передаете attributes
, edit
метод рендеринга и save
метод рендеринга (то, что рендерится на опубликованной странице). Поскольку я использую динамический блок, который отображает разметку сайта с помощью PHP, я возвращаю null
в моем методе save
.
registerBlockType('amilia-store/amilia-store-iframe', { title: __('Amilia Store'), category: 'embed', icon: { foreground: '#46aaf8', src: 'store' }, attributes: { url: { type: 'string', default: null }, color: { type: 'string', default: '#46aaf8' } }, edit: withState({status: ''})(AmiliaControl), save: function(props) { return null; } });
Редактировать компоненты
Метод edit
- это то, где вы составляете пользовательский интерфейс с компонентами React. Вы просто возвращаете список компонентов для отображения на экране. В ES5 вы используете вспомогательную функцию wp.element.createElement
, сокращенную до el
, для создания экземпляра компонента. WordPress поставляется с полной библиотекой компонентов. В моем случае я использовал компоненты TextControl и ColorPalette для настройки своего блока. Я заключил их в компонент InspectorControls, чтобы они отображались на боковой панели. Другие компоненты визуализируются в блоке. Это как-то странно, но вот как это работает.
Компоненты React довольно просты в использовании. Вы передаете им параметры отображения и onChange
обратный вызов для сохранения пользовательских изменений. Очень похоже на традиционные плагины jQuery. Я сам очень повеселился, пробуя разные компоненты. Например, чтобы захватить атрибут цвета, я поиграл с SelectControl, затем с ColorPicker, но остановился на ColorPalette. Именно здесь Гутенберг сияет - предлагая большое количество компонентов для использования разработчиками блоков.
Атрибуты
Атрибуты - это фрагменты, которые сохраняются для блока. В редакторе вы пишете компоненты, которые будут показывать и получать их от пользователя. На опубликованных страницах WordPress передает их обработчику шорткода PHP.
Состояние
WordPress предоставляет удобную вспомогательную функцию withState
. Состояние в React - это информация, которую компонент должен обрабатывать и отображать для бизнес-логики, но которая не сохраняется в атрибутах. Например, в моем плагине я выполняю вызов AJAX для проверки URL-адреса, переданного пользователем. Результат проверки сохраняется в свойстве состояния с именем status
. Другой пример - компонент Popover.
Полный справочник по коду
Файл amilia-store-iframe.js содержит следующее:
(function(wp) { var registerBlockType = wp.blocks.registerBlockType; var InspectorControls = wp.editor.InspectorControls; var PanelBody = wp.components.PanelBody; var TextControl = wp.components.TextControl; var ColorPicker = wp.components.ColorPicker; var ColorPalette = wp.components.ColorPalette; var SelectControl = wp.components.SelectControl; var Dashicon = wp.components.Dashicon; var el = wp.element.createElement; var withState = wp.compose.withState; var __ = wp.i18n.__; function AmiliaControl(props) { var attributes = props.attributes; var setAttributes = props.setAttributes; var setState = props.setState; var status = props.status; var url = attributes.url === null ? window.Amilia.storeUrl : attributes.url; function onValidateUrl(result) { setState({status: result.message}); } if (status === '') setState({status: Amilia.validateStoreUrlString(url, onValidateUrl).message}); var inspectorControl = el(InspectorControls, {}, el('h4', {}, el('span', {}, Amilia.lang('iframe-title'))), el(TextControl, { label: Amilia.lang('url-label'), value: url, onChange: function(value) { setAttributes({url: value}); setState({status: Amilia.validateStoreUrlString(value, onValidateUrl).message}); } }), el('p', {className: 'input-helper'}, status), el('label', {}, Amilia.lang('color')), el(ColorPalette, { color: attributes.color, colors: Object.keys(Amilia.COLORS).map(function(k) { return {name: Amilia.lang(k), color: Amilia.COLORS[k]}; }), onChange: function(value) { setAttributes({color: value}); } }), el(PanelBody, {title: Amilia.lang('help'), initialOpen: false}, el('p', {}, Amilia.lang('instructions-p1')), el('p', {}, Amilia.lang('instructions-p2')), el('p', {}, Amilia.lang('instructions-p3')), el('p', {}, Amilia.lang('instructions-p4')) ) ); return el('div', { className: 'amilia-store-block', style: { backgroundColor: attributes.color, color: Amilia.invertColor(attributes.color) } }, el('img', {src: Amilia.pluginUrl + 'images/amilia-a.svg', className: 'logo'}), el('p', {className: 'strong'}, Amilia.lang('amilia-store')), el('p', {className: 'italic'}, Amilia.lang('block-info')), inspectorControl ); } registerBlockType('amilia-store/amilia-store-iframe', { title: __('Amilia Store'), category: 'embed', icon: { foreground: '#46aaf8', src: 'store' }, attributes: { url: { type: 'string', default: null }, color: { type: 'string', default: '#46aaf8' } }, edit: withState({status: ''})(AmiliaControl), save: function(props) { return null; } }); })(window.wp);
Заключение
Какими бы устрашающими ни были блоки Гутенберга и React, в конце концов все оказалось не так уж и плохо. К счастью, блоки можно писать на ES5 и сразу же работать в любом современном браузере. Блоки также могут совместно использовать функции рендеринга PHP, используемые шорткодами, благодаря динамическим блокам и компоненту ServerSideRender. Эти вещи помогли мне сократить время на адаптацию существующих шорткодов к блокам.
Однако в будущем я планирую перенести весь блочный код на JavaScript и, в частности, на ES6 (и JSX). Для этого потребуется webpack и Babel для транспилирования исходного кода. Кроме того, исходный код, скорее всего, будет размещен на GitHub. Задача webpack потребуется для создания файлов распространения, которые будут зафиксированы в репозитории SVN. Здесь требуется много работы. Однако, как только они появятся, улучшения плагина станут доступны моим коллегам-разработчикам React. Писать PHP больше не нужно. Они будут счастливы.
Спасибо Жереми Брайону, Александру-Хо Латрейю и Даниэлю Тузиньяну за чтение черновика этого сообщения.