React, Browserify y Babel en Plugins de WordPress (I)
Al grano. Vamos crear un plugin muy sencillo que utilice React en un menú del wp-admin. El plugin generará una página de administración y cargaremos React ahí mismo. Obviamente lo podríamos usar para crear una interfaz en el front y no sólo en el admin.
Para los más impacientes hay un repositorio donde voy poniendo el código: https://github.com/igmoweb/react-plugin/. Para ver este post en concreto hay que elegir la rama step-1.
Qué necesitamos
- React: Es una famosísima librería para generar interfaces de usuario. No es un todo en uno como Angular, React sólo sirve para crear la interfaz, no contiene nada para modelar datos y demás. No profundizaré mucho en React porque no es el objetivo de este post.
- Browserify: Nos ayudará a generar un único archivo JavaScript que contiene todos los módulos necesarios. A este archivo final se le llama en inglés bundle. Browserify nos permite importar paquetes o módulos desde distintos archivos (véase React entre otros) para luego juntarlos todos en uno sólo, que es el archivo que usaremos en nuestro menú wp-admin.
- Babel: Es un transcompilador de ES6 (ECMAScript 6). Convierte código JavaScript ES6 en código ES5, que es el que entienden todos los navegadores hoy en día. Trabajar en ES6 es más fácil que en ES5, y más divertido.
¿Por qué no Webpack? Te estarás preguntando, amigo informático “listillo”. Pues porque Webpack es complicado, un monstruo, una bestia que hace DE TODO, un Browserify supervitaminado. Antes de meternos en Webpack podemos usar herramientas más sencillas como Browserify porque luego Webpack se entiende mejor.
Aquí un dibujo de cómo funciona más o menos todo a la vez:
Requisitos previos
Tenemos que instalar Node.js en nuestro ordenador. Node incluye un gestor de paquetes llamado npm que nos servirá para gestionar las dependencias de nuestro proyecto. Como nuestro plugin utiliza React y Browserify, éstos se convierten en dependencias del plugin.
El plugin
El plugin que vamos a crear es muy sencillo: Crea un menú de administración en WordPress desde donde cargará el archivo JS que sacará por pantalla el HTML necesario gracias a React. Para esto creamos una nueva carpeta en wp-content/plugins, por ejemplo react-plugin. Ahora creamos el archivo principal del plugin (no va a haber más): react-plugin.php, y empezamos a picar código:
<?php
/**
* Plugin Name: React Plugin
*/
class React_Plugin {
private $page_id;
public function __construct() {
add_action( 'admin_menu', array( $this, 'admin_menu' ) );
}
public function admin_menu() {
$this->page_id = add_menu_page(
'React Plugin',
'React Plugin',
'manage_options',
'react-plugin',
array( $this, 'render_menu' )
);
}
public function render_menu() {
?>
<div class="wrap">
<h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
<div id="app"></div>
</div>
<?php
}
}
new React_Plugin();
Es un plugin bastante tonto: Genera un menú (cuyo ID generado lo guarda como atributo de la clase React_Plugin, ahora veremos porqué) y nada más. El contenido del menú (React Plugin), es un simple div#app. Dentro de este div, React generará el código HTML.
Estado de las carpetas:
-wp-content/plugins/react-plugin * react-plugin.php
Instalación de dependencias
Gracias a npm, vamos a empezar a instalar todas las dependencias para nuestro proyecto. En el terminal, nos situamos en la carpeta de nuestro plugin y escribimos:
npm init -y
Esto nos genera un archivo package.json, un documento que npm necesita para saber qué instalar y qué características tiene el proyecto. Lo vamos a dejar tal cual porque no quiero meterme mucho en él, es todo un mundo.
Instalamos las dependencias:
npm install --save react react-dom npm install --save-dev browserify babelify babel-preset-react babel-preset-es2015
Con esto, veremos que se ha creado una carpeta nueva, node_modules, donde se habrán descargado las siguientes dependencias:
- React y ReactDOM. Éste último sirve para renderizar los componentes de React.
- Browserify
- Babelify: Un plugin de Browserify para transcompilar el código ES6 a ES5. Al final es Babel adaptado a Browserify, nada más
- Babel Preset React y Babel Preset ES2015: Son paquetes de Babel que contienen la configuración necesaria para transcompilar de ES6 (la versión de Junio de 2015) a ES5. Podríamos usar otro paquete como ES2016 o ES2017 si quisiéramos una versión de ES6 más avanzada. El ES2015 es más que suficiente. El Preset de React ayuda a transcompilar el código de React a ES5 también.
Una vez todo instalado, el fichero package.json debería quedar más o menos así:
{
"name": "react-plugin",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"react": "^15.3.2",
"react-dom": "^15.3.2"
},
"devDependencies": {
"babel-preset-es2015": "^6.16.0",
"babel-preset-react": "^6.16.0",
"babelify": "^7.3.0",
"browserify": "^13.1.0"
}
}
Estado de las carpetas:
-wp-content/plugins/react-plugin - node_modules * package.json * react-plugin.php
Estructura de carpetas
Dentro de la carpeta del plugin vamos a crear dos carpetas adicionales:
- src: Aquí vamos a meter todos los archivos JS escritos en ES6 así como los componentes de React.
- build: Con ayuda de Browserify, a partir de todo lo que hay en src + React (que está en node_modules/react), se generará un archivo app.js dentro de este directorio. Dicho archivo será el que cargaremos en nuestro menú de WordPress.
Estado de las carpetas:
-wp-content/plugins/react-plugin - build - node_modules - src * package.json * react-plugin.php
Componentes de React
Vamos a crear el componente principal de nuestro proyecto. Va a ser un simple Hello WordPress
src/App.js
import React from 'react';
export default class App extends React.Component {
render() {
return <div>Hello WordPress</div>;
}
}
src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(<App />, document.getElementById('app') );
Es algo bastante simple: App.js es el elemento principal de la interfaz. Es un simple div con un Hello WordPress dentro. Para crear un componente en React, extendemos simepre de la clase React.Component (en ES5 sería más feo y engorroso). export default significa que cuando queramos importar (con import from) dicho archivo desde otro fichero JS, es la clase App la que se exporta.
index.js es el archivo principal de la aplicación. Lo que hace es importar el componente App (ya sabemos, con import from) y renderizarlo en el elemento HTML div#app. También importa React y ReactDOM para pintar en la página. Recordad que este elemento ya lo tenemos en nuestro menú de administración.
-wp-content/plugins/react-plugin - build - node_modules - src * App.js * index.js * package.json * react-plugin.php
Configuración de Babel
Si pusiéramos a trabajar ahora mismo a Browserify, podríamos generar el archivo build/app.js pero todo saldría en ES6, cosa que los navegadores todavía no entienden bien. Tenemos que decirle a Browserify que utilice Babel y que éste use los presets que nosotros le digamos. Para ello creamos un nuevo fichero:
.babelrc
{ "presets": ["es2015", "react"] }
Con esto le estamos diciendo a Babel que use los presets (que ya tenemos instalados en node_modules) ES2015 y React.
-wp-content/plugins/react-plugin - build - node_modules - src * App.js * index.js * .babelrc * package.json * react-plugin.php
Ejecución de Browserify
Emoción, vamos a generar por primera vez nuestro fichero build/app.jscon su React, sus componentes y todo traducido a ES5 por un módico precio de cero maravedíes. A su vez, nuestros componentes estarán separados en sus respectivas carpetas para una mejor organización del código. Es un win-win en toda regla.
Para ello ejecutamos la siguiente sentencia en nuestro terminal:
./node_modules/.bin/browserify -t [ babelify ] src/index.js -o build/app.js
Con esto le decimos a Browserify: “Usando el plugin babelify, transforma el archivo src/index.js y todo lo que importa éste, en build/app.js. Si lo ejecutáis deberíais ver un archivo súper tocho en build/app.js.
Para no tener que ejecutar un comando tan largo, package.json tiene un apartado para crear scripts propios. Añadimos lo siguiente:
{ "name": "react-plugin", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "build": "browserify src/index.js -t [ babelify ] -o build/app.js" }, ...
Ahora simplemente ejecutamos
npm run build
Y Browserify se ejecutará tal y como hemos hecho antes.
Estado de las carpetas:
-wp-content/plugins/react-plugin - build * app.js - node_modules - src * App.js * index.js * .babelrc * package.json * react-plugin.php
De vuelta al plugin
Vale, ya tenemos generado nuestro build/app.js pero no lo estamos usando en el plugin de ninguna manera. Hay que hacer uso de hooks y funciones de WordPress para ello. Aquí está la versión definitiva del mini plugin que hemos hecho:
<?php
/**
* Plugin Name: React Plugin
*/
class React_Plugin {
private $page_id;
public function __construct() {
add_action( 'admin_menu', array( $this, 'admin_menu' ) );
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
}
public function admin_menu() {
$this->page_id = add_menu_page(
'React Plugin',
'React Plugin',
'manage_options',
'react-plugin',
array( $this, 'render_menu' )
);
}
public function render_menu() {
?>
<div class="wrap">
<h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
<div id="app"></div>
</div>
<?php
}
public function enqueue_scripts( $hook ) {
global $wp_version;
if ( $this->page_id === $hook ) {
wp_enqueue_script( 'react-plugin', plugin_dir_url( __FILE__ ) . 'build/app.js', array(), $wp_version, true );
}
}
}
new React_Plugin();
Si nos fijamos, utilizando el hook admin_enqueue_scripts, añadimos nuestro build/app.js sólo en la página de nuestro plugin gracias a la comparación $this->page_id === $hook. De esta forma no agregamos nuestro JS en todas las páginas. Sólo una nota más: El script debe ir en el footer, de otra forma no funcionará, ya que en el header todavía no existe nuestro div#app.
Próximamente…
En el siguiente post vamos a adentrarnos en cómo automatizar todo esto. Es un poco fastidioso ejecutar npm build cada vez que queremos cambiar algo. Para echo, usaremos un watcher que detecte los cambios en los ficheros JS y que rehaga el build en build/app.js.