Cómo utilizar ReactJS con Browserify

En los últimos posts vimos qué nos ofrece ReactJS a otras librerías para desarrollar interfaces de usuario con Javascript y construimos un componente de ejemplo con ReactJS para hacernos una idea de cómo trabajar con ReactJS.

Como ya vimos al crear el componente de ejemplo, ReactJS permite utilizar una sintaxis especial llamada JSX para generar las vistas, y es necesario realizar de alguna forma la conversión de este JSX al código Javascript puro que se encargará de construir el DOM. Esto se puede hacer directamente en el browser, pero es preferible hacerlo durante la «compilación» de la aplicación para minimizar el impacto en los clientes que accedan a nuestra aplicación web.

En este post vamos a ver cómo podemos aprovechar herramientas basadas en node.js para crear un flujo de trabajo basado en grunt que nos facilite el desarrollo de aplicaciones ReactJS.

Browserify

Browserify es una herramienta que permite organizar una aplicación Javascript en varios archivos y referenciar unos desde otros a través de require('module'), como si se tratase de una aplicación para node.js.

Browserify se encargará de unir estos archivos de manera «inteligente», es decir, incluyendo sólo los módulos necesarios en el orden adecuado, y generar un único fichero listo para ser utilizado en el browser, ya sea incluyéndolo directamente en la página o a través de alguno de los sistemas de carga de módulos (AMD, CommonJS, UMD, etc.).

Para nuestro ejemplo vamos a usar browserify junto a grunt, por lo que deberemos instalar las siguientes herramientas:

npm install grunt-cli -g
npm install grunt --save-dev
npm install grunt-browserify --save-dev
npm install grunt-contrib-watch --save-dev

Técnicamente, la última dependencia no es necesaria, pero nos resultará útil para detectar automáticamente cambios en el código fuente y recompilar la aplicación.

Con esto instalado, podríamos crear un fichero de configuración para grunt como éste:

/*global module, require */

module.exports = function(grunt) {
  var srcFiles = ['Gruntfile.js', './lib/**/*.js', './specs/**/*.js'];
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),    
    browserify: {
      build: {
        options: {
          standalone: "app"
        },
        src: './lib/index.js',
        dest: './dist/app.js'
      }
    },
    watch: {
      dev: {
        files: srcFiles,
        tasks: ['browserify'],
        options: { spawn: false }
      }
    }
  });
  grunt.loadNpmTasks('grunt-browserify');
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.registerTask('default', ['browserify']);
  grunt.registerTask('dev', ['watch']);
};

Ya vimos en su momento cómo configurar grunt, así que nos centraremos en la configuración de browserify.

En la configuración del task de browserify debemos indicar el archivo que contiene el «punto de entrada» a nuestra aplicación. A partir de él, browserify irá analizando los módulos importados y detectará automáticamente todos los archivos que necesita incluir en el resultado final. También debemos indicar el nombre del fichero de salida, en este caso ./dist/app.js y el «tipo» de resultado que queremos generar, en este caso app para indicar que será un script que incluiremos directamente en una página sin utilizar ningún sistema de carga de módulos.

Además hemos añadido un task watch (y un alias dev para ese task) que se encarga de detectar cambios en los fuentes y relanzar el proceso de compilación, haciéndonos la vida un poco más cómoda durante el desarrollo.

React-Tools

Lo que hemos visto hasta ahora es completamente genérico y podemos utilizarlo para cualquier tipo de aplicación. Si queremos desarrollar una aplicación con ReactJS, vamos a necesitar instalar algunos paquetes más:

npm install react --save-dev
npm install react-tools --save-dev
npm install grunt-react --save-dev

react es el módulo npm de ReactJS. Sería equivalente a incluir el script de ReactJS en la página, pero nos permite usarlo al «estilo node.js» usando un require('react') desde los archivos que luego pasaremos por browserify.

react-tools contiene, entre otras cosas, herramientas para transformar el código JSX en código Javascript, y grunt-react contiene todo lo necesario para integrar react-tools con grunt.

Con esto instalado, podemos modificar la configuración de browserify en grunt para incluir la transformación de los JSX:

  // ... el resto de la configuración no cambia
  browserify: {
    options: {
      transform: [ require('grunt-react').browserify ]
    },
    build: {
      options: {
        standalone: 'app'
      },
      src: './lib/index.js',
      dst: './dist/app.js'
    }
  },
  // ... resto de configuración

Escribiendo el código

Todo esto está muy bien, pero llevamos un rato y no hacemos más que instalar herramientas. ¿Cómo se escribe el código ahora que hemos montado todo esto?

Si volvemos al ejemplo de ReactJS que vimos en el post anterior, ahora podríamos partirlo en tres ficheros y organizarlo un poco (esto es, claramente, innecesario en un caso tan simple como éste, pero bueno).

En un primer fichero podemos definir el componente para mostrar un único elemento de la lista y dejarlo en un fichero BeerItem.js:

/** @jsx React.DOM */

var React = require('react');

var BeerItem = React.createClass({
  // ... código del post anterior
});

module.exports = BeerItem;

Utilizamos el típico sistema de módulos de node.js, con su require para acceder a otros módulos y su exports para definir lo que exporta este módulo. Es importante recordar que cada fichero debe empezar con la directiva /** @jsx React.DOM */ para que el preprocesador de ReactJS sepa cómo tratarlo.

La estructura del código para la lista completa (BeerList.js) es similar:

/** @jsx React.DOM */

var React = require('react');
var BeerItem = require('./BeerItem.js');

var BeerList = React.createClass({
  // .... código de post anterior
});

module.exports = BeerList;

Aquí además del módulo react que instalamos antes, estamos referenciando el módulo BeerItem que acabamos de crear.

Para finalizar, tendríamos el fichero index.js con el punto de entrada de la aplicación:

/** @jsx React.DOM */

var React = require('react');
var BeerList = require('./BeerList');

React.renderComponent(<BeerList/>, document.getElementById('content'));

Ahora para compilar el resultado final, podemos ejecutar grunt, y si queremos que se ejecuté automáticamente cada vez que cambiamos algo, grunt dev.

Tenéis el proyecto completo en github por si queréis jugar un rato con él.

Resumen

La sintaxis JSX usada por ReactJS para mezclar Javascript y HTML es algo que puede causar rechazo, sobre todo a los que estén muy preocupados por el rendimiento de las páginas y la comodidad a la hora de trabajar.

Con el conjunto de herramientas que hemos visto en este post no hay que sacrificar rendimiento al cargar nuestras páginas parseando el JSX (cosa que, por ejemplo, si ocurre con las directivas de angularjs) y podemos conseguir un flujo de trabajo muy cómodo a la hora de desarrollar.

13 comentarios en “Cómo utilizar ReactJS con Browserify

  1. Pingback: Ni grunt, ni gulp: solo npm | Koalite

  2. Hay una errata en la instrucción para instalar «watch» en Grunt:

    npm install grunt-contrib-wath –save-dev

    Se ha escrito «wath» en lugar de «watch».

  3. Estoy trasteando con React y ando siguiendo tu secuencia de artículos como referencia. Por el momento React me parece interesante, pero la conversión desde JSX a JavaScript no me termina de convencer. Razones:

    1. Se genera un fichero muy grande que incluye toda la funcionalidad de React.

    2. No se puede reducir el uso del ancho de banda mediante CDN.

    3. Si se usan más de una página web hay demasiada duplicidad del código de React, ya que se requiere incluir React en la transformación de JSX a JavaScript. De acuerdo que React está pensado para SPA, pero veo muy excesivo la limitación a una sola página.

    ¿Hay algún detalle que se me haya escapado para optimar el uso de React?

  4. Hola Miguel,

    Los problemas que planteas, más que con la conversión de JSX a Javascript, tienen que ver con el propio React. Podrías escribir a mano el Javascript equivalente al JSX y estarías igual.

    Dicho esto, son preocupaciones totalmente razonables, pero que se pueden solucionar:

    1) El tamaño del fichero es relativo. React no es pequeño, pero una vez minificado y gzipeado, son unos 26KB, menos que otras librerías como AngularJS (40KB) o Ember (90KB).

    2) Puedes usar React desde un CDN sin problemas. En ese caso, puedes no hacer el require desde tu código, y asumir que está disponible globalmente, o puedes indicar a browserify que lo excluya.

    3) Lo mismo que en el punto 2). Si es una SPA, en general no es mucho problema hacer un bundle con tu código + react y minificarlo todo junto. De hecho ahorras una petición HTTP y puede ayudar al rendimiento. Si tienes varias páginas, puedes separar React de tu aplicación y aprovechar la cache del navegador.

    De todas formas, no olvides que también puedes usar react sin browserify. Si no te gusta su sistema de módulos y lo estás usando sólo para convertir de JSX a Javascript, puedes utilizar directamente babel (con el preset de react) para compilar el JSX a Javascript, y luego montar el sistema de módulos/bundles que mejor te venga.

    Un saludo,

    Juanma.

  5. Muchas gracias Juanma. Gran explicación.

    He mirado la opción de babel y me convence mucho más la filosofía de Browserify.

    Las soluciones que planteas son muy razonables y seguiré los pasos marcados en tus artículos una vez más :-D

  6. Quizás pueda interesar a alguien la solución que he adoptado. Finalmente he configurado Grunt para que me genere dos «bundles»: uno para los ficheros de React y otro para los JSX.

    El proceso está explicado aquí:

    http://willacton.com/easily-browserify-common-libraries/

    Básicamente se añade una nueva tarea a Browserify para que genere un «bundle» con los ficheros de React y se establece que los excluya del JSX convertido en JavaScript.

    Sólo indico las diferencias con respecto al código establecido en este artículo:

    browserify:
    {
    	// ...
    	libs:
    	{  
    		src: ['.'],
    		dest: 'lib/libs.js',
    		options:
    		{
    			alias: ['react:', 'react-dom:']
    		}
    	},
    	build:
    	{
    		options:
    		{
    			// ...
    			external: ['react', 'react-dom'],
    		},
    		// ...
    	}
    }
    
  7. Un aspecto muy interesante al separar los ficheros de React y los de la aplicación es que la tarea de Grunt (watch) se puede ejecutar mucho más rápido mientras estás desarrollando.

    El «bundle» de React sólo es necesario crearlo una vez, después se puede dejar comentada la tarea «libs» de «browserify». En mi máquina el tiempo de ejecución pasa de 3 segundos a 100 ms para una aplicación pequeña.

    Habría que ajustar la configuración para la integración continua, pero durante el desarrollo lo veo más cómodo.

  8. Al final he desestimado Browserify y usaré webpack. Me ha parecido demasiado complejo el uso de los plugins.

    Necesito utilizar «dynamic routing» con React Router (https://github.com/reactjs/react-router/blob/latest/docs/guides/DynamicRouting.md) y con webpack es muy sencillo aplicarlo. He visto que Browserify dispone de un «plugin» llamado «partition-bundle» (https://github.com/arian/partition-bundle) que permite ir cargando los «scripts» con los componentes conforme se van necesitando, pero considero que la documentación es floja y tampoco se observa un uso entre la comunidad.

  9. Si necesitas hacer carga dinámica de módulos, webpack es mejor opción, aunque también introduce más complejidad (desde mi punto de vista).

    Muchas gracias por ir aportando todos estos comentarios y enlaces al post. Me están viniendo genial y se de más de uno que también los está aprovechando.

  10. Muchas gracias a ti por la magnífica información que ofreces.

    Por cierto, con Browserify adopté watchify y funciona de maravilla. Gracias por la recomendación.

    Por el momento me paso a Webpack por la necesidad de carga dinámica de módulos. Si saco tiempo lo miraré con Browserify. Sé que el «plugin» que he puesto debe funcionar, pero al pensar en adaptarlo a Grunt y React y ver poca información en general sobre el «plugin»… he considerado que esto puede acabar complicándose demasiado y me he rajado XD

    Estoy de acuerdo contigo en que Browserify ofrece mayor simplicidad. Antes no me he expresado con claridad.

Comentarios cerrados.