Construcción efectiva con Gulp: Parte 1

Esta es una serie de tres artículos sobre el uso de Gulp en los que nos centraremos en los aspectos básicos de su funcionamiento, en los plugins más interesantes para mejorar nuestro flujo de trabajo y en otros aspectos avanzados.

Introducción y características principales

Gulp es un sistema de construcción de aplicaciones web cuya principal característica es su capacidad de encadenar flujos de tareas a modo de pipeline y de favorecer la definición de las tareas a realizar a través de código en lugar de en base a configuración.

Creado con posterioridad a Jake y a Grunt, ha supuesto una revolución por su rendimiento, claridad y facilidad de configuración.

Grunt se convirtió en una opción muy popular en 2013 a la hora de automatizar nuestro flujo de trabajo en el desarrollo de aplicaciones web. En la actualidad sigue siendo muy utilizado y dispone de miles de extensiones, aspecto muy importante cuando buscamos documentación y soluciones a un problema concreto.

En lugar de contar con una sección de configuración como Grunt, Gulp permite definir las tareas a realizar de una forma más programática, centralizando en un mismo sitio la definición de las tareas a realizar, con los valores de configuración que estas necesitan.

Por otra parte, Gulp utiliza de forma intensiva el API de Streams de NodeJS, con lo que hace un uso mucho menor del disco duro y aumenta su rendimiento de ejecución de tareas de una forma sustancial.

Este funcionamiento se ve reflejado en como se encadenan transformaciones dentro de una tarea con la función pipe:

1
2
3
4
5
6
7
8
gulp.task('js', function () {
return gulp.src('js/*.js')
.pipe(jshint())
.pipe(jshint.reporter('default'))
.pipe(uglify())
.pipe(concat('app.js'))
.pipe(gulp.dest('build'));
});

El cual sigue este esquema de procesamiento en memoria:

{<1>}pipeline

[Para más información acerca de los streams en NodeJS, podéis consultar el Stream Handbook.]

Finalmente, Gulp permite extender su funcionalidad mediante plugins con un API realmente sencilla. Por todo esto y mucho más, Gulp está siendo adoptada por multitud de proyectos entre los que podemos destacar Google Web Starter Kit.

Con el fin de poder ir viendo las diferencias, vamos a comparar como Grunt y Gulp son capaces de aplicar un pre-procesador CSS como LESS y pasar su output por Autoprefixer para añadir los “vendor prefixes” al CSS resultante.

Esta tarea en Grunt quedaría de la siguiente forma:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
grunt.initConfig({
less: {
development: {
files: {
"build/tmp/app.css": "assets/app.less"
}
}
},
autoprefixer: {
options: {
browsers: ['last 2 version', 'ie 8', 'ie 9']
},
multiple_files: {
expand: true,
flatten: true,
src: 'build/tmp/app.css',
dest: 'build/'
}
}
});

grunt.loadNpmTasks('grunt-contrib-less');
grunt.loadNpmTasks('grunt-autoprefixer');

grunt.registerTask('css', ['less', 'autoprefixer']);

Siendo así su correspondiente versión en Gulp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
var gulp = require('gulp'),
less = require('gulp-less'),
autoprefix = require('gulp-autoprefixer');

gulp.task('css', function () {
gulp.src('assets/app.less')
.pipe(less())
.pipe(autoprefix('last 2 version', 'ie 8', 'ie 9'))
.pipe(gulp.dest('build'));
});
```

Sencillo, ¿no? :)

Así pues, si prefieres especificar tus ficheros de construcción con código en lugar de utilizar estructuras JSON complejas para configurar tus tareas, [Gulp](http://gulpjs.com/) puede ser tu sistema de construcción.

Vamos pués a ver como instalarlo y ponernos manos a la obra.

## Instalación

Para poder utilizar [Gulp](http://gulpjs.com/), la única dependencia a instalar es la de NodeJS y Npm. Podemos comprobar si tenemos correctamente instaladas estas herramientas mediante los siguientes comandos:

```bash
$ node -v
v0.10.29

$ npm -v
1.4.14

(En caso de no tener instalado node, recomendamos su instalación mediante NVM (Node Version Manager), el cual permite tener instaladas varias versiones de NodeJS y configurarlas de forma sencilla).

Una vez instalado NodeJS y Npm, ya podemos instalar Gulp mediante el siguiente comando:

1
$ npm install -g gulp

De esta forma, tendremos el comando disponible de forma general en el sistema y no sólo para el proyecto actual.

Una vez concluida la instalación, configuraremos un proyecto de ejemplo y comenzaremos a explorar las funcionalidad que ofrece esta herramienta de construcción.

Creación de un proyecto de ejemplo

Para comenzar, crearemos el directorio inicial del proyecto de ejemplo que vamos a utilizar e inicializaremos su fichero de definición de dependencias para que Npm sea capaz de gestionarlo. Este fichero se llama package.json y recoge todas las librerías utilizadas por el proyecto:

1
2
3
$ mkdir basic
$ cd basic
$ npm init

El siguiente paso es activar Gulp en nuestro proyecto, para ello ejecutaremos:

1
$ npm install --save-dev gulp

(Npm acepta el parámetro -D como alias de --save-dev)

Finalmente, sólo tenemos que crear el fichero de definición de las tareas de construcción que va a servir para que Gulp pueda ejecutar todos los pasos necesarios para la construcción de nuestro proyecto.

Este fichero se debe de llamar gulpfile.js y en los siguientes apartados describiremos sus capacidades en detalle.

Definición del gulpfile.js

El gulpfile.js más básico que podemos definir es este:

1
2
3
4
5
var gulp = require('gulp');

gulp.task('default', function() {
console.log('Gulp works!!');
});

Si ahora ejecutamos el comando gulp dentro del directorio del proyecto … it works!! Gulp está ya operativo y construyendo nuestro proyecto:

1
2
3
4
5
$ gulp
[20:26:42] Using gulpfile /opt/devel/workspaces/gulp/basic/gulpfile.js
[20:26:42] Starting 'default'...
Gulp works!!
[20:26:42] Finished 'default' after 76 μs

Pasemos a ver como se definen las distintas tareas a realizar dentro de un fichero de construcción de Gulp

La tarea invocada por defecto

Por defecto, cuando no le especificamos ninguna tarea a ejecutar, Gulp ejecutará la marcada como “default”. En cualquier caso, siempre podemos invocar una tarea en concreto o una lista de ellas de la siguiente forma:

1
2
$ gulp <tarea>
$ gulp <tarea1> <tarea2>

Dependencias entre tareas

Es muy habitual que la construcción de un proyecto complejo sea la suma de la ejecución de distintas tareas específicas. Así, podemos procesar y minificar el código CSS, nuestros scripts JavaScript o CoffeeScript, optimizar nuestras imágenes o cualquier otra tarea que se nos ocurra.

Para completar este conjunto de tareas, podemos hacer que cuando se ejecute la tarea por defecto, se llame previamente a una lista de tareas de las cuales depende.

Podemos especificar estas definiciones de la siguiente forma, teniendo en cuanta que todas ellas se lanzarán de forma asíncrona:

1
2
3
4
5
6
7
8
9
var gulp = require('gulp');

gulp.task('init', function() {
console.log('Inicializamos el proyecto ...');
});

gulp.task('default', ['init'], function() {
console.log('Gulp works!!');
});

Así, cuando ejecutamos el comando gulp, se ejecutará primero la tarea de inicialización y luego de por defecto, ya que esta depende de init:

1
2
3
4
5
6
7
[10:27:40] Using gulpfile /opt/devel/workspaces/gulp/task-deps/gulpfile.js
[10:27:40] Starting 'init'...
Inicializamos el proyecto ...
[10:27:40] Finished 'init' after 128 μs
[10:27:40] Starting 'default'...
Gulp works!!
[10:27:40] Finished 'default' after 38 μs

En un proyecto real, es bastante común que la tarea principal de construcción sea la que lanza el resto de transformaciones a realizar:

1
2
3
gulp.task('build', ['css', 'js', 'imgs'], function () {
// Acciones a realizar
});

(Recuerda que css, js e imgs se lanzarán de forma asíncrona, con lo que no se garantiza el orden de finalización de estas tareas).

Lista de ficheros origen de una operación

Antes de poder minificar o revisar el formato de uno o más ficheros de nuestro proyecto, tenemos que ser capaces de definir una expresión que indique sobre qué ficheros trabajar.

Con gulp.src() podremos definir un GLOB sobre el que realizar la operación.

En NodeJS, un GLOB es una expresión textual o vector de las mismas, de match para uno o varios ficheros que pueden ser usados en un pipeline para actuar posteriormente sobre ellos. Algunos ejemplos de GLOB pueden ser:

1
2
3
4
'style.css'     // Fichero concreto
'*.css' // Todos los fichero con extensión CSS
'**/*.js' // Todos los fichero JS en cualquier subdirectorio
'!js/main.js' // Exclusión de un fichero concreto

En el siguiente ejemplo, podemos ver una expresión compuesta de varios patrones:

1
2
3
4
5
gulp.task('default', function() {
gulp.src(['js/**/*.js', '!js/main.js'])
.pipe(...)
...
});

Referencias

Puedes encontrar todos los ejemplos utilizados en este artículo en el repositorio de ejemplos de Programmer At Work