Primeros pasos con Grunt para diseñadores web


En el día a día de un Frontend, hay que realizar un montón de tareas comunes en el desarrollo de un proyecto web utilizando, en muchos casos, diferentes aplicaciones.

Compilar los archivos de preprocesadores, optimizar las imágenes o reducir el tamaño de los archivos de Javascript ocupan un buen espacio de tiempo en nuestro flujo de trabajo.

Grunt

Si queremos automatizar y evitar tener que realizar todos esos procesos repetitivos podemos utilizar Grunt, un ejecutor de tareas de Javascript.

Para trabajar con Grunt, necesitamos tener instaladas 3 2 cosas:

  1. Node.js , un sistema para poder ejecutar Javascript fuera del navegador.
  2. npm (Node Packaged Modules) para manejar dependencias en Node.js Desde la versión 0.6.3 npm viene integrado en el proceso de instalación de Node.js, así que no tienes que instalarlo por separado. *Gracias a mi tocayo @FelixOrtegaM
  3. El propio Grunt primero de forma global y luego como módulo dentro de cada proyecto.
Puede que todo esto te suene a chino y empieces a pensar que no es para tí, pero tranquilo/a, para trabajar con Grunt no es necesario conocer Node.js a fondo, ni ser un experto en Javascript (yo no lo soy), es el resultado final lo que importa y te animo a seguir leyendo.

nodejs_npmjs

La instalación de Node.js es muy sencilla tan solo hay que visitar la web (Nodejs) y descargar la aplicación dependiendo de tu sistema operativo.

A continuación instalaremos Grunt en nuestra máquina de forma global escribiendo en la consola:

npm install -g grunt-cli

Usando Grunt en un proyecto

Vamos a partir de una carpeta como haríamos en cualquier proyecto con la siguiente estructura:

estructura de carpeta inicial

Para poder controlar las dependencias y los metadatos del proyecto tenemos que crear un primer archivo llamado package.json que estará situado en la carpeta raíz y cuyo contenido inicial será el siguiente:

{
"name": "nombre-proyecto",
"version": "0.0.1"
}

Podemos modificar y personalizar los valores de nombre y versión con los que queramos.

Ahora hay que trabajar dentro de la carpeta de nuestro proyecto desde la consola, por lo que nos situaremos en ella. La forma más rápida de completar este paso es escribir “cd” mas un espacio en blanco, arrastrar la carpeta del proyecto dentro de la propia consola y pulsar Enter.

situarse_en_carpeta

Una vez dentro del proyecto, instalaremos el módulo de Grunt, escribiendo:

npm install grunt --save-dev

Si abrimos ahora el archivo packaged.json creado anteriormente, veremos que se ha actualizado incluyendo el módulo de Grunt como dependencia.

{
  "name": "nombre-proyecto",
  "version": "0.0.1",
  "devDependencies": {
    "grunt": "~0.4.5"
  }
}

También se habrá creado una carpeta en la raiz del proyecto llamada node_modules donde se irán almacenando todos los plugins que vayamos instalando.

El siguiente paso es crear un segundo archivo, que cuelgue también de la raíz al que llamaremos Gruntfile.js. Es el encargado de la carga de los plugins además de incluir la configuración de cada una de las tareas. Su contenido inicial será:

module.exports = function(grunt){
 
    grunt.initConfig({
        
    });
 
};

Hasta aquí las instalaciones y configuraciones básicas, ahora es el momento de las tareas.

Minificar los archivos de Javascript

Cada una de las tareas en Grunt sigue el mismo patrón:

1) Instalar el plugin desde la consola

Para minificar los archivos de Javascript usaremos el plugin grunt-contrib-uglify y para instalarlo hay que escribir esto en la consola:

npm install grunt-contrib-uglify --save-dev

Como hemos visto antes, se actualizará el archivo packaged.json con la nueva dependencia.

2) Cargar la tarea en el archivo Gruntfile.js

Abrimos el archivo Gruntfile.js y añadimos:

grunt.loadNpmTasks('grunt-contrib-uglify');

3)  Configurar la tarea también desde Gruntfile.js

Este paso será personalizado para cada tarea y lo ideal es consultar la documentación de cada plugin que suele estar incluida en GitHub.

module.exports = function(grunt) {
 
  grunt.initConfig({
 
    uglify: {
      my_target: {
        files: {
        '_/js/scripts.min.js': ['_/components/js/vendor/*.js','_/components/js/scripts.js']
        }
      } //my_target
    } //uglify
  
  });
  
  grunt.loadNpmTasks('grunt-contrib-uglify');
 
};

Por un lado tenemos el nombre de la tarea (uglify) y en su interior añadimos un target (my_target) con las acciones y los archivos necesarios para llevarlas a cabo. Además de los targets es posible que algunas tareas puedan llevar también opciones…lo mejor, como he comentado antes, es consultar la documentación de cada plugin.

En el caso de minificar los archivos de Javascript lo que estamos buscando es unir y comprimir en un único archivo todos los archivos .js que tenemos.

Según la estructura de carpetas del proyecto, los archivos .js que queremos incluir son todos los que están dentro de /_/components/js.

La estructura es del tipo archivo .js final : archivos .js añadidos, estos últimos como son varios, los incluimos en un array. El orden es muy importante para evitar problemas de dependencia, en el caso del ejemplo he incluido primero todos los archivos .js de la carpeta vendor gracias al asterisco y después el archivo scripts.js que está fuera de ella.

Si no nos interesa incluir alguno de los archivos de la carpeta vendor (en mi caso suelo excluir el archivo de Modernizr que siempre cargo en la cabecera de la página), podemos añadir una exclamación antes del nombre del archivo que queramos dejar fuera.

El archivo resultante scripts.min.js estará minificado y se alojará en la carpeta js que está fuera de components.

Es el momento de probar que la tarea funciona, para ello escribimos en la consola:

grunt uglify:my_target

Se habrá creado un archivo nuevo dentro de la carpeta /_/js/ llamado scripts_min.js.

Es posible incluir más de un target por cada tarea, dependerá de lo que queremos conseguir, para diferenciar cada uno de ellos  podemos ejecutar grunt tarea:target. Si solo escribimos grunt tarea, estaríamos ejecutando todos los targets contenidos en esa tarea.

Vigilar los cambios

Si cada vez que cambiamos o modificamos algo de un js tenemos que volver a la consola para ejecutar la tarea, el proceso no es nada efectivo.

Para evitar este paso, tenemos que hacerlo automático a través de otro plugin llamado grunt-contrib-watch.

Este plugin se encargara de vigilar si estamos realizando cambios en algún archivo y en caso afirmativo ejecutará la tarea o tareas que le indiquemos.

La forma de instalarlo, sigue el mismo patrón que hemos visto antes, primero instalamos el plugin:

npm install grunt-contrib-watch --save-dev

A continuación añadimos la tarea a Gruntfile.js:

grunt.loadNpmTasks('grunt-contrib-watch');

Y por último, incluimos la configuración de la tarea, previa consulta a la documentación del plugin. En nuestro caso queremos que cada vez que se modifique alguno de los archivos de la carpeta /_/components/js se vuelva a ejecutar la tarea uglify. Para ello añadimos lo siguiente:

watch: {
 
  scripts: {
 
    files: ['_/components/js/*.js'],
    tasks: ['uglify'],
    options: {
      spawn: false,
    }
 
  } //scripts
  
} //watch

Para ver como funciona, nos vamos a la consola y escribimos grunt watch. A partir de ahora, si hacemos un cambio a un archivo .js contenido dentro de la carpeta components, se actualizará automáticamente el archivo scripts_min.js

Comprimir las imágenes

Una tarea básica, pero laboriosa, es optimizar y comprimir las imágenes de un proyecto para mejorar los tiempos de carga. Con Grunt existe un plugin que se encarga de hacerlo, grunt-contrib-imagemin

Instalación:

npm install grunt-contrib-imagemin --save-dev

Habilitar dentro de Gruntfile.js :

grunt.loadNpmTasks('grunt-contrib-imagemin');

Y configurar:

imagemin: {
 
    main: {
 
      files: [{
        expand: true,
        cwd: '_/components/img/',
        src: ['**/*.{png,jpg,gif,.svg}'],
        dest: '_/img/'
      }]
 
    }
 
}//imagemin

Lo que queremos es que todas las imágenes .gif, .jpg, .png o .svg incluidas en /_/components/img/ se optimicen y se guarden en la carpeta /_/img/.

El plugin contiene diferentes optimizadores para cada tipo de imagen. Para que el proceso se realice automáticamente, hay que recordar añadir un nuevo target a la tarea watch.

watch: {
  images: {
 
    files: ['_/components/img/*.{png,jpg,gif}'],
    tasks: ['imagemin'],
    options: {
      spawn: false,
    }
 
  }//images
}// watch

El principal problema que tiene este plugin es que como tengas muchas imágenes el proceso se hace muy muy lento. Además la tarea vuelve a comprimir todas las imágenes cada vez que se ejecuta así que hay que buscar una solución.

Existe un plugin que se dedica justamente a eso, a detectar qué imágenes se han añadido recientemente a la carpeta y solo trabaja con ellas. Se llama grunt-newer y como siempre, ya sabéis:

Instalación desde la consola:

npm install grunt-newer --save-dev

Añadir a Gruntfile.js:

grunt.loadNpmTasks('grunt-newer');

En este caso no lleva configuración, solo tenemos que añadir la tarea newer dentro de la tarea watch de las imágenes:

watch: {
    images: {
 
        files: ['_/components/img/*.{png,jpg,gif}'],
        tasks: ['newer:imagemin'],
        options: {
            spawn: false,
        }
 
    }//images
}// watch

Sass & Compass

La compilación de los archivos de los procesadores es otra de las funciones que podemos usar con Grunt. Existen plugins para LESS, Sass y Stylus.

En mi caso uso Sass + Compass, por lo que siempre instalo el plugin de este último. *Es necesario tener previamente instalados tanto Sass como Compass.

npm install grunt-contrib-compass --save-dev

Añadimos a Gruntfile.js

grunt.loadNpmTasks('grunt-contrib-compass');

Para gestionar la tarea, solo hay que indicar el valor del archivo de configuración de Compass (config.rb) y en mi caso, siempre activo los source maps. Si quieres saber para que pueden servir los source maps, echa un vistazo a este artículo Depurando Sass desde el navegador

compass: {     
  dist: { 
    
    options: {         
      config: 'config.rb',         
      sourcemap: true       
    } 
    
  }//dist      
}//compass

Por ultimo, añadimos un nuevo target a la tarea watch, para que actualice automáticamente los .css, cada vez que cambiemos cualquier archivo .scss

watch: {
 
...
 
  scss: {       
    files: '_/components/scss/**/*.scss',       
    tasks: ['compass']     
  } //scss
  
...
 
//watch

En el valor de files, con el doble asterisco le estamos diciendo que incluya todas las carpetas contenidas dentro de scss y con el asterisco que sean todos los archivos .scss.

Añadir y quitar vendor prefixes

Ahora es el turno de un nuevo plugin para poder gestionar los vendor prefixes en las propiedades CSS3, se llama Autoprefixer y es uno de mis favoritos!

Funciona como un post-procesador, analiza el CSS resultante de la compilación con Sass y añade o quita los vendor prefixes usando la base de datos de Can I Use.

Instalar:

npm install grunt-autoprefixer --save-dev

Añadir a Gruntfile.js

grunt.loadNpmTasks('grunt-autoprefixer');

Para gestionar la tarea:

autoprefixer: {
 
  options:{
    browsers: ['last 2 version','ie 9']
  },
 
  single_file: {
    src: '_/css/style.css'
  }
 
}, //autoprefixer

Aquí le estamos diciendo que analice el archivo final compilado de Sass y tome en cuenta las dos ultimas versiones de cada navegador descartando cualquier versión anterior a Internet Explorer 9.

Para que el proceso sea automático, añadimos también la tarea dentro de watch, justo después de la compilación de Compass.

watch: {
  
...
  
  scss: {
    files: '_/components/scss/**/*.scss',
    tasks: ['compass','autoprefixer']
  } //scss
 
...
 
}//watch

Sincronizar en el navegador y otros dispositivos

Para finalizar, vamos a instalar Browser-Sync que nos va a permitir ver una versión sincronizada de nuestras páginas en el navegador, actualizándose cada vez que realicemos un cambio en algún archivo ya sea un HTML, un js, un CSS o una imagen.

Además, gracias a la URL que nos proporciona, podemos comprobar en otros dispositivos (tabletas y móviles) como va quedando nuestra página.

Instalamos desde la consola:

npm install grunt-browser-sync --save-dev

Habilitamos el plugin en Gruntfile.js

grunt.loadNpmTasks('grunt-browser-sync');

En la configuración queremos que sincronice todos los archivos de imágenes, los .css, los .js, los .html y los .php, si es que los utilizamos. Como estamos usando la tarea watch es necesario añadir una opción con el valor watchTask:true

browserSync: {
 
  dev: {
  
    bsFiles: {
      src : [
        '_/css/*.css',
        '_/img/**/*.jpg',
        '_/img/**/*.png',
        '_/img/**/*.svg',
        '_/js/**/*.js',
        '**/*.php',
        '**/*.html'
        ]
      },
  
    options: {
        watchTask: true,
        debugInfo: true,
        logConnections: true,
        notify: true,
        proxy: "proyecto.dev:8888",
        ghostMode: {
          scroll: true,
          links: true,
          forms: true
        }
 
    }
  
  } //dev
 
} // browserSync

En mi caso, suelo utilizar un servidor local creando un virtual host al que le asigno un nombre personalizado por cada proyecto, por lo que tengo que incluir una opción de proxy con ese nombre y de esta forma poder compartir la URL que nos genera con el resto de dispositivos conectados a misma red.

Registrar la tarea o tareas por defecto

Un último paso necesario es registrar la tarea/s que queramos que Grunt ejecute por defecto (default). Para ello añadimos al final de nuestro archivo Gruntfile.js:

grunt.registerTask('default', ["browserSync", "watch"]);

Así cada vez que escribamos grunt en la consola, estaremos ejecutando la tarea browserSync y watch, en ese orden.

Esto es solo una muestra de lo que se puede hacer, actualmente existen mas de 3.000 plugins de Grunt, puedes consultarlos todos a traves de este listado. Si el trabajo con la consola no te asusta y quieres ahorrarte la licencia de algunas aplicaciones, te animo a dar el salto y empezar a usarlo.

Termino dejando un pequeño boilerplate que utilizo para comenzar algunos proyectos y que incluye todos los plugins vistos en este artículo. Una vez clonado o descargado, te sitúas con la consola dentro de la carpeta y ejecutas npm install para que se instalen todos los plugins.