En este artículo vamos a hablar sobre Grunt. Es un Task Runner, que en español libre significa “Ejecutor de Tareas”. Fue creado en 2012 y hoy tiene alrededor de 423.000 descargas semanales en npm. Pero, ¿cómo nos ayuda? Nos asiste en una cuestión muy importante, la automatización.
¿Qué es automatizar?
La palabra automatizar significa básicamente convertir algún servicio en una tarea automática. Es decir, permitir que un servicio repetitivo se vuelva más simple de realizar, de manera mecánica. Esto nos ayuda a desarrollar el servicio de manera más ágil y optimizada.
La automatización es algo que el ser humano está acostumbrado a crear. Piensa solo en dos ejemplos: 1) piensa en cómo se fue reconociendo con el tiempo la necesidad de crear puertas de garaje automáticas, para que no tuviéramos que bajarnos de nuestros coches para abrirlas y cerrarlas, o 2) piensa en cómo fue necesario crear un destornillador eléctrico, para que el servicio que antes llevaba algunos minutos, pudiera ser realizado en pocos segundos. Estos dos ejemplos son muy diferentes entre sí, pero ambos tienen en común la automatización.
Cuando trabajamos con el Desarrollo Web no es diferente. Realizamos muchas tareas que pueden poco a poco volverse repetitivas. Ve algunos ejemplos: 1) sabemos que es muy interesante tener un archivo CSS o JS que contenga todos los códigos desarrollados en esos lenguajes. Esto ayuda a que nuestro archivo HTML sea más pequeño. 2) También se recomienda que eliminemos todos los espacios y saltos de línea de nuestros archivos externos, es decir, minificar los archivos. Esto es importante para que nuestro sitio sea más ligero. Estas dos tareas son necesarias, pero al mismo tiempo son repetitivas. ¿No sería bueno si pudiéramos automatizarlas? ¡Con Grunt es posible!
¿Cómo instalar Grunt?
Fue construido completamente en JavaScript y se ejecuta en el Terminal. Es necesario tener Node para instalarlo (usando npm) y para ejecutarlo también. Si no tienes Node, podrás ver cómo instalarlo en uno de nuestros videos en YouTube, haciendo clic aquí.
Después de instalar Node, vamos a instalar Grunt de hecho en nuestra computadora. Para ello, abre un Terminal y ejecuta el siguiente comando:
npm install grunt-cli -g
Node usará nuestra Internet para descargar Grunt CLI e instalarlo de manera global en nuestro sistema. De este modo, podremos utilizar sus recursos en cualquier proyecto que deseemos.
Vamos a crear un pequeño proyecto para que podamos probar Grunt. Este proyecto contendrá una carpeta llamada assets, que almacenará nuestros archivos CSS y JavaScript. Cada archivo CSS tendrá estilos específicos para una sección de nuestro sitio. Y cada archivo JavaScript contendrá una función útil para nuestro sistema. También tendremos una carpeta llamada dist, que contendrá los archivos de terceros. La estructura de carpetas quedará así:
Grunt trabajará bastante con Node, principalmente descargando dependencias. Así, será necesario iniciar un proyecto Node en este directorio. Para ello, vamos a ejecutar el siguiente comando:
npm init -y
Agregando Grunt al Proyecto
Para agregar Grunt a nuestro proyecto, vamos a descargarlo. Tal vez nos preguntemos: “¿Pero no instalamos Grunt ya hace un momento de manera global?” En realidad, ese era Grunt CLI, responsable de realizar la llamada de los comandos de Grunt desde la línea de comando. Sin embargo, el Grunt de hecho, que nos dará todos los recursos de automatización, se instalará ahora. Haremos esto con el siguiente comando:
npm install grunt
El siguiente paso es crear el archivo Gruntfile.js. En él agregaremos todos los códigos relacionados con las funciones de Grunt. Como estamos trabajando con Node.js, exportaremos un módulo en este archivo. La sintaxis básica será la siguiente:
module.exports = grunt => {
};
Recordando que enseñamos todo sobre módulos nativos y módulos de terceros en nuestro Curso Completo de Node.js, donde desarrollamos proyectos increíbles usando Node.
Ahora vamos a crear nuestra primera automatización con Grunt. ¿Recuerdas que hablamos sobre la buena práctica de juntar todos los archivos CSS y JS en uno solo? Grunt tiene un módulo que hace eso, grunt-contrib-concat. Para usarlo, es necesario instalarlo usando npm. Vamos a ejecutar lo siguiente en el Terminal:
npm install grunt-contrib-concat
Luego, en nuestro archivo Gruntfile.js, lo implementaremos en nuestro código usando el método loadNpmTasks(), propio de Grunt. El código quedará así:
module.exports = grunt => {
grunt.loadNpmTasks("grunt-contrib-concat");
};
Para iniciar la configuración de Grunt, usaremos el método initConfig(). Este espera un objeto con las configuraciones. La primera que realizaremos es la del concat. El código quedará así:
grunt.initConfig({
concat: {
}
});
La clave “concat” es un nombre estándar, que viene del módulo de concat. Dentro de esta clave informaremos los archivos CSS y JS que queremos juntar, con el siguiente código:
grunt.initConfig({
concat: {
css: {
src: [
'./assets/css/header.css',
'./assets/css/section.css',
'./assets/css/footer.css'
],
dest: './dist/all.css'
},
js: {
src: [
'./assets/js/site.js',
'./assets/js/utils.js'
],
dest: './dist/all.js'
}
}
});
Explicando: nosotros separamos lo que Grunt ejecutará entre CSS y JS, con dos claves del objeto. Dentro de cada uno de estos objetos tenemos dos opciones:
1 - src. Define cuáles serán los archivos que iremos a juntar. Espera un array de rutas para los archivos.
2 - dest. Define cuál será el archivo final o archivo de destino donde todos los códigos de los archivos anteriores se reunirán. Espera una cadena con la ruta para ese archivo. Algo muy interesante es que si este archivo no existe, lo crea por nosotros.
Vamos a probar este módulo. Volvamos a nuestro Terminal y ejecutemos grunt concat. Se devolverá lo siguiente:
La tarea se ejecutó correctamente, sin errores. Si vamos a la carpeta dist, encontraremos los archivos all.css y all.js, que estarán así:
Algo interesante es que como separamos este módulo en dos namespaces, podemos ejecutar simplemente grunt concat:css y solo los archivos CSS serán afectados. Podemos hacer lo mismo para los archivos JS.
Estos estilos y funciones estaban en archivos separados, pero pudimos juntar sus contenidos a través de Grunt. El módulo concat es muy útil, ¿no crees?
Vamos a aprender otro módulo, relacionado a la minificación de archivos. Comenzaremos con el CSS. Hay un módulo llamado grunt-contrib-cssmin. Para implementarlo en nuestro proyecto, seguiremos los mismos pasos que realizamos con concat.
Primero descargaremos usando npm, con el siguiente comando:
npm install grunt-contrib-cssmin
Realizaremos la solicitud en nuestro código con la siguiente línea de código:
grunt.loadNpmTasks("grunt-contrib-cssmin");
Finalmente, lo agregaremos a nuestra configuración de Grunt. El nombre estándar de su clave es cssmin. Quedará así en nuestro código:
cssmin: {
styles: {
files: {
'./dist/all.min.css': ['./dist/all.css']
}
}
}
Explicando: las claves styles y files son necesarias para que cssmin se ejecute correctamente. Dentro de la opción files, informamos primero la ruta de destino donde se creará el archivo minificado y luego el archivo original, que sufrirá la minificación.
Para probar si todo funciona, ejecutaremos lo siguiente en nuestro Terminal:
grunt cssmin
Se devolverá el siguiente mensaje en el Terminal:
La ejecución fue un éxito. En este momento ya debería haberse creado un archivo all.min.css en nuestro directorio dist. Este será su contenido:
¡Increíble! Nuestro archivo que ya tenía todos sus estilos unidos en un mismo lugar, ahora también está minificado con la ayuda de Grunt. Sin embargo, observa que si cambiamos algo en el archivo CSS original y deseamos repetir el proceso, necesitaremos ejecutar ambos comandos nuevamente. ¿No sería mejor si pudiéramos ejecutar el comando una sola vez y él hiciera todo por nosotros? Esto es posible con Grunt, a través del método registerTask(). Este método registra una tarea personalizada, es como un “escucha de eventos” de Grunt. Necesitamos crear este método fuera de la variable de la función de configuración. Su sintaxis quedará así:
grunt.registerTask('styles', function() {
grunt.task.run(['concat:css', 'cssmin']);
});
Explicación: el primer parámetro es el nombre de la tarea personalizada. Podemos elegir el nombre que deseemos. El segundo parámetro es una función y dentro de ella realizamos la llamada al método nativo de grunt llamado run(). Es como un trigger de Grunt, y dispara la llamada de uno o más módulos. En este caso, realizamos la llamada de los dos módulos relacionados con el CSS.
Para probar si esto funciona, vamos a añadir más un estilo CSS al archivo footer.css. Agregaremos el estilo text-decoration: none, solo como ejemplo. Ahora, basta ejecutar en nuestro Terminal grunt styles, que es la tarea que creamos. Cuando ejecutamos esto en el Terminal, vemos el siguiente retorno:
Nota que las dos tareas se ejecutaron. Ahora, si vamos a nuestros archivos all.css y all.min.css, veremos lo siguiente:
Ambos archivos fueron alterados con la tarea personalizada de Grunt.
El siguiente módulo que vamos a conocer será uno para la minificación de archivos JavaScript. Su nombre es uglify. Repetiremos el proceso de instalación, ya nos estamos volviendo expertos en esto :).
Primero ejecutaremos en nuestro Terminal:
npm install grunt-contrib-uglify
Agregaremos este módulo al proyecto con el método loadNpmTasks():
grunt.loadNpmTasks("grunt-contrib-uglify");
Y ahora crearemos su configuración en el método initConfig():
uglify: {
functions: {
files: {
'./dist/all.min.js': ['./dist/all.js']
}
}
}
Observa que la estructura es bastante similar a cssmin. Solo cambiamos la clave “styles” por “functions”. ¿Qué te parece si practicamos la creación de tareas personalizadas usando registerTask()? Vamos a unir todo lo relacionado con JavaScript usando la tarea “scripts”, que vamos a crear ahora:
grunt.registerTask('scripts', () => {
grunt.task.run(['concat:js', 'uglify']);
grunt.log.ok('¡Todo correcto!');
});
La sintaxis es muy similar a la última tarea personalizada que creamos. Solo una pequeña diferencia: observa que usamos la propiedad .log.ok() en este ejemplo. Esta propiedad de Grunt nos permite escribir mensajes de retroalimentación en el Terminal, informando si todo ocurrió bien o no. El método ok() es para el éxito, pero hay otros métodos:
1 - warn() para informar una alerta.
2 - error() para informar un error.
3 - writeln() para escribir algún mensaje en línea.
4 - header() y subhead() que definen un encabezado para el mensaje.
Regresando a las pruebas, vamos a ejecutar grunt scripts en nuestro Terminal. Este será el resultado:
Nota que el mensaje de éxito regresó en verde, como programamos arriba, y también se nos informó que las dos tareas se ejecutaron con éxito. Si vamos al archivo all.min.js, veremos lo siguiente:
Nuestro código fue minificado correctamente. Si realizamos alguna modificación en nuestros archivos JS originales, solo es necesario ejecutar nuevamente este comando en el Terminal y la compilación ocurrirá de nuevo.
Aprendimos módulos muy interesantes, pero aprenderemos uno más que nos ayuda mucho. Imagina que estamos usando Grunt en nuestro desarrollo y alteramos el archivo CSS. Tendremos que ejecutar en el Terminal la tarea que minimiza el CSS. Si alteramos el JS haremos lo mismo. Sin embargo, estas alteraciones son muy recurrentes, lo que puede hacer que la tarea de ejecutar los comandos en el Terminal sea un poco repetitiva. ¿No sería más interesante tener algún módulo que ejecutara automáticamente la llamada de estas tareas? Para eso existe el módulo watch. Es como un “vigilante” que está atento a los cambios en nuestro código. Cuando guardamos un archivo, ejecuta en el Terminal todas las tareas que ya programamos.
Vamos a implementarlo en nuestra aplicación, repitiendo el proceso que ya hicimos algunas veces. Primero, vamos a ejecutar npm install grunt-contrib-watch en nuestro Terminal. Traeremos este módulo a nuestra aplicación con la siguiente línea de código:
grunt.loadNpmTasks("grunt-contrib-watch");
Y, para configurar su actuación en nuestro método initConfig(), usaremos el siguiente código:
watch: {
css: {
files: ['./assets/css/*.css'],
tasks: ['concat:css', 'cssmin']
},
js: {
files: './assets/js/*.js',
tasks: ['concat:js', 'uglify']
}
}
Nuevamente dividiremos el módulo entre archivos CSS y JS. La propiedad files espera un array con los archivos a los que necesita vigilarlos, es decir, estar atento a los cambios. Observa que tanto en CSS como en JavaScript usamos el * (asterisco), para que watch esté vigilando todos los archivos con las extensiones .css y .js. Y la propiedad tasks espera un array de tareas que deben ejecutarse cuando se guarda un archivo de esa extensión. Básicamente, insertamos las tareas que ya habíamos creado en las tareas personalizadas.
Para probar Grunt, ejecutemos grunt watch en nuestro Terminal. Veremos el siguiente resultado:
Mira que devuelve un mensaje de “Esperando”. Este módulo es diferente porque no cierra su proceso, sino que queda todo el tiempo con el Terminal ejecutándose, esperando cambios. Si ahora modificamos cualquier archivo y lo guardamos, veremos lo siguiente en el Terminal:
Grunt puede percibir cuál archivo fue alterado, en este caso el header.css, qué tareas se ejecutaron y hasta la fecha en que ocurrió. ¡Sensacional, ¿verdad?
Podemos hacer solo un cambio más. El método registerTask() puede crear tareas personalizadas, pero también tiene tareas predeterminadas. Una de esas tareas se llama “default”, que define lo que ocurrirá si ejecutamos solo grunt en el Terminal. Crearemos esta tarea con el siguiente código:
grunt.registerTask('default', ['watch']);
Esto muestra que podemos informar al método registerTask() solo un array de tareas si lo deseamos, en lugar de una función.
Ahora, si ejecutamos solo grunt en el Terminal, veremos lo siguiente:
Ahora la tarea watch se dispara por defecto y podemos dejar esta tarea ejecutándose mientras programamos nuestros proyectos.
Aprendimos algunos módulos de Grunt en este artículo, pero hay muchos más. Puedes encontrar una lista completa de otros módulos o plugins haciendo clic aquí.
También puedes encontrar los códigos desarrollados en este artículo en nuestro GitHub, haciendo clic aquí.
En este artículo aprendimos cómo Grunt puede ayudarnos a automatizar tareas repetitivas en nuestros proyectos y vimos lo práctico que realmente es. Sin embargo, hay otra herramienta que tiene el mismo objetivo y se ha vuelto incluso más conocida que Grunt: estamos hablando de Gulp. Hablaremos más sobre este automatizador en el próximo artículo: Gulp: Automatizando tareas sin Dificultad, que ya sale mañana, ¡te espero allí! :)