Tutorial Less: 2. Compilar Less con Gulp

Logo gulpsimbolo masLogo less

¿Que es Gulp?

Gulp es un conjunto de herramientas para automatizar tareas pesadas y repetitivas como compilar, minificar los archivos, validar, … y agilizar el desarrollo.

En Gulp en lugar de configurar la forma de automatizar las tareas, se programan lo que lo hace realmente sencillo a la vez que potente gracias a la extensa cantidad de plugins con la que cuenta (3205 en este momento).

Uno de sus puntos fuertes sin duda es su velocidad ya que gracias al uso de los strems de Node no crea archivos intermedios reduciendo los accesos al disco a lo mínimo necesario.

Instalando Gulp, less,…

Gulp se es instala con Node.js, con lo que el primer paso será instalarlo si no lo tenemos ya, podemos comprobar si lo tenemos instalado con los siguientes comandos.


node -v
v6.11.0

npm -v
3.10.10

Si no lo tenemos lo descargamos desde nodejs.org y lo instalamos

Continuamos con la instalación de Gulp y comprobamos que la instalación ha ido bien comprobando la versión.


npm install -g gulp

gulp -v
[19:11:07] CLI version 3.9.1

Una vez lo tenemos instalado ya estamos listos para empezar a utilizarlo, el primer paso es crear un proyecto ejecutando npm init en el directorio en el que vamos a tener nuestro proyecto, podemos pulsar intro para dejar todas las opción por defecto y al final decimos que si esta ok para que nos genere el package.json.


npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help json` for definitive documentation on these fields
and exactly what they do.

Use `npm install  --save` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
name: (tutorial_less)
version: (1.0.0)
description: Tutorial de Less
entry point: (index.js)
test command:
git repository:
keywords:
author: Ivan Salas
license: (ISC)
About to write to D:\Programacion\programando\htdocs\tutorial_less\package.json:

{
  "name": "tutorial_less",
  "version": "1.0.0",
  "description": "Tutorial de Less",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Ivan Salas",
  "license": "ISC"
}


Is this ok? (yes) yes

Ahora vamos a añadir las dependencias para nuestro proyecto para poder compilar los archivos less y añadir los prefijos, minificar el css resultante y ver los cambios automáticamente en el navegador con Gulp ejecutando los siguientes comandos.


npm install gulp --save-dev 
npm install gulp-less --save-dev
npm install gulp-csso --save-dev
npm install gulp-autoprefixer --save-dev
npm install gulp-livereload --save-dev
npm install gulp-plumber --save-dev
npm install gulp-util --save-dev
npm install gulp-rename --save-dev
npm install gulp-replace --save-dev

Tras la instalaciones de todos los paquetes podemos ver que se han añadido como dependencias al archivo package.json, ahora cuando compartamos el proyecto o queramos instalar las dependencias en otro lugar bastara con que ejecutemos npm install en el directorio en el que está el archivo package.json y se instalaran todas las dependencias que están dentro de devDependencies y de este modo lo hacemos todo con un único comando.


{
  "name": "tutorial_less",
  "version": "1.0.0",
  "description": "Tutorial de Less",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Ivan Salas",
  "license": "ISC",
  "devDependencies": {
    "gulp": "^3.9.1",
    "gulp-autoprefixer": "^4.0.0",
    "gulp-csso": "^3.0.0",
    "gulp-less": "^3.3.2",
    "gulp-livereload": "^3.8.1",
    "gulp-plumber": "^1.1.0",
    "gulp-rename": "^1.2.2",
    "gulp-replace": "^0.6.1",
    "gulp-util": "^3.0.8"
  }
}

Configurar Gulp para trabajar con Less

Configurar quizás no sea la palabra porque en realidad lo que vamos a hacer es programar las tareas usando javaScript así que la «configuración» resulta sencilla y muy fácil de entender aunque sea la primera vez que la veas.

El siguiente paso es crear el gulpfile.js que es el archivo en el que tenemos que definir las tareas que se van a ejecutar para el proyecto. El archivo que vamos a crear para este ejemplo es el siguiente que iremos explicando paso a paso a continuación.


// Dependencias
var gulp = require('gulp');
var less = require('gulp-less');
var autoprefixer = require('gulp-autoprefixer');
var minifyCSS = require('gulp-csso');
var plumber = require('gulp-plumber');
var liveReload = require('gulp-livereload');
var gutil = require('gulp-util');
var rename = require('gulp-rename');
var replace = require('gulp-replace');

// Compila + añade prefijos
gulp.task('css-des', function(){
  return gulp.src('src/less/**/*.less')
    .pipe(plumber(function (error) {
      gutil.log(error.message);
      this.emit('end');
    }))
    .pipe(less())
    .pipe(autoprefixer())
    .pipe(gulp.dest('src/css'))
    .pipe(liveReload())
});

// Compila + añade prefijos + minimiza
gulp.task('css-dist', function(){
  return gulp.src('src/less/**/*.less')
    .pipe(plumber(function (error) {
      gutil.log(error.message);
      this.emit('end');
    }))
    .pipe(less({modifyVars: {'@bg-color': 'red', '@font-color': '#147'}}))
    .pipe(autoprefixer())
    .pipe(rename({suffix: '.min'}))
    .pipe(minifyCSS())
    .pipe(gulp.dest('dist/css'))
});

// Copia html a dist y remplaza la extension .css por .min.css
gulp.task('html-dist', function(){
  return gulp.src('src/**/*.html')
    .pipe(replace('.css">', '.min.css">'))
    .pipe(gulp.dest('dist'))
});

// Observa los archivos .less y cuando cambian ejecuta la tarea css
gulp.task('watch', function() {
  liveReload.listen();
  gulp.watch('src/less/*.less', ['css-des', 'css-dist']);
});

// Distribuir
gulp.task('dist', ['html-dist', 'css-dist']);

// Tarea por defecto si no se indica nada
gulp.task('default', ['watch']);

Son poco más de 50 líneas y en realidad podíamos quedarnos solo con la mitad, pero así es más sencillo ver las posibilidades que nos ofrece Gulp. Vamos paso a paso viendo el archivo para ver cómo funciona.

En las primeras líneas cargamos las dependencias de todos los plugins que vamos a utilizar.


// Dependencias
var gulp = require('gulp');
var less = require('gulp-less');
var autoprefixer = require('gulp-autoprefixer');
var minifyCSS = require('gulp-csso');
var plumber = require('gulp-plumber');
var liveReload = require('gulp-livereload');
var gutil = require('gulp-util');
var rename = require('gulp-rename');
var replace = require('gulp-replace');

Las tareas se definen con gulp.task('nombre_tarea', function(){}); y dentro de la función pues se realizan las funciones que corresponda.

En este caso empezamos con gulp.src() que define los archivos que van a ser la entrada de nuestra función que en nuestro caso son los archivos con extensión .less que estén dentro de src/less.


// Compila + añade prefijos
gulp.task('css-des', function(){
  return gulp.src('src/less/**/*.less')
    .pipe(plumber(function (error) {  // Control de errores
      gutil.log(error.message);
      this.emit('end');
    }))
    .pipe(less())   // Compila los ficheros less
    .pipe(autoprefixer()) // Añade los vendor prefixes
    .pipe(gulp.dest('src/css')) // Guarda el resultado en src/css
    .pipe(liveReload()) // Avisa al navegador de que algo a cambiado para que se recargue
});

Mediante el método .pipe() de Node.js vamos encadenando tareas de forma que la salida de cada función se utiliza como entrada de la siguiente con lo que no es necesario generar archivos intermedios. Dentro del método .pipe() vamos llamando a los plugins para que realicen su función en un orden lógico, la forma de llamarlos es mediante el nombre que le hemos dado al cargar las dependencias. El motivo de usar plumber es para controlar las excepciones cuando algo falle para que cuando llamemos a esta tarea para que se ejecute cuando se produzca algún cambio y algo falle porque el css sea incorrecto se muestre en el log el error pero que siga funcionando porque si no hacemos esto dejará de responder y aunque se corrija el error no se va a volver a ejecutar.

Con esto ya tenemos todo lo necesario para hacer la compilación usando el comando gulp css-des (gulp nombre_tarea).

Y ahora nos pasamos al final del archivo donde estamos definiendo la tarea default que es la tarea que se va a ejecutar cuando simplemente escribamos gulp y que como parámetro recibe una lista con las tareas que se van a ejecutar (aunque también podría ser una tarea «normal y corriente» y tener un function(){};), en este caso solo ejecuta la tarea watch.


// Tarea por defecto si no se indica nada
gulp.task('default', ['watch']);

En la tarea watch hacemos 2 cosas, primero añadimos liveReload (para ver los cambios es necesario tener el plugin de LiveReload instalado y habilitado en nuestro navegador) para que cuando se produzca un cambio se lo pueda notificar al navegador (lo hacemos en la tarea css-des después de generar los nuevos css) y watch observamos los cambios de los ficheros less y ejecutamos las tareas css-des y css-dist aunque no es el mejor ejemplo del mundo porque son 2 tareas sobre los mismos archivos vemos cómo podemos ejecutar varias tareas a la vez y en este caso por un lado generamos el css para desarrollo que no lo minimizamos y hacemos la llamada a liveReload porque en el proceso de desarrollo queremos ver los cambios automáticamente sin necesidad de recargar la página manualmente y por el otro estamos generando el css minimizado que será el que distribuiremos y le añadimos la extensión .min para remarcar que lo está.


// Observa los archivos .less y cuando cambian ejecuta la tarea css
gulp.task('watch', function() {
  liveReload.listen();
  gulp.watch('src/less/**/*.less', ['css-des', 'css-dist']);
});

Uno de los principales puntos fuertes de los preprocesadores css son las variables, pero tienen el inconveniente de que se sustituyen por su valor cuando se compilan a css por lo que si queremos tener por ejemplo dos templates con diferentes colores tenemos que modificar las variables antes de compilarlo o bien sacar las variables a archivos externos y crear un .less base para cada template que solo contenga imports y en el que solo cambia el import del archivo con las variables si esta fuese la única diferencia, normalmente esta será la opción mas recomendada porque se mantiene toda la configuración en less, pero también existe la posibilidad de modificar los valores pasándoselos directamente al compilador con modifyVars como está hecho en la tarea css-dist .pipe(less({modifyVars: {'@bg-color': 'red', '@font-color': '#147'}})) de forma que no necesitamos tener múltiples archivos con los valores de las variables para cada una de las configuraciones y así se pueden generar estilos que cambien muy a menudo o en los que la cantidad de posibles configuraciones sea excesiva y que de otra forma serían muy tediosos de generar.

Y para terminar una tarea muy sencilla que pasa los archivos html al directorio dist y sustituye en la importación de los estilos la extensión .css por .min.css que es la que se va a distribuir, esto mismo lo podríamos usar por ejemplo para setear valores a las variables de Less.


// Copia html a dist y remplaza la extension .css por .min.css
gulp.task('html-dist', function(){
  return gulp.src('src/**/*.html')
    .pipe(replace('.css">', '.min.css">'))
    .pipe(gulp.dest('dist'))
});

También podemos crear tareas concentradoras, agrupadoras, aglutinadoras o como las queramos llamar para ejecutar múltiples tareas a la vez.


// Distribuir
gulp.task('dist', ['html-dist', 'css-dist']);

Y bueno ya que hemos llegado hasta aquí pues vamos a ver cómo se puede hacer una tarea dependiente de otra, es decir, que para que se pueda ejecutar es necesario que se ejecute previamente la tarea de la que depende.

Aunque no lo había comentado cuando se ejecutan varias tareas como en el caso anterior todas se ejecutan a la vez de forma asíncrona y si son independientes esta genial porque terminaran antes pero si tienen dependencias llegan los problemas, pero por suerte hacer una tarea dependiente de otra es muy sencillo, solo hay que añadir un segundo parámetro después del nombre de la tarea con la lista de tareas de las que depende y de este modo cunado ejecutemos la tarea primero se ejecutaran las tareas de las que depende.


gulp.task('fin', ['dist'], function () {
  console.log('Ya estamos listos!!')
});

El resultado de la ejecución sería el siguiente:


gulp fin
[01:01:29] Using gulpfile D:\Programacion\programando\htdocs\tutorial_less\gulpfile.js
[01:01:29] Starting 'html-dist'...
[01:01:29] Starting 'css-dist'...
[01:01:29] Finished 'html-dist' after 105 ms
[01:01:29] Finished 'css-dist' after 105 ms
[01:01:29] Starting 'dist'...
[01:01:29] Finished 'dist' after 15 μs
[01:01:29] Starting 'fin'...
Ya estamos listos!!
[01:01:29] Finished 'fin' after 520 μs

Y para terminar el proyecto de ejemplo que hemos creado para descargar .