Este tutorial pretende ser una iniciación a la herramienta Gulp, que sirve para automatizar tareas en proyectos Javascript. En él veremos como instalarlo y como hacer una primera aplicación de ejemplo en Javascript que utilice Gulp para realizar unas sencillas tareas.
Índice de contenidos.
- Introducción.
- Entorno.
- Instalación.
- Funcionamiento.
- Gulp API.
- El gulpfile.
- Ejemplo de uso.
- Plugins.
- Conclusiones.
- Referencias.
1. Introducción.
Con el auge de los nuevos frameworks javascript como AngularJs, Backbone, EmberJs, React, Polymer, etc. surge la necesidad de herramientas para gestionar los proyectos.
Las arquitecturas basadas en estos frameworks necesitan un build system, es decir, es una colección de tareas que automatizan un trabajo repetitivo. De manera análoga en Java tendríamos Maven o en Groovy tendríamos Gradle.
Los componentes más comunes en un flujo típico de front-end son:
- Gestores de dependencias
- Preprocesadores (opcionalmente)
- Herramientas para la construcción de tareas
Gulp es una herramienta para la gestión y automatización de tareas en Javascript. Estas tareas serán de uso común para los desarrolladores. El único requisito para utilizar Gulp es tener instalado Node.js
Algunas de las tareas más frecuentes son:
- Compilación de CSS y Javascript preprocesado
- Concatenación
- Minificación
- Lanzar un servidor para la recarga automática en el browser
- Creación de una build para despliegue
- Ejecución de tests unitarios
- Ejecución de herramientas para detectar errores en el código
- Gestionar el código en un repositorio
2. Entorno.
Este tutorial está escrito usando el siguiente entorno:
- Hardware: Portátil Mac Book Pro 15″ (2,3 Ghz Intel Core i7, 16 GB DDR3)
- Sistema Operativo: Mac OS X El Capitan
- NodeJS v4.4.3
- npm 3.8.3
- Atom 1.7.1
- Gulp 3.9.0
3. Instalación.
Abrimos un terminal e instalamos Gulp de manera global con el siguiente comando.
sudo npm install --global gulp-cli
4. Funcionamiento.
A diferencia de otras herramientas de este tipo, como Grunt, Gulp utiliza streams para la ejecución de tareas. Un stream es un flujo de datos para el intercambio de información. Gulp utiliza el módulo Stream de Node.js. Internamente utiliza la librería vinyl-fs para leer y escribir en un stream.
Además se utilizan tuberías(pipes) para realizar operaciones sobre un stream. Las tuberías son un mecanismo para la comunicación y sincronización entre procesos.
- Basados en el patrón productor/consumidor
- Están implementadas de forma muy eficiente en los sistemas operativos
- Inician todos los procesos al mismo tiempo
- Atienden automáticamente los requerimientos de lectura de datos para cada proceso cuando los datos son escritos por el proceso anterior
5. Gulp API.
El API de Gulp es muy sencillo y consta 4 instrucciones básicas: gulp.task(), gulp.src(), gulp.dest() y gulp.watch().
- gulp.task()
- Define una tarea
- Tiene 3 argumentos:
- el nombre de la tarea
- dependencias de otras tareas
- la función a ejecutar
- gulp.src()
- Toma como parámetro una cadena que coincida con uno o más archivos
- Utiliza patrones que usa el intérprete de comandos de unix(shell)
- Retorna un stream que puede ser “pipeado” a un plugin adicional ó hacia el método gulp.dest()
- gulp.dest()
- Canaliza y escribe archivos desde un stream
- Puede canalizar a varias carpetas
- Creará las carpetas que no existan
- Retornará el stream, por si deseamos realizar alguna acción más
- Sirve para escribir los datos actuales de un stream
- gulp.watch()
- Observa archivos y realiza una acción cuando se modifica un archivo
- Esto siempre devuelve un EventEmitter que emite los eventos de cambio
- Tiene 2 formas de uso:
- gulp.watch(glob, [tasks])
- gulp.watch(glob, callback)
- El primer parámetro es un glob con la ruta o un patrón con uno o varios ficheros
- El segundo puede ser una lista de tareas o una función a ejecutar
- gulp.series()
- Realiza ejecución de tareas de manera secuencial
- Tiene de 2 argumentos:
- El nombre de la/s tarea/s a ejecutar
- Una función a ejecutar (opcional)
- gulp.parallel()
- Realiza ejecución de tareas en paralelo
- Tiene de 2 argumentos:
- El nombre de la/s tarea/s a ejecutar
- Una función a ejecutar (opcional)
- El gulpfile debe de contener una estructura con los siguientes elementos:
- La importación de otros módulos
- La importación de un fichero de configuración del proyecto (opcional)
- La definición de las tareas
- Observadores que se ejecutan en función de ciertos cambios (opcional)
- Una tarea por defecto a ejecutar
- Si el gulpfile del proyecto es muy grande conviene modularizarlo en varios ficheros más pequeños por funcionalidad e importarlos
- En una arquitectura donde tengamos muchos módulos similares conviene externalizar las tareas comunes en un módulo de Node.js para no repetir las mismas tareas en muchos puntos y que éstas puedan divergir.
- Concatenar ficheros
- Minimizar js
- Minimizar imágenes
- Inyectar dependencias dinámicamente
- Ejecutar tests
- Gestionar el código en un repositorio (svn, git)
- Empaquetar un directorio
- Ejecutar jshint, htmlhint
- Generar css ejecutando saas, less, etc.
- Mostrar ayuda
- gulp-zip
- Para comprimir directorios con ficheros
- Permite generar una distribución front-end
- gulp-if
- Permite ejecutar una tarea de manera condicional
- gulp-git
- Para ejecutar comandos sobre un repositorio de código GIT
- Commit, add, push, clone, etc
- https://joellongie.com/
- https://medium.com/@preslavrachev/gulp-vs-grunt-why-one-why-the-other-f5d3b398edc4#.fdjr9cp2m
- http://frontendlabs.io/1669–gulp-js-en-espanol-tutorial-basico-primeros-pasos-y-ejemplos
- https://es.wikipedia.org/wiki/Tuber%C3%ADa_(inform%C3%A1tica)
- https://www.npmjs.com/
- http://www.slideshare.net/lvaroNavarroIborra/automatizacion-de-tareas-con-gulp-58773249
- https://github.com/alvNa/nodejs/tree/master/gulp-demo
A partir de gulp 4 se incluyen algunas instrucciones nuevas. Las más relevantes son gulp.series() y gulp.parallel() para facilitar la definición de flujos de tareas. Aunque esto se podía realizar en la versión anterior de Gulp, de esta manera el código queda más claro y flexible.
6. El gulpfile.
En primer lugar debemos de crear un directorio para el proyecto y dentro crear un módulo de node de la siguiente manera.
mkdir <nombre_proyecto> cd <nombre_proyecto> npm init
A continuación completamos los valores que se solicitan (para el ejemplo basta con el nombre, versión y el entry point) para rellenar el package.json
Después ejecutamos el siguiente comando de npm para instalar gulp en el proyecto y guardarlo en el package.json.
npm install gulp --save-dev
Cuando se bajen dependencias de npm se guardarán por defecto en el directorio node_modules en la raíz de nuestro proyecto, y se incluirán dentro del package.json.
Posteriormente debemos crear un fichero llamado gulpfile.js, que debe de estar en la raíz del proyecto (y estar referenciado en la propiedad main del package.json de nuestro proyecto).
7. Ejemplo de uso.
Una vez que tenemos creados el package.js y el gulpfile.js podemos definir nuestra primera tarea (la tarea por defecto).
var gulp = require('gulp'); gulp.task('default', function() { console.log('Hello world!'); });
A continuación vamos a ver cómo podemos crear una tarea para minificar código en javascript. Para empezar, vamos a crear un código de prueba simulando el que sería en código real de nuestra aplicación. Creamos un directorio src/js para guardar ahí nuestros ficheros de código. Los llamaremos file1.js y file2.js y contendrán lo siguiente:
file1.js
function add(paramA, paramB) { console.log('This is a sum'); return paramA + paramB; }
file2.js
function substract(arg0, arg1) { console.log('This is a substract'); return arg0 - arg1; }
A continuación modificaremos nuestro gulpfile para añadir la tarea minify, que deberá concatenar y ofuscar el código. Para ello haremos uso de dos plugins de gulp, que son gulp-concat y gulp-uglify, que se importan en el gulpfile mediante require.
npm install --save-dev gulp-concat npm install --save-dev gulp-uglify
var gulp = require('gulp'); var concat = require('gulp-concat'); var uglify = require('gulp-uglify'); gulp.task('minify', function() { console.log('minifying js ...'); return gulp.src('src/js/*.js') .pipe(concat('all.js')) .pipe(gulp.dest('build/js/')); }); gulp.task('default', function() { console.log('Hello world!'); });
Para empezar concatenamos el código tomando como fuente todos los ficheros javascript del directorio src/js, escribiendo el resultado en el fichero all.js en el directorio build.
Ahora modificamos la tarea para además de concatenar, ofuscar el código.
gulp.task('minify', function() { console.log('minifying js ...'); return gulp.src('src/js/*.js') .pipe(concat('all.js')) .pipe(uglify()) .pipe(gulp.dest('build/js/')); });
En este caso además de ofuscar el código queremos que se eliminen todos los comentarios que se generan con el console.log(). Para ello hay una propiedad en el plugin uglify que permite hacerlo. En general, cuando utilicemos plugins podemos buscar su documentación, como veremos en el punto siguiente(https://www.npmjs.com/package/gulp-uglify)
Finalmente añadimos la opción drop_console en el plugin uglify para quitar los comentarios y aprovechamos para introducir un watcher que ejecute la tarea minify y añadimos la dependencia del watcher en la tarea por defecto. De esta manera cuando ejecutemos la tarea gulp se ejecutará el watcher y cuando se modifique algún fichero del código fuente se aplicará la tarea de minificación.
var gulp = require('gulp'); var concat = require('gulp-concat'); var uglify = require('gulp-uglify'); gulp.task('minify', function() { console.log('minifying js ...'); return gulp.src('src/js/*.js') .pipe(concat('all.js')) .pipe(uglify({ compress: { drop_console: true } })) .pipe(gulp.dest('build/js/')); }); gulp.task('watch-js', function() { gulp.watch('src/js/*.js', ['minify'], function() { console.log('watching js changes...'); }); }); gulp.task('default', ['watch-js'], function() { console.log('Executing gulp...'); });
Y el resultado final es este..
8. Plugins.
Gulp tiene alrededor de ~2346 plugins “oficiales” y otros no-oficiales. Los plugins disponibles permiten realizar tareas muy versátiles como:
En el ejemplo anterior hemos visto los plugins gulp-concat y gulp-uglify. A continuación vamos a mostrar algunos más.
var gulp = require('gulp'); var zip = require('gulp-zip'); gulp.task('default', function() { return gulp.src('src/*') .pipe(zip('archive.zip')) .pipe(gulp.dest('dist')); });
var gulpif = require('gulp-if'); var uglify = require('gulp-uglify'); var condition = true; // TODO: add business logic gulp.task('task', function() { gulp.src('./src/*.js') .pipe(gulpif(condition, uglify())) .pipe(gulp.dest('./dist/')); });
var gulp = require('gulp'); var git = require('gulp-git'); // Run git commit without checking for a // message using raw arguments gulp.task('commit', function(){ return gulp.src('./git-test/*') .pipe(git.commit(undefined, { args: '-m "initial commit"', disableMessageRequirement: true })); }); // Run git push gulp.task('push', function(){ git.push('origin', 'master', function (err) { if (err) throw err; }); });
Podemos encontrar documentación de los mismos en:
9. Conclusiones.
En los nuevos desarrollos con los frameworks javascript este tipo de herramientas son muy útiles para ayudarnos a ser productivos. Este tipo de herramientas junto con otros elementos del ecosistema javascript, como pueden ser la gestión de dependencias (con npm, bower, jspm, etc.), el uso de frameworks js (AngularJs, Flux, Marionette, etc.) la realización de tests (Jasmine, Protractor) un IDE amigable y ligero (Sublime, Atom, Webstorm, etc.) hacen que el desarrollo en javascript sea algo muy distinto a lo que era hace unos años.
En particular Gulp (2014) no fue la primera herramienta relevante de este tipo en aparecer, fue Grunt (2012). Aunque Grunt sigue gozando de una gran popularidad, una gran cantidad de plugins desarrollados y una amplia comunidad de desarrolladores, Gulp es superior a Grunt en varios aspectos, lo cual hace que ya en este momento y en el futuro Gulp sea la herramienta de automatización de tareas predominante.
En primer lugar Grunt tiene un enfoque de configuración sobre código, mientras que Gulp tiene un enfoque de código sobre configuración. Esto hace que Gulp sea mucho más conciso y claro, mientras que Grunt es más verboso. En segundo lugar Gulp es mucho más eficiente, por su concepción basado en el uso de streams implica operaciones de lectura y escritura en memoria. En el caso de Grunt, para encadenar varias operaciones en una tarea se realizan varias lecturas y escrituras en disco.
Gulp es muy sencillo de aprender, pero en proyectos grandes debemos de tener cuidado en la definición y organización de nuestras tareas y las dependencias entre ellas.
Hola Álvaro,
No me convence mucho lo de instalar con sudo.
En MacOS y en Linux, la mejor forma de instalar Node (npm), es mediante nvm. De esta forma, no se instalará en carpetas que necesitas permisos de root.
El problema de instalarlo con sudo, es que todo lo que se baje en node_modules será con otro propietario. Y luego cada dos por tres te pedirá que lo ejecutes con sudo, porque si no no tiene permisos.
Luego podrás usar npm install –global gulp-cli porque el node_modules de global sí tendrás permiso para escribir en él.
Hola Juan Antonio,
Me parece muy bien tu comentario. Se podría mejorar el paso de la instalación haciéndolo con nvm como dices. Aunque para el alcance de este tutorial me he centrado más en lo que es en el API en sí y cómo se definen las tareas. Aprovecho tu sugerencia para recomendar el tutorial de Rubén Aguilera (http://adictosaltrabajo.com/tutoriales/como-montar-un-entorno-de-desarrollo-para-typescript/) donde esta parte lo hace de manera similar a lo que comentas.
[…] Para utilizar esta herramienta es necesario tener instalado Node.js y Gulp. Para poder comprender este tutorial se necesita como requisito al menos unas nociones de cómo funciona Gulp y como se definen y ejecutan tareas (Tutorial “Primeros pasos con Gulp”.). […]