hand Inicio
hand JSBloqs
hand GutenBloqs
Wallpaper

Plugin para Gutenberg con React

El desarrollo con React en WordPress es un poco lioso al tener que combinar dos entornos de trabajo en uno, pero una vez te acostumbras lo cierto es que la experiencia resulta muy agradable

Aquí vamos a verlo con un plugin para programar un bloque de Gutenberg

UPDATE 2020.10: Reescrito y corregidas las dependencias

Prólogo

WordPress ya ha introducido React en el frontend, y de momento sigue con su backend en php

Sin embargo, este React parece que está solo disponible para el editor Gutenberg, es decir solo para el frontend de la página de administración de WordPress

Y si bien en un principio esto es cierto, cuesta muy poco mover ese React también en todo frontend

Esto lo vemos aquí mientras creamos un plugin que nos defina un bloque de Gutenberg y que tienes en gutenbloqs

Vamos allá

Preparando el entorno de trabajo

Entorno de trabajo

Por el lado de WordPress ya lo tenemos todo listo si lo hemos instalado en local (tienes el curso aquí)

Por el lado de React también lo tenemos todo listo si hemos instalado Node y demás (tienes el curso aquí)

Flujo de trabajo

Por flujo de trabajo me refiero a cómo funcionará un desarrollo de este tipo, porque al mezclar dos tecnologías es algo peculiar

Y es que necesitamos tener a dos tecnologías funcionando, el backend de WordPress, y el frontend de Node

  1. Lo primero es tener el servidor de WordPress funcionando, en el curso de antes yo utilizo Laragon

  2. Lo segundo es tener Node corriendo, que lo que nos hará es transformar nuestro código JavaScript a código JavaScript "antiguo"

Si WordPress se reescribiera en Node sólo necesitaríamos correr esta tecnología para el desarrollo

Y si en lugar de escribir en JavaScript moderno lo hiciésemos en una versión antigua, en verdad tampoco necesitaríamos Node

Pero como la abuela no fuma, vamos con el plan habitual

Creando el plugin, parte php

Creando el plugin

Lo primero que tenemos que hacer es configurar un plugin en WP

Para ello necesitamos saber dónde está la carpeta de plugins de nuestra instalación WordPress

  • En laragon tenemos que apretar el botón ROOT y se nos abrirá el explorador de archivos en la carpeta donde laragon almacena las instalaciones WP

  • Allí nos vamos a la carpeta /wp-content/plugins y creamos una carpeta que yo le diré kw-gutenberg-imgur y dentro un archivo php con el mismo nombre kw-gutenberg-imgur.php

Lo suyo será abrir la carpeta que acabas de crear con vscode

Abrimos ese archivo con vscode y añadimos lo siguiente

php
<?php

/**
* Plugin Name: KW GUTENBERG Imgur
* Description: Fetching imgur videos
* Version: 0.0.1
* Author: KUWowrking
* Author URI: https://www.gutenbloqs.com
*/

Con esto ya tenemos el plugin configurado y si nos vamos al dashboard de WP ya lo veremos listado y listo para ser activado

Ahora simplemente vamos a incrustar nuestro JavaScript

php
<?php

/**
* Plugin Name: KW GUTENBERG Imgur
* Description: Fetching imgur videos
* Version: 0.0.1
* Author: KUWowrking
* Author URI: https://www.kuworking.com
*/


add_action( 'enqueue_block_editor_assets', 'kw_gutenberg_imgur_enqueue' );
function kw_gutenberg_imgur_enqueue() {
wp_enqueue_script(
'kw-gutenberg-imgur-gutenberg',
plugins_url( 'kw-gutenberg-imgur.js', __FILE__ )
);
}

Con add_action le decimos a WordPress que ejecute la función kw_gutenberg_imgur_enqueue cuando esté preparando la interfaz de Gutenberg, esto es, en el hook enqueue_block_editor_assets

Y la función nos inserta el script con wp_enqueue_script que nos pide

  • Un nombre único para el script
  • La ruta del script (aquí la función plugins_url nos devuelve la ruta de los plugins de forma más cómoda)

Añadiendo React

Ahora ya tenemos nuestro JavaScript incrustado (aunque aún no hemos definido el archivo)

Pero aquí WordPress nos permite proporcionar ciertas librerias a este archivo, y una de estas librerías es React, que para WordPress se llama wp-element

php
<?php
/**
* Plugin Name: KW GUTENBERG Imgur
*/


add_action( 'enqueue_block_editor_assets', 'kw_gutenberg_imgur_enqueue' );
function kw_gutenberg_imgur_enqueue() {
wp_enqueue_script(
'kw-gutenberg-imgur-gutenberg',
plugins_url( 'kw-gutenberg-imgur.js', __FILE__ ),
['wp-blocks', 'wp-element']
);
}

Le hemos añadido un array de dependencias que contiene wp-blocks y wp-element, el último es React en versión WordPress

Y ahora sólo nos faltaría crear el archivo kw-gutenberg-imgur.js

Pero antes, vamos a cambiar el código de arriba para utilizar wp-scripts, un conjunto de utilidades que WordPress nos da para facilitarnos la vida

Aquí esto nos servirá para automatizar las dependencias que queramos pasarle al script, y también para añadir un número de versión que cambiará cuando toque (y evitará que tengamos problemas de caché)

Y aprovecho y también le cambio el nombre del archivo de JavaScript, de kw-gutenberg-imgur.js a build/index.js

php
<?php
/**
* Plugin Name: KW GUTENBERG Imgur
* ...
*/


add_action('enqueue_block_editor_assets', 'kw_gutenberg_imgur_enqueue');
function kw_gutenberg_imgur_enqueue()
{
$asset_file = include(plugin_dir_path(__FILE__) . 'build/index.asset.php');

wp_enqueue_script(
'kw-gutenberg-imgur-gutenberg',
plugins_url('build/index.js', __FILE__),
$asset_file['dependencies'],
$asset_file['version']
);
}

El cambio de nombre del archivo es porque trabajaremos con un archivo original, y luego Node nos compilará la versión "antigua", y esta versión le diremos que la guarde en la carpeta build y con el nombre index.js

De momento, con esto ya tenemos la parte php lista

Creando el plugin, parte JS con NodeJS

Ahora que ya tenemos React medio incrustado en nuestro script, seguimos

Para la instalación y puesta a punto del entorno de Node tienes este curso

Para escribir nuestro script de JavaScript necesitamos hacer lo habitual, y esto empieza por definir el archivo package.json donde le diremos las librerías que necesitamos para nuestro desarrollo

Este archivo lo ponemos en el directorio del plugin, que en mi caso es kuworking/wp-content/plugins/kw-gutenberg-imgur/ y allí creo el archivo package.json con el siguiente contenido

js
{
"name": "kw-gutenberg-imgur",
"version": "0.0.1",
"main": "index.js",
"description": "kuworking",
"author": "kuworking.com",
"license": "ISC",
"scripts": {
"start": "wp-scripts start",
"build": "wp-scripts build"
}
}

Aquí aún no hemos especificado ninguna dependencia, lo podríamos hacer manualmente, o bien utilizanso npm o yarn

A mi me gusta utilizar el terminal que incluye vscode, y en el mismo directorio donde está el package.json correr lo siguiente:

bash
yarn add --dev --exact @wordpress/scripts
yarn add --exact styled-components
yarn add --dev --exact @babel/core @babel/plugin-proposal-class-properties @wordpress/babel-preset-default @wordpress/scripts babel-eslint babel-plugin-styled-components eslint eslint-config-prettier eslint-config-standard eslint-plugin-import eslint-plugin-node eslint-plugin-prettier eslint-plugin-promise eslint-plugin-react eslint-plugin-standard file-loader prettier

Todos los paquetes los instalaremos en el entorno de desarrollo --dev excepto la librería styled-components, que la necesitaremos en la aplicación en sí

Todas las librerías que sólo utilicemos durante el desarrollo van a --dev, y todas las que necesitemos en el frontend van sin el --dev

Éste es el caso de styled-components que será nuestra manera de estilar los componentes web

Como curiosidad verás que no estoy instalando React ni Webpack (este último es el responsable de compilar el código), esto es así porque WordPress ya los está utilizando y por lo tanto nosotros utilizaremos el mismo que él

Para completar, posiblemente te sea útil incluir estos archivos:

Si planeas subir el código en un repositorio, crea el archivo .gitignore y vuelca el siguiente contenido para evitar sincronizar estos archivos / carpetas

git
node_modules.cache
public
yarn.lock
yarn - error.log

Y para vscode, aparte de las extensiones que ya tienes instaladas del curso del entorno de trabajo, añade las propias de php

  • Format HTML in PHP
  • PHP Debug
  • PHP Extension Pack
  • PHP Intelephense (y aquí en settings añade en stubs el stub wordpress)
  • PHP IntelliSense
  • PHP Sniffer & Beautifier

Desarrollando el bloque de Gutenberg

Y ahora, una vez ya tenemos toda la infraestructura necesaria, ya podemos entrar en materia

Registrando el bloque

En el código de antes apuntábamos al archivo /build/index.js

Este archivo nos lo fabricará webpack a partir del archivo /src/index.js, que será nuestro archivo (en el que escribiremos)

Entonces, en el directorio del plugin creamos la carpeta src y dentro de esta creamos el archivo /src/index.js y volcamos lo siguiente

js
import { registerBlockType } from '@wordpress/blocks'
import { Fragment } from '@wordpress/element'
import styled from 'styled-components'

registerBlockType('kw-gutenberg-imgur/imgur', {
title: 'Imgur Block',
icon: 'format-video',
category: 'widgets',
attributes: {
content: {
type: 'string',
},
},
edit: ({ attributes, setAttributes, className }) => {
const onChangeContent = newContent => setAttributes({ content: newContent.target.value })
return (
<Input placeholder="javascript" className={className} value={attributes.content} onChange={onChangeContent} />
)
},
save: ({ attributes }) => {
return (
<Fragment>
<div id="kw_gutenberg_imgur"></div>
<script type="text/javascript">
{`var kw_gutenberg_imgur = {attributes: ${JSON.stringify(attributes)}};`}
</script>
</Fragment>
)
},
})

const Input = styled.input`
background: #fff;
border-radius: 8px;
border: 1px solid #ccc;
padding: 15px;
`

Primero importamos registerBlockType que nos servirá para registrar un nuevo bloque en Gutenberg

Después importamos React.Fragment, pero en lugar de hacerlo desde react lo hacemos desde el react que WordPress nos ofrece (que a efectos prácticos es el mismo)

Luego importamos el styled de styled-components

Y luego pasamos ya a la función para registrar el bloque registerBlockType, una función que nos pide 2 argumentos:

  • un nombre
  • un objeto

Para el nombre utiliza el nombre de tu plugin para evitar colisiones con otros plugins

Y en el objeto es donde hay la chicha, con las siguientes propiedades

  • title: el nombre de bloque
  • icon: puedo escoger entre unos básicos (que es lo que hago ahora para salir del paso) o utilizar un archivo aparte (que sería lo suyo)
  • category: te permite posicionar tu bloque en la categoría que quieras del editor Gutenberg
  • attributes: los datos de tu bloque, el estado que modificarás con el contenido que añada el usuario

Attributes

Esta propiedad es de las importantes

Aquí lo defino como un objeto que tiene asimismo la propiedad content, y esa propiedad (le podría haber puesto el nombre que quisiese) le digo que será sí o sí del type: 'string'

Lo que pongamos en attributes puede ser lo que he puesto arriba, que es simplemente definir contents y añadirle una validación que es opcional

O puede ser algo más sofisticado, como directamente guardar el contenido que se escriba en un elemento particular

Digo sofisticado porque esa gestión del contenido ya nos la estaría haciendo Gutenberg, pero aquí lo haremos nosotros mismos

(puedes ver las posibilidades en la documentación oficial)

Edit y Save

Siguiendo con las propiedades del objeto en registerBlockType, después de los attributes tenemos las dos opciones troncales edit y el save

El edit se refiere a lo que veremos en el editor

El save se refiere a lo que veremos en el frontend

Y aquí viene un gran qué

En el edit tenemos a nuestra disposición React, por lo que si queremos utilizar styled-components podemos hacerlo sin problema

Pero en el save NO tenemos React, con lo que si utilizamos cualquier cosa que trabaje sobre React no nos va a funcionar

WordPress nos da React sólo en el frontend, pero sólo en el frontend del backend 😵, es decir en el frontend de las páginas que vemos como administrador

Nos ocupamos del save en unos momentos, para el edit necesitamos una entrada de datos para que el usuario escriba los tags de las imágenes y vídeos que quiera ver

Esto lo hacemos poniendo un <input> que estilamos con styled-components, el cual gestionamos con un onChange que lo que hace es actualizar un estado

(repito el código anterior)

js
import { registerBlockType } from '@wordpress/blocks'
import { Fragment } from '@wordpress/element'
import styled from 'styled-components'

registerBlockType('kw-gutenberg-imgur/imgur', {
edit: ({ attributes, setAttributes, className }) => {
const onChangeContent = newContent => setAttributes({ content: newContent.target.value })
return (
<Input placeholder="javascript" className={className} value={attributes.content} onChange={onChangeContent} />
)
},
})

const Input = styled.input`
background: #fff;
border-radius: 8px;
border: 1px solid #ccc;
padding: 15px;
`

Pero de qué estado estamos hablando, si no hemos utilizado ningún useState?

No nos hace falta, recibimos el estado en los argumentos de la función edit, donde lo vemos con attributes y setAttributes

*El className aquí no nos sirve para nada, pero tampoco nos hace daño ponerlo

Añadiendo un ancla para React en save

Y ahora sí, vamos con el save donde te decía que aunque pueda parecer que sí, en save NO tenemos acceso a React

Si pruebas a utilizar algún componente con styled-components en save verás que no te funciona

Pero que WordPress no nos dé acceso de forma directa no quiere decir que no lo podamos utilizar, sólo que será un tanto más indirecto

Cómo?

Añadiendo un elemento que luego utilizaremos para renderizar nuestra aplicación React, una ancla

Pero también necesitamos poder comunicarnos con la aplicación, en concreto necesitaremos que esa aplicación sepa qué contenido ha puesto nuestro usuario en nuestro bloque de Gutenberg

Para hacerlo crearemos una variable global en JavaScript

Pero no podemos crearla allí mismo porque el frontend no la verá (es otra página, no se comparte información entre páginas)

Necesitamos crear el código que creará la variable en el mismo frontend, y eso lo hacemos con un tag de <script>, y pasando el objeto attributes en formato JSON

Podríamos hacerlo de otras maneras, pero esta me parece la más sencilla y directa

(repito el código anterior)

js
import { registerBlockType } from '@wordpress/blocks'
import { Fragment } from '@wordpress/element'
import styled from 'styled-components'

registerBlockType('kw-gutenberg-imgur/imgur', {
save: ({ attributes }) => {
return (
<Fragment>
<div id="kw_gutenberg_imgur"></div>
<script type="text/javascript">
{`var kw_gutenberg_imgur = {attributes: ${JSON.stringify(attributes)}};`}
</script>
</Fragment>
)
},
})

El ancla es el elemento con id="kw_gutenberg_imgur" y el <script> genera una variable global que es un objeto que contiene los datos guardados del usuario en forma de string, que creamos con JSON.stringify

Pero qué nos falta? Nos falta que en el frontend (esto es, la página que estamos editando con Gutenberg y que tendrá lo que acabamos de escribir en save) tengamos en efecto acceso a React

Añadiendo React en el frontend

Para tener acceso a React en el frontend necesitamos insertar la librería

Lo hacemos en el archivo del plugin kw-gutenberg-imgur.php, al cual le añadimos lo siguiente

php
add_action('enqueue_block_editor_assets', 'kw_gutenberg_imgur_enqueue');
function kw_gutenberg_imgur_enqueue()
{
$asset_file = include(plugin_dir_path(__FILE__) . 'build/index.asset.php');

wp_enqueue_script(
'kw-gutenberg-imgur-gutenberg',
plugins_url('build/index.js', __FILE__),
$asset_file['dependencies'],
$asset_file['version']
);
}

add_action('wp_enqueue_scripts', 'react_enqueue');
function react_enqueue()
{
$asset_file = include(plugin_dir_path(__FILE__) . 'build/imgur.asset.php');

wp_enqueue_script(
'kw-gutenberg-imgur-frontend',
plugins_url('build/imgur.js', __FILE__),
$asset_file['dependencies'],
$asset_file['version'],
true
);
}

Es decir, antes habíamos añadido el script con el hook enqueue_block_editor_assets, que nos estaba añadiendo un bloque de Gutenberg

Ahora, aparte de esto también estamos añadiendo un script con wp_enqueue_scripts que lo veremos en el frontend

Estamos apuntando build/imgur.js que como antes se corresponderá a su versión no compilada src/imgur.js

Por lo tanto, creamos este archivo src/imgur.js y añadimos lo siguiente

js
const { render } = wp.element
import { Imgur } from './components/imgur'

render(<Imgur />, document.getElementById(`kw_gutenberg_imgur`))

Y con esto estamos consiguiendo tener acceso a React también en el frontend

Estamos utilizando el componente <Imgur>, por lo que creamos la carpeta components y allí creamos el archivo ./components/imgur.js y le volcamos lo siguiente

js
const { useState } = wp.element
import styled from 'styled-components'

export const Imgur = () => {
const tags = window.kw_gutenberg_imgur && window.kw_gutenberg_imgur.attributes.content

return (
<div>
<h2>Imgur tag: #{tags}</h2>
</div>
)
}

Y con esto ya tenemos el todo el esqueleto completo del bloque Gutenberg 🙌🙌🙌

Extendiendo WebPack

Pero nos falta una última cosa

Webpack lo que hace es coger todo el código moderno de JavaScript y generar un único archivo con código JavaScript más común

Pero lo que queremos es que

  • Transforme /src/index.js a /build/index.js
  • Transforme /src/imgur.js a /build/imgur.js

Esto lo podemos especificar extendiendo el webpack que nos viene con wp-scripts

Tan fácil como irnos a la raíz de la carpeta (donde tenemos package.json) y creamos el archivo webpack.config.js y allí importamos el archivo que ya nos da wp-script y lo complementamos de la siguiente manera

js
const path = require('path')
const defaults = require('@wordpress/scripts/config/webpack.config')

module.exports = {
...defaults,
entry: {
index: path.resolve(process.cwd(), 'src', 'index.js'),
imgur: path.resolve(process.cwd(), 'src', 'imgur.js'),
},
externals: {
react: 'React',
'react-dom': 'ReactDOM',
},
}
}

Aquí hacemos lo siguiente

  • Partimos de la configuración base de webpack de WordPress, que la tienes aquí

  • Le decimos que nos compile dos archivos, el index.js y el imgur.js

  • Le decimos que vincule react y React, y react-dom y ReactDOM, para que así cualquier librería que utilicemos y espere encontrar React pueda llegar al React que nos da WordPress

Si ojeas el archivo de WordPress verás que en los archivos .js utiliza babel-loader, esto lo que hace es delegarlo todo a la configuración de Babel, por lo que vamos a crear el archivo .babelrc y allí volcamos lo siguiente

js
{
"presets": ["@wordpress/babel-preset-default"],
"plugins": ["babel-plugin-styled-components", "@babel/plugin-proposal-class-properties"]
}

Aquí utilizamos la configuración por defecto de WordPress y añadimos el plugin de styled-components que lo tienes aquí (y la documentación para babel la tienes aquí)

Y ahora ya sí, ya tenemos el esqueleto básico para poder desarrollar un bloque de Gutenberg con React tanto en el editor como en el frontend 👏👏

Preparando el bloque Imgur

Ahora que ya tenemos la estructura preparada, ahora sólo nos falta implementar la funcionalidad que queremos

El objetivo era

  • El usuario escoge el bloque y añade los tags que quiera, separados por una coma
  • El bloque cuando se ejecuta en el frontend muestra una lista de las imágenes/videos de Imgur más recientes con esos tags

Imgur nos ofrece esa posibilidad mediante una API aunque nos pide que nos registremos (aquí)

Seguimos las indicaciones (que encuentras aquí) y tendremos nuestro CLIENT ID y CLIENT SECRET, a mi me ha llevado menos de 1 minuto

Para acceder tendremos que configurar el funcionamiento para la autentificación, y esto sucede en 2 fases

  • El primero para pedir un token
  • El segundo para con ese token hacer la petición

Pero afortunadamente, si sólo queremos mostrar imágenes sólo necesitamos identificarnos con el CLIENT ID, lo cual simplifica enormemente el desarrollo

Este desarrollo ya es puramente con React

Entonces, pedimos una galería de la siguiente manera

js
const { useEffect, useState } = wp.element
import styled from 'styled-components'

export const Imgur = () => {
const tags = window.kw_gutenberg_imgur && window.kw_gutenberg_imgur.attributes.content
const client_id = 'pon_tu_código_aquí'
const url = `https://api.imgur.com/3/gallery/search/viral/week/?q=${tags}`
const request = {
crossDomain: true,
referrerPolicy: 'origin-when-cross-origin',
method: 'GET',
cache: 'no-cache',
mode: 'cors',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
Authorization: `Client-ID ${client_id}`,
},
}

const [data, setData] = useState([]) // aquí almacenaremos lo que nos mande Imgur

useEffect(() => {
/**
/* Necesitamos una función anónima
/* para poder tener un bloque con `async`
*/

;(async () => {
const responseRaw = await fetch(url, request)
const response = await responseRaw.json()
setData(response.data)
})()
}, [])

/**
/* Display decidirá si inserta una imagen o un video
*/

const Display = ({ url }) =>
url.endsWith('mp4') ? (
<video width="100px" controls muted autoplay="true" preload="none">
<source src={url} type="video/mp4" />
Nope
</video>
) : (
<img src={url} loading="lazy" alt="imgur image" />
)

/**
/* Utilizamos el array de datos que nos devuelve imgur para renderizarlo todo
*/

return (
<div>
<h2>Imgur tag: #{tags}</h2>
<Grid>
{data.map(el => (
<Display url={el.images ? el.images[0].link : el.link} />
))}
</Grid>
</div>
)
}

/**
/* Componente para definir un grid sin florituras
*/

const Grid = styled.div`
display: grid;
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
grid-gap: 5px;
& > div {
border-radius: 8px;
}
& img {
width: 100%;
}
`

En definitiva lo que estamos haciendo es

  • Pedir las imágenes a imgur con nuestro CLIENT_ID
  • Lo hacemos una única vez con useEffect
  • Lo hacemos con una función anónima para que pueda ser async y así esperar a tener cada respuesta antes de ir al siguiente paso
  • Almacenamos los datos recibidos con useState
  • Y así en cuando se actualiza este estado, automáticamente se re-renderiza el tema y se muestran las imágenes o vídeos de imgur

El código dentro del componente <Display> es solamente para averiguar si lo que nos da imgur es una imagen o un vídeo

Y luego tenemos el componente Grid que simplemente define un css-grid básico

El useEffect tal y como está arriba, al tener funciones que no son inmediatas (los fetch), si el usuario se va de la página antes de que estos hayan acabado, este código intentará seguir y nos dará un warning por posibles fugas de memoria

Lo suyo sería evitarlo (no está incluido en el código)

Hay mucho margen de mejoras

Por ejemplo se puede implementar un lazy loading de las imágenes, que esto a nivel de navegadores modernos lo podemos hacer con loading="lazy", pero a nivel de video necesitaríamos buscar alguna librería o hacerlo nosotros mismos (yo utilizo la librería react-intersection-observer)

Pero esto ya se escapa de lo que quería enseñarte en este curso

Y evidentemente también tenemos la estética, que tal y como está ahora presenta un problema:

En móvil se ve bien

imagen

Pero en escritorio tenemos un ancho de columna que está definido por encima de la aplicación de React

imagen

Cómo podemos modificar ese ancho para que nos ocupe una mayor parte del espacio disponible?

Pues hay que ser creativos con el css

Y es que al final el plugin siempre dependerá de las constricciones del theme, por lo que normalmente estos cambios deberán hacerse a nivel de theme, aunque como digo el css es muy aplio y aquí podríamos utilizar un position: absolute para sortear las restricciones del theme

Comentario

Ya has visto cómo desarrollar un bloque de Gutenberg con React y JavaScript moderno

Las ventajas de hacer esto son básicamente las de poder prescindir del ecosistema de desarrollo tradicional de WordPress para frontend y disfrutar de un desarrollo en React con toda su enorme comunidad a tu alcance

🙋‍♂️

draw of me

Hola, tienes en mente desarrollar una web?

Si quieres, te ayudo

11ty para webs ultra-rápidaseleventy js
Gatsby para webs complejasgatsby js
WordPress para webs para el usuario finalwordpress

Escríbeme

Lista de correo: escribo algo de tanto en cuanto