Desarrolla en javascript más cómodamente usando grunt

Una de las cosas que resulta confusa cuando uno empieza a desarrollar con javascript, especialmente si viene del mundo .NET, es la falta de un conjunto de herramientas estándar. Es verdad que las últimas versiones de Visual Studio ofrecen muchas opciones que nos ayudan con el desarrollo de aplicaciones javascript, pero a veces puede resultar una herramienta demasiado pesada y con unos flujos de trabajo más apropiados para otros lenguajes como C# que para javascript.

En mi anterior post veíamos que existen herramientas basadas en node.js para facilitarnos la vida al desarrollar aplicaciones en javascript, incluso aunque se tratase de aplicaciones puramente cliente que no utilizasen node.js para nada.

Lo malo es que todas las herramientas que vimos había que ejecutarlas secuencialmente cada vez que introducíamos cambios en el código, lo que hacía que el flujo de trabajo no fuese todo lo eficiente que podría ser. En este post vamos a ver cómo mejorar ese flujo de trabajo que nos resulte mucho más cómodo y ser más productivos.

Automatizar la «compilación» de Javascript usando Grunt

En .NET podemos usar MSBuild para crear un script de compilación automatizado. Con Grunt podemos hacer lo mismo, pero para aplicaciones desarrolladas con javascript. Grunt nos permite automizar toda la ejecución de tareas relacionadas con nuestra aplicación, como la validación del código, la ejecución de tests, etc.

Vamos a ver un ejemplo de uso de Grunt, e igual que hicimos con MSBuild, lo primero que vamos a hacer es definir una estructura de carpetas sobre la que trabajar. Existen varias maneras de organizar el código y en github podéis encontrar unos cuantos ejemplos de ellas, pero para nuestra pequeña prueba vamos a usar la siguiente:

Estructura de carpetas con grunt

La carpeta src contendrá el código javascript. En carpeta spec es donde iremos colocando los tests (porque ya sabes lo importantes que son los tests en javascript, ¿verdad?), y en la carpeta build es donde acabará el resultado final: nuestro código javascript correctamente concatenado y minificado para que resulte lo más ligero posible a la hora de usarlo. La carpeta node_modules contendrá todas las herramientas y librerías que vayamos instalando con npm, y que en nuestro caso sólo serán de utilidad durante el desarrollo de la aplicación.

En la carpeta raíz del proyecto deberemos tener el fichero Gruntfile.js, con nuestra configuración de tareas para grunt, y un fichero package.json con la información del proyecto y sus dependencias. Para generar el fichero package.json inicial debemos ejecutar npm init y responder a las preguntas que van apareciendo.

Todo el código está disponible en github, así que si hay algo que no te queda claro, lo puedes mirar allí.

Nuestro objetivo va a ser crear un script que nos permita realizar los siguientes pasos:

  1. Ejecutar JSHint para detectar posibles errores en el código.
  2. Ejecutar una suite de tests creada con jasmine.
  3. Usar UglifyJS2 para concatenar los ficheros, minificarlos y generar un source map para luego poder depurar cómodamente.

Lo primero que necesitamos es instalar las herramientas de línea de comandos de grunt:

npm install -g grunt-cli

Es recomendable instalarlas con el flag -g para que se instalen globalmente y se añadan al path, lo que hará más sencillo su uso posterior. A continuación, instalamos ya localmente el resto de librerías que vamos a utilizar:

npm install grunt --save-dev
npm install grunt-contrib-jshint --save-dev
npm install grunt-contrib-jasmine --save-dev
npm install grunt-contrib-watch --save-dev
npm install grunt-contrib-uglify --save-dev

El parámetro --save-dev hace que npm actualice el fichero package.json que mencionaba antes incluyendo estas dependencias como dependencias de desarrollo. De esta forma, si eres de los que cree que no se deben subir las dependencias externas al control de código fuente, luego puedes reinstalarlas al descargar el código ejecutando:

npm install

Una de las cosas que más me gusta de desarrollar en javascript es que todo es javascript. Si en MSBuild necesitábamos manejar una sintaxis especial basada en xml para compilar nuestro proyecto en C#, con grunt el fichero de configuración, Gruntfile.js se define usando código javascript.

La estructura básica de este fichero, explicada en los comentarios del código, es la siguiente:

module.exports = function(grunt) {
  
  // Toda la configuración de grunt se define a través del método
  // initConfig, que recibe un objeto con las distintas opciones
  // de configuración y nos permite definir las tareas que queremos
  // ejecutar
  grunt.initConfig({

    pkg: grunt.file.readJSON('package.json'),

    // Cada tarea lleva su propia configuración, dependiente del 
    // tipo de tarea concreta
    jshint: { /* ... */ },
    uglify: { { /* ... */ },
    jasmine: { /* ... */ },
    watch: { /* ... */ }

  });

  // Con loadNpmTasks podemos cargar "plugins" para grunt que 
  // aumentan su funcionalidad
  grunt.loadNpmTasks('grunt-contrib-jshint');
 
  // Finalmente podemos definir tareas que consisten en ejecutar
  // otras tareas secuencialmente
  grunt.registerTask('default', ['watch']);
  grunt.registerTask('build', ['jshint', 'jasmine', 'uglify']);
};

Espero que con los comentarios se entienda más o menos la idea. Por si acaso os recuerdo que aquí tenéis el código completo de Gruntfile.js.

No voy a entrar en detalle en las opciones de configuración de cada tarea a realizar porque son muy específicas de cada herramientas y lo mejor es consultar la documentación correspondiente, pero vamos a ver un ejemplo con la configuración de jasmine para entender mejor la filosofía.

La configuración de jasmine es la siguiente:

jasmine: {
  build: {
    src: 'src/**/*.js',
    options: {
      specs: 'spec/**/*.js'
    }
  }
}

La configuración de cada tarea se define usando un objeto en el que cada clave se corresponde con una posible configuración. En nuestro ejemplo, cada tarea sólo se ejecuta de una forma predeterminada, que he llamado build, pero podríamos tener distintas configuraciones, por ejemplo para ejecutar jasmine con distintos conjuntos de ficheros, o sobre distintos navegadores, etc.

Podemos ejecutar directamente una tarea usando el comando:

grunt jasmine:build

Si tuviésemos distintas configuraciones para la tarea jasmine, podríamos elegir qué configuración usar jugando con los parámetros de línea de comando, por ejemplo grunt jasmine:slow-tests o grunt jasmine:fast-tests.

Dentro del objeto de configuración encontramos una propiedad src que contiene los ficheros que contiene el código que queremos testear, y otra propiedad options en la que indicamos los ficheros que contienen los tests (o especificaciones, si usamos la terminología BDD de jasmine).

Ésta es la manera habitual de configurar una tarea en grunt. Tenemos algunas propiedades que existen en todas las tareas, como srcFiles, y un objeto option donde se coloca la configuración específica de la tarea.

Las tareas jshint y uglify siguen un patrón similar al que acabamos de ver, por lo que os ahorro la explicación.

Además de estas tareas, en el fichero Gruntfile.js he definido una tarea, watch, que se encarga de lanzar automáticamente el resto de tareas cuando se produce algún cambio en un fichero. Esto es seguramente una de las cosas más útiles de grunt, porque nos permite tener información prácticamente en tiempo real de lo que estamos haciendo y detectar posibles errores muy pronto.

Esta tarea es la que está registrada como tarea por defecto, por lo que será la que se ejecute cuando ejecutemos grunt sin ningún parámetro:

Salida de consola al ejecutar grunt watch

Como véis, el sistema detecta que ha cambiado un archivo (en este caso funcs.js) y a continuación lanza la comprobacion con JSHint, los tests con jasmine y genera el fichero final automáticamente. Para que esto sea realmente útil es importante que las tareas que se ejecuten automáticamente sean rápidas, eso quiere decir que seguramente no sea una buena idea incluir cosas como tests de integración.

Además del modo de ejecución watch, en el script de ejemplo se incluye una tarea, build, que lanza el proceso una única vez. Esta tarea nos permitiría lanzar este script dentro de un servidor de integración continua para generar el resultado final.

Conclusiones

Grunt es una herramienta muy potente, relativamente fácil de configurar (para mi gusto, es más sencilla que MSBuild) y con infinidad de plugins para realizar todo tipo de tareas.

En este post no hemos llegado a ver en detalle todas las posibilidades que ofrece, pero espero que os sirva de base para haceros una idea de la potencia de grunt y cómo nos puede ayudar a mejorar nuestro flujo de trabajo al desarrollar aplicaciones con javascript. El código de ejemplo que podéis encontrar en github os puede servir para entender mejor las cosas.

Cuando desarrollo con javascript, normalmente suelo tener siempre una consola abierta y visible con un grunt ejecutándose en modo watch, y la verdad es que es realmente cómodo poder detectar rápidamente cuando has metido la pata, especialmente en un lenguaje como javascript, con su tipado dinámico y sus, admitámoslo, rarezas.

2 comentarios en “Desarrolla en javascript más cómodamente usando grunt

  1. Quizás sea una pregunta simplona pero no termino de verlo: ¿Cómo estructurarías esto dentro de un proyecto ASP.NET MVC?

    Esta automatización que planteas es muy interesante pero se me escapa el detalle de integrarlo dentro de un proyecto y también a la hora de ejecutar estas tareas en una aplicación de integración continua. En mi caso utilizo CruiseControl.NET.

  2. Juan María Hernández dijo:

    Hola Bernardo,

    Son preguntas completamente lógicas.

    Si tienes un proyecto con ASPNET MVC, tienes ya una dependencia muy fuerte del Visual Studio y hay partes de este flujo de trabajo que posiblemente sea más cómodo realizar desde Visual Studio, por ejemplo la parte relativa a la minificación y concatenación la puedes hacer mediante bundles. Aun así, hay otras cosas, como el JSHint o el continuous testing a las que les puedes sacar mucho partido.

    Al final el montaje es sencillo, tienes un par de carpetas con los «fuentes» y los tests de javascript, y haces que el grunt trabaje contra ellas. A Visual Studio le va a dar igual. Si al final decides que la parte de minificación la quieres hacer también con grunt, lo que puedes hacer es tener una estructura de carpetas parecida a la que pongo en el post, y generar el fichero final sobre una carpeta concreta que luego referencias desde tu proyecto de Visual Studio. De esta forma el Visual Studio sólo vería la versión «compilada» de tu librería.

    En cuanto a montarlo con un servidor de integración continua, yo uso también CruiseControl.NET y lo que hago es lanzar grunt desde MSBuild usando un task Exec (tiene algo de truco porque node.js y la consola en windows se llevan un poco mal, lo que hace que capturar la salida de grunt para pintarla en el CCNET sea incómodo).

    Saludos.

Comentarios cerrados.