
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
-
Lo primero es tener el servidor de WordPress funcionando, en el curso de antes yo utilizo
Laragon
-
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ónROOT
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 archivophp
con el mismo nombrekw-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
/**
* 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
/**
* 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
/**
* 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
/**
* 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
{
"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:
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
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
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 bloqueicon
: 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 Gutenbergattributes
: 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)
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
ensave
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)
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
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
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
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
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 elimgur.js
-
Le decimos que vincule
react
yReact
, yreact-dom
yReactDOM
, para que así cualquier librería que utilicemos y espere encontrarReact
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
{
"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
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
Pero en escritorio tenemos un ancho de columna que está definido por encima de la aplicación de React
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
🙋♂️
Lista de correo: escribo algo de tanto en cuanto