Automatización de tareas en el front-end con gulp.js

jv81

A la hora de desarrollar aplicaciones siempre habrán tareas que realicemos varias veces. Pongamos como ejemplo las siguientes, asociadas al desarrollo front-end de una página web:

  • Ejecutar nuestros tests
  • Pasarle un ‘lint’ a nuestro JavaScript
  • Transformar nuestros estilos escritos en Sass a CSS
  • Transformar nuestro código escrito en Jade a HTML
  • Optimizar nuestras imágenes
  • Minificar archivos HTML, CSS y JS
  • Concatenar archivos CSS y JS
  • Subir nuestra web a un servidor

Estas tareas definen parte de nuestro flujo de trabajo y seguramente ya hagamos uso de herramientas para automatizar algunas de ellas.

Una práctica común para abordar la ejecución de este tipo de acciones de una manera más eficiente consiste en hacer uso de un sistema que se encargue de realizar estas tareas por nosotros. El uso de éstos sistemas permite, además, la implantación de mecanismos de integración continua para evaluar la calidad del código, generar documentación, definir eventos y respuestas asociadas al resultado de la ejecución de tareas y la generación de reportes para el programador o programadores que participen en el desarrollo de la aplicación.

En el contexto del desarrollo front-end Gulp.js es una herramienta que nos permite crear y ejecutar estas tareas de una manera sencilla y flexible.

Una vez instalado, con npm vía npm install –global gulp, podemos empezar a utilizarlo.

Entiendiendo a Gulp; programación de tareas y manejo de streams

Gulp se diferencia de otras herramientas de este tipo en que en vez de “configurar” tareas se “programan” con código JavaScript. Otro aspecto que lo diferencia es el uso de un mecanismo de flujo de información, a partir de ahora stream, que se inicia a partir de una fuente y se transporta a través de una serie de conductos, a partir de ahora pipes, que procesan y transforman la información que pasa a través de ellos.

Veamos un ejemplo de lo anterior definiendo la siguiente tarea: tenemos una serie de archivos con estilos escritos en Sass en la carpeta ‘./src/scss’, queremos procesarlos para obtener el correspondiente código en CSS, minificar el resultado y copiarlo a la carpeta ‘./build/css’.

Creemos un archivo llamado gulpfile.js en la raíz de nuestro proyecto con el siguiente código:

La definición de nuestra tarea comienza con gulp.task donde el primer argumento indica el nombre de la tarea y el segundo una función anónima. En ésta gulp.src especifica un patrón de búsqueda que resulta en la lectura de una serie de archivos de extensión scss. Estos archivos se convierten en un stream de archivos en lenguaje sass que viaja a través de varios pipes (.pipe()), la transformación que sufre el stream al pasar por un pipe se transfiere al siguiente pipe.

Analicemos que ocurre en cada pipe:

  • .pipe(sass()) : El stream es transformado por el módulo gulp-sass, del pipe sale un stream en el que el código de los archivos ha sido convertido a css.
  • .pipe(minifyCSS()): El stream es transformado por el módulo gulp-minify-css, del pipe sale un stream en el que el código de los archivos en css ha sido minificado.
  • .pipe(gulp.dest(‘./build/css’)): El stream es volcado como archivos en css minificados a la ruta especificada.

Y con esto nuestra tarea en gulp queda definida.

Para iniciar gulp tenemos dos opciones, ejecutar gulp ‘nombre de la tarea’, el cual ejecuta una tarea especifica, o ejecutar gulp a secas, que en este caso se ejecutará la tarea ‘default’ la cual inicia todas las tareas en paralelo contenidas en el vector del segundo miembro en gulp.task(‘default’,[]).

Una vez entendemos el funcionamiento de gulp podemos aprovecharnos de la gran cantidad de plugins que tenemos a nuestra disposición para sacarle el mayor partido a la herramienta.

A continuación una serie de puntos interesantes para ayudarnos a entender mejor y a refinar nuestras tareas.

Patrones de selección

gulp.src() usa un sistema de patrones de selección ‘globbing’ que nos permite seleccionar los archivos que generan un stream. Una referencia para consultar este sistema de patrones puede encontrarse en el siguiente enlace (Gulp y Grunt comparten este sistema de patrones).

Concatenar, enlazar y “browserificar” (y ya que estamos, minificar)

Si tenemos varios javascripts en una página web, concatenarlos en un solo archivo disminuye el tiempo de carga de nuestra página. Minificar es igualmente conveniente por el mismo motivo, y en este caso podemos crear una tarea que haga ambas cosas gracias a los plugins gulp-concat y gulp-uglify, tal como se muestra en el siguiente snippet:

Como vemos, concat() nos permite indicar el nombre del javascript resultante que deberemos referenciar en el HTML encargado de cargarlo.

Existen unas pocas librerías cuya carga será, la mayoría de los casos, obtenida de la caché del navegador, por tanto es mejor referenciar los CDN’s correspondientes que realizar una concatenación con ellos. Como ejemplos, el omnipresente jQuery y el conocido Bootsrap (del cual conviene referenciar también el archivo CSS).

Browserify es la nueva forma preferida, en detrimento de Require.JS, de definir y cargar nuestros módulos y módulos de terceros en javascript tal como lo haríamos en node. Esto nos permite cargar módulos vía require() y a definir módulos añadiendo un objeto a module.exports. Para ello instalamos Browserify y lo ejecutamos especificando los javascripts que queremos ‘browserificar’, lo cual genera el código correspondiente y compatible con el navegador.

El uso de Browserify tiene un detalle importante, si procesamos archivos que requieran módulos, el contenido de éstos quedarán añadidos al código resultante ‘browserificado’. Es decir, a efectos prácticos Browserify está concatenando el código de las dependencias definidas por un script en el propio script. Esto hay que tenerlo en cuenta para no añadir referencias de más en nuestro HTML y para vigilar la concatenación de código ‘browserificado’ con gulp.

Veamos ahora como usar Browserify con gulp:

Para este ejemplo queremos obtener una serie de archivos ‘browserificados’ a partir de javascripts localizados en ‘./src/js’, si tenemos módulos que no queramos incluir dentro de ‘./build/js’, podemos meterlos en un directorio que no sea leído por el patrón de búsqueda definido por gulp.src‘./src/js/lib’, por ejemplo.

Un ejemplo de todo esto puede encontrarse en este repositorio .

Definiendo un entorno que condicione el comportamiento de nuestras tareas

Podemos definir una variable que sea especificada a la hora de ejecutar de gulp mediante un argumento gracias al módulo yargs. Así, si queremos procesar dentro de nuestro gulpfile.js una variable env, podemos hacerlo de la siguiente manera:

Y especificar manualmente en valor de env (si no lo especificásemos su valor por defecto sería ‘dev’) al ejecutar gulp de esta forma:

Ahora veamos un ejemplo práctico donde esto puede ser útil: queremos browserificar nuestro javascript, pero en caso de estar en modo de desarrollo nos interesa generar los sourcemaps correspondientes. Para este caso optaremos por asignar a la variable de entorno env el valor dev. Por contra, si queremos obtener un código listo para producción omitiremos los source maps, para ello le daremos a env el valor prod. Un plugin de gulp que nos permite introducir condicionales dentro de nuestros pipes es gulp-if, y para este ejemplo podemos usarlo de la siguiente manera:

Ejecución de tareas en serie y paralelo

A la hora de agrupar tareas vía gulp.task(‘nombre del grupo de tareas’,[‘tarea1′,’tarea2’…’tarea-n’]), las tareas contenidas en el vector del segundo argumento serán ejecutadas en paralelo. Para poder ejecutar tareas en serie haremos uso del plugin run-sequence.

Como ejemplo:

run-sequence ejecutará en serie aquellas tareas que no estén especificadas dentro de un vector y en paralelo aquellas que sí lo estén. En este caso es conveniente no ejecutar ninguna otra tarea hasta que se verifique que los tests son satisfactorios, igualmente que no se ejecuten otras tareas hasta que el directorio ‘build’ haya terminado de ser eliminado (tarea clean en este ejemplo). De la misma forma querremos que todas las tareas hayan finalizado antes de subir los archivos de nuestro proyecto a un host vía ftp (tarea ftp).

Ejecución automática de tareas y visualización inmediata de cambios de nuestro código en el navegador

gulp.watch() nos permite ejecutar tareas cuando el contenido de una serie de archivos, especificados por un patrón, cambien su contenido. Un ejemplo:

Vemos lo sencillo que resulta, asociamos tres tareas a tres patrones diferentes.

Si queremos visualizar los cambios que vayamos haciendo en nuestro navegador tendremos que hacer uso del módulo browser-sync de la siguiente manera:

Al igual que con gulp.watch(), cambios en archivos especificados mediante patrones son necesarios, en este caso para actualizar el navegador web. Sólo nos resta indicar el directorio que queremos tratar como raíz de nuestra web (vía parámetro BaseDir) y ya tendremos nuestro browser-sync listo.

Un ejemplo y esqueleto de página web que incorpora gulp con un conjunto de tareas típicas para el front-end podemos encontrarlo en este repositorio. Notar que, al igual que en las tareas mostradas a lo largo de este artículo, el directorio ‘src’ contiene las fuentes inalteradas de nuestra web y el directorio ‘build’ (eliminado y creado en cada ejecución de la tarea build) contiene todos los archivos procesados de ‘src’ por gulp.

Acerca de

Ver todas las entradas de