Construcción efectiva con Gulp: Parte 3

Finalizamos esta serie con el tercero de los artículos dedicado a Gulp, hablando de otras consideraciones que debemos tener cuando utilizamos Gulp. No olvides revisar el primer capítulo de introducción o segundo sobre plugins para mejorar el flujo de trabajo.

Otras consideraciones de uso

Carga de plugins desde el package.json

Resulta bastante incómodo ver aumentar la parte de definición de variables para cada módulo que queremos usar de Gulp, ya que nuestros scripts pueden comenzar a ser difíciles de leer:

1
2
3
4
5
6
7
var gulp = require('gulp');
var uglify = require('gulp-uglify');
var concat = require('gulp-concat');
var coffee = require('gulp-coffee');
var es = require('event-stream');
...

Una forma de solucionar esta situación es mediante el uso del módulo gulp-load-plugins, el cual permite hacer referencia a cualquier módulo configurado en nuestro package.json sin necesidad de declarar una variable.

Para ello, el primer paso es instalar la dependencia:

1
npm install gulp-load-plugins --save-dev

Posteriormente, debemos asegurarnos de que los módulos a utilizar se encuentran instalados y disponibles en nuestro package.json:

1
2
3
4
5
6
7
8
9
10
{
"name": "load-plugins",
"version": "0.0.0",
"devDependencies": {
"gulp": "~3.8.7",
"gulp-uglify": "~0.3.1",
"gulp-concat": "~2.3.4",
"gulp-load-plugins": "~0.5.3"
}
}

Con todas las dependencias listas y partiendo de un ejemplo que utilice alguna de ellas:

1
2
3
4
5
6
7
8
9
10
var gulp = require('gulp');
var uglify = require('gulp-uglify');
var concat = require('gulp-concat');
gulp.task('default', function () {
gulp.src('js/*.js')
.pipe(concat('all.min.js'))
.pipe(uglify())
.pipe(gulp.dest('dist'));
});

Podemos pasar a un ejemplo equivalente utilizando gulp-load-plugins:

1
2
3
4
5
6
7
8
9
var gulp = require('gulp'),
plugins = require('gulp-load-plugins')();
gulp.task('default', function () {
gulp.src('js/*.js')
.pipe(plugins.concat('all.min.js'))
.pipe(plugins.uglify())
.pipe(gulp.dest('dist'));
});

En este ejemplo más reducido puede parecer que no se simplifica demasiado el fichero de construcción, pero en proyectos con muchas más transformaciones la mejora se hace realmente evidente. Además, la versión 0.0.4 de gulp-load-plugins ha añadido la carga lazy de los módulos, lo cual mejora bastante el rendimiento de esta solución.

Reutilización de partes del flujo de construcción

En ocasiones, ocurre que dos tipos de procesamientos distintos acaban teniendo una parte común en el flujo de transformación. Es el ejemplo del procesamiento de ficheros JavaScript y de CoffeeScript. Ambos deben ser minificados y concatenados, pero además, los ficheros CoffeeScript deben pre-procesarse para generar JavaScript.

Si atacamos esta problemática por separado, las acciones a realizar tendrían que ser de la forma:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var gulp = require('gulp');
var uglify = require('gulp-uglify');
var coffee = require('gulp-coffee');
var gulpif = require('gulp-if');
gulp.task('coffeescript', function () {
gulp.src('coffee/*.coffee')
.pipe(coffee())
.pipe(uglify())
.pipe(gulp.dest('dist'));
});
gulp.task('default', ['coffeescript'], function() {
gulp.src('js/*.js')
.pipe(uglify())
.pipe(gulp.dest('dist'));
});

Como podemos ver, estas dos tareas comparten gran parte de la definición del flujo. Para poder reaprovechar estas definiciones y no duplicar código, tenemos dos opciones.

La primera es combinarlas haciendo uso de un módulo llamado gulp-if. Pare ello lo podemos instalar con:

1
npm install gulp-if --save-dev

Y luego podemos partir de un GLOB que incluya tanto los ficheros JavaScript como CoffeeScript y aplicar sólo la compilación de los ficheros .coffee mediante gulp-if:

1
2
3
4
5
6
7
8
9
10
11
12
13
var gulp = require('gulp');
var uglify = require('gulp-uglify');
var concat = require('gulp-concat');
var coffee = require('gulp-coffee');
var gulpif = require('gulp-if');
gulp.task('default', function () {
gulp.src(['js/*.js', 'coffee/*.coffee'])
.pipe(gulpif(/[.]coffee$/, coffee()))
.pipe(concat('all.min.js'))
.pipe(uglify())
.pipe(gulp.dest('dist'));
});

Como ejemplo adicional, hay algunas veces en las que puede resultar interesante consultar el NODE_ENV (variable de entorno que nos dice si estamos en “production” o “development”) a la hora de generar los ficheros minificados o legibles y “bonitos” para simplificar su depuración.

Para ello debemos instalar el módulo gulp-beautify:

1
npm install gulp-beautify --save-dev

Y modificiar nuestro gulpfile.js con la siguiente configuración:

1
2
3
4
5
6
7
8
9
10
11
12
13
var gulp = require('gulp');
var uglify = require('gulp-uglify');
var beautify = require('gulp-beautify');
var concat = require('gulp-concat');
var gulpif = require('gulp-if');
gulp.task('default', function () {
gulp.src('js/*.js')
.pipe(concat('all.min.js'))
.pipe(gulpif(process.env.NODE_ENV === 'production', uglify()))
.pipe(gulpif(process.env.NODE_ENV !== 'production', beautify()))
.pipe(gulp.dest('dist'));
});

Con respecto a la segunda estrategia de la que hablábamos a la hora de compartir partes del flujo de construcción de una forma más orientada a streams, podemos hacer uso del módulo de event-stream. Lo primero es siempre instalar dicho módulo:

1
npm install event-stream --save-dev

De forma que ya podemos modificar nuestro ejemplo para reutilizar las partes comunes de ambos procesos:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var gulp = require('gulp');
var uglify = require('gulp-uglify');
var concat = require('gulp-concat');
var coffee = require('gulp-coffee');
var es = require('event-stream');
gulp.task('default', function () {
var coffeeFiles = gulp.src('coffee/*.coffee')
.pipe(coffee());
var jsFiles = gulp.src('js/*.js');
return es.merge(coffeeFiles, jsFiles)
.pipe(concat('all.min.js'))
.pipe(uglify())
.pipe(gulp.dest('dist'));
});

Referencias

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