Empezar a utilizar Grunt

Como programador front-end hay ciertas tareas que pueden repetirse a diario como:

  • Modularizar el CSS pero posteriormente concatenarlos en unos pocos archivos css en su versión de producción. Lo mismo para el JavaScript.
  • Comprimir el CSS y el JavaScript para conseguir un tamaño menor de los ficheros cuando se ponen en producción.
  • Realizar una optimización de todas las imágenes utilizadas.
  • Compilar el código de Less, Sass, SCSS, etc a código real CSS.

logo de gruntjs

La lista anterior, que desde luego no es una lista completa de las tareas de un front-end, pero son cosas que tenemos que hacer, podemos decir que es una lista de tareas.

Pues bien, Grunt, del que seguro que ya has oído hablar,  resulta que es un gestor de tareas. Grunt puede realizar tareas automáticas, como las indicadas anteriormente,  que apenas requieran de nuestro maravilloso talento. Simplemente tendremos que abrir la línea de comandos, ir hasta el directorio raíz de nuestro proyecto, escribir grunt y darle al return. Así de sencillo. Obviamente es necesario configurarlo antes, y ahí es donde nos podemos quedar atascados ya que en contra de la forma en que lo venden, no es algo tan trivial.

Y como creo que Grunt es más conocido de lo que luego en realidad se usa, he decidido escribir este post en donde todos podamos ver cómo configurarlo para un proyecto web.

Lo primero que debemos saber es que Grunt se ejecuta sobre Node, por lo que deberemos tener Node instalado. La instalación de Node a día de hoy es trivial (siguiente, siguiente, siguiente y  finalizar).

Instalar Grunt

Primero abrimos una ventana de comandos con permisos de administrador y vamos hasta el directorio raíz de nuestro proyecto. Escribimos el siguiente comando:

npm install -g grunt-cli

Con esto instalamos la interfaz de línea de comandos de Grunt de forma global.

grunt-packagejson

 

Después necesitaremos un fichero en la raíz de tu proyecto llamado package.json. Este fichero define cómo Node y NPM trata las dependencias. Una vez tenemos el package.json en su sitio e indicando que grunt es una dependencia, podemos teclear el siguiente comando:

npm install

Y veremos que se crea una nueva carpeta llamada node_modules, que es donde se nos han descargado todas las dependencias necesarias.

grunt-node_modules

Concatenar ficheros con Grunt

Ya tenemos Grunt instalado y vemos que en nuestro proyecto tenemos tres ficheros de javascript responsables de realizar 3 peticiones al servidor que podríamos reducirlos a un sólo fichero y por lo tanto a una sola llamada al servidor.

  • plugins.js
  • main.js
  • carrusel.js

En producción, concatenaremos estos tres ficheros en uno, produccion.js. Tenemos que indicarle a Grunt que lo haga. El plugin oficial de Grunt para concatener ficheros se llama grunt-contrib-concat. En su página podemos leer que para incluirlo como dependencia es necesario ejecutar el siguiente comando:

npm install grunt-contrib-concat --save-dev

Si ahora volvemos a nuestro package.json veremos que se ha añadido la línea:

«grunt-contrib-concat»: «^0.5.1»

Para usar este plugin necesitamos empezar a configurar Grunt para que realmente lo utilice tal y como nosotros queremos. Para ello vamos a crear al mismo nivel que package.json un fichero llamado Gruntfile.js (ojo con la G mayúscula)

module.exports = function ( grunt ) {
  // La configuración de Grunt va aquí
  
  grunt.initConfig({
    pkg: grunt.file.readJSON( 'package.json' ), //lectura del package.json
    
    concat: {
      //La configuración para concatenar los fichero va aquí
    }

  });

  // Le decimos a Grunt que queremos usar grunt-contrib-concat
  grunt.loadNpmTasks( 'grunt-contrib-concat' );

  // Donde le decimos a Grunt qué hacer cuando teclearmos "grunt" en la consola.
  grunt.registerTask('default', [ 'concat' ] );

};

Hay que seguir la sintaxis anterior en donde se puede ver por los comentarios, de qué va el asunto.

concat: {
      dist: {
        src : [ 'js/plugins.js', 'js/carrusel.js', 'js/main.js' ],
        dest: 'build/js/produccion.js'
      }
}

Aquí, en el fichero Gruntfile.js hemos incluido dentro de la tarea concat, la sintaxis necesaria para concatenar los tres ficheros js en uno solo llamado produccion.js que está en la subcarpeta js de la carpeta build. src es de source y dest de destination.

Para ejecutarlo tecleamos

grunt

Y si todo ha ido bien, ¡Mágia!. Grunt pasa a ejecutar nuestra tarea default que lanzará a su vez la tarea ‘concat‘. Vemos que aunque no existía la carpeta build, Grunt la ha generado.

grunt-concat

Dejaré para futuros post cómo hacer que Grunt haga otras tareas muy comunes por nosotros como minificar javascript, optimizar imágenes.

 

 

Empezar a utilizar Grunt

Propiedades enumerables de un objeto javascript

 

Por defecto, todas las propiedades que se añaden a un objeto son de tipo enumerable, por lo cual, se puede iterar por ellas utilizando el loop for-in.

 

var propiedad;
for ( propiedad in objeto ) {
  console.log( "Nombre: " + propiedad );
  console.log( "Valor: " + objeto[ propiedad ] );
}

Si necesitas una lista de propiedades enumerable de un objeto ECMAScript5 introdujo el método Object.keys() que devuelve un array de String con los nombres de las propiedades del objeto.

var propiedades = Object.keys(objeto);

Hay una diferencia entre las propiedades por las que itera el loop for-in y las que devuelve el método Object.keys(). El loop for-in incluye las propiedades prototype del objeto mientras que Object.keys() solo nos proporciona las own properties.

Ten en cuenta que no todas las propiedades son enumerables. De hecho, la mayoría de las propiedades prototype no lo son. Puedes comprobar si una propiedad es enumerable utilizando el método propertyIsEnumerable().

var persona = {
  nombre: "Aitor"
};
console.log( "nombre" in persona ); //true
console.log( persona.propertyIsEnumerable( "nombre" ) ); /true

var propiedades = Object.keys( persona ); //devuelve un Array

console.log( "length" in propiedades ); //operador in devuelve true
console.log( propiedades.propertyIsEnumerable( "length" ) ); //false

Aquí, la propiedad nombre del objeto persona es enumerable. En cambio, la propiedad length del array no es un enumerable ya que forma parte de Array.prototype que heredan todos los arrays, es decir, es una prototype property.

Propiedades enumerables de un objeto javascript

Borrar propiedades de un objeto en javascript

Lo mismo que las propiedades puede añadirse en cualquier momento, también se pueden borrar. El dar un valor de null a la propiedad no hace que ésta se borre realmente. Necesitas usar el operador delete.

jsCuando el operador delete ha borrado la propiedad con éxito, nos devolverá true (algunas propiedades no se pueden borrar).

En el siguiente ejemplo se ve la forma en cómo usar el operador delete.

var persona = {
  nombre: "Aitor",
  edad: 41
};
console.log( "nombre" in persona ); //true
delete persona.nombre;
console.log( "nombre" in persona ); //false
console.log( persona.nombre ); // undefined

En el ejemplo también se puede ver que si se intenta acceder a una propiedad ya inexistente, simplemente nos devolverá undefined, pero no se lanzará ninguna excepción de error.

 

Borrar propiedades de un objeto en javascript

Detectar correctamente propiedades de un objeto JavaScript

jsComo las propiedades en JavaScript pueden ser añadidas a un objeto en cualquier momento, a veces es necesario comprobar si dicha propiedad existe.

Se suele ver a menudo que esta tarea se realiza siguiendo el patrón:

if (persona.edad){
  //hacer algo con la edad
}

El problema con este patrón es que JavaScript evalúa como falso cualquier valor que sea:

  • null
  • undefined
  • false
  • NaN
  • «» (string vacío)

Como la propiedad puede contener uno de los anteriores valores el chequeo anterior puede dar falsos negativos.

Por ejemplo, si la edad de persona fuera 0, la condición se evaluará como false incluso aunque la propiedad exista.

Una forma de realizar la comprobación de si una propiedad existe sería con el operador in. El operador in busca la propiedad en el objeto y devuelve true si la encuentra.

var persona = {
  nombre: "Aitor",
  edad: 41,
  saludar: function(){
    console.log( "Hola, soy " + this.nombre );
  }
};

console.log( "nombre" in persona); // true
console.log( "edad" in persona); // true
console.log( "apellidos" in persona); // false
console.log( "saudar" in persona); // true

Ten en cuenta que los métodos también son propiedades y por lo tanto también puedes usar el operador in para detectarlos, como puedes ver en el ejemplo del método saludar().

En la mayoría de los casos, el operador in es la mejor forma de determinar si la propiedad existe. En otros casos sin embargo, puede que necesites comprobar la existencia de una propiedad no heredada, de las que están en el prototype. Para entendernos, si realizamos

console.log( "toString" in persona)

nos devolverá true.

Si necesitamos detectar si existe una propiedad de las llamadas own properties, deberemos usar el método hasOwnProperty().

console.log( "nombre" in persona); //true
console.log( persona.hasOwnProperty( "nombre" )); //true
console.log( "toString" in persona); //true
console.log( persona.hasOwnProperty( "toString" )); //false

Si hasta ahora has utilizado el patrón mostrado al principio y te están entrando sudores fríos, recuerda que a partir de ahora debes utilizar para detectar si existe una propiedad en un objeto:

  • el operador in (para own properties y prototype properties)
  • el método hasOwnProperty()  (sólo para own properties)

 

Detectar correctamente propiedades de un objeto JavaScript

Utilizando requireJS

Veamos cómo es posible cargar ficheros de javascript según los necesitemos en nuestro proyecto de forma dinámica. Para ello podemos usar la librería requireJS.

Indicar que es necesario hacer el siguiente taller a través de un servidor HTTP, si no requireJS dará error ya que no podrá realizar su cometido.

Creamos un proyecto con un esquema como el siguiente:

require-1

Vamos a importar a través de requireJS las librerías sumar.js y multiplicar.js que vamos a necesitar desde el index. El contenido de estos scripts es:


sumar.js:
function sumar(arg1, arg2){
    return arg1 + arg2;
}

multiplicar.js:
function multiplicar (arg1, arg2) {
    return arg1 * arg2;
}

 

Haremos uso de estas interesantes librerías desde app.js pero en un principo, en el index.html, sólo tenemos importados los scripts de require.js y app.js.

require-3

require.js nos proporciona dos funciones en las que está la clave de todo:

  • define(nombreModulo, modulosRequeridos, definiciónModulo)
  • require(modulosRequeridos, definiciónModulo)

En este caso vamos a ir a lo más sencillo de uso de require.js y sólo vamos a ver la utilización del método require(). Dejo para otro taller el uso de define().

Desde app.js será necesario requerir tanto sumar.js como multiplicar.js. Esto se hace de la siguiente manera (muy al estilo de AngularJS):


app.js:
require([ "js/sumar.js", "js/multiplicar.js" ], function(){
    alert( sumar(5,10) );
    alert( multiplicar(5,10) );
});

Una vez dentro del callback de require() ya podemos disponer de sumar.js y multiplicar.js tal y como si estuvieran importadas.

De esta forma, que es la forma más sencilla de usar requireJS, podemos ahorrarnos el tener que ir con una armadura de scripts constantemente a todas las páginas web que se visiten.

Aquí vemos desde las devtools de Chrome que realmente sumar.js y multiplicar.js se han cargado posteriormente.

require-2

Utilizando requireJS

Versionamiento Semántico

Existe en el desarrollo de software actual, en el que se utiliza un innumerable número de frameworks, micro frameworks, librerías y otras hierbas, surge la pesadilla de mantener una gestion de dependencias entre unas librerías con otras.

semver

Por eso existe una convención para etiquetar las versiones de software que nos conviene mantener. En este post comento de forma resumida en qué consiste dicha convención, llamada SemVer.

La sintaxis de versión es:

 

MAJOR . MINOR . PATCH

 

  1. PATCH: Cuando se producen cambios menores, típicamente arreglos de bugs que en ningún momento interfieren en la compatibilidad con la versión anterior.
  2. MINOR: Cuando se introducen nuevas funcionalidades o unas mejoras notables sobre las funcionalidades anteriores. Tampoco interfiere en la compatibilidad.
  3. MAJOR: Gran cantidad de cambios, o aún siendo pocos cambios, éstos son muy significativos, abandono de soporte a funcionalidades deprecated. Puede interferir, y de hecho lo suele hacer, en la compatiblidad con versiones anteriores.

Así que si en en nuestro proyecto actualizamos a nueva versión major es necesario realizar una batería completa de pruebas. Se pueden utilizar los gestores de descargas de librerías como bower o composer para tener controlada y automatizada la actualización de librerías.

Versionamiento Semántico

Inyectar scripts de forma dinamica

A través de JavaScript se puede crear casi cualquier elemento HMTL e inyectarlo después en el DOM de la pagina. El elemento script no es una una excepción. Un script puede moverse, eliminarse y también crearse mediante programación en JavaScript. Un script puede crearse e incluirse en el DOM utilizando los métodos standard para ello:

var nuevoScript = document.createElement("script");
nuevoScript.type = "text/javascript";
script.src = "/ruta/al/fichero.js";
document.getElementByTagName("head')[0].appendChild(nuevoScript);

Con el código anterior incluiremos en el <head> el archivo fichero.js. El fichero empezará a descargarse tan pronto sea inyectado en el DOM. Lo importante aquí es saber que fichero.js es descargado y ejecutado sin bloquear cualquier otro proceso de la página sin importar donde coloquemos el script. Se puede incluso colocar en el <head> del documento sin afectar al resto de la página (a parte, por supuesto, de utilizar una conexión HTTP para la descarga del fichero).

Es mucho más seguro insertar el script en <head> en vez de en el <body> especialmente si el código se ejecuta durante la carga de la página. Internet Explorer puede dar algún problema …

Utilizar esta técnica cuando queremos descargar un solo script está muy bien. Pero si se descargan varios script (a no ser que no tengan ninguna relación entre ellos) y unos dependen de otros puede suponer un problema ya que no se garantiza el orden de descarga ni ejecución. Si incluimos dos scripts, por ejemplo ficheroA.js y ficheroB.js, y ficheroB.js llama a una función que se encuentra en ficheroA.js, nada garantiza (a pesar de que en código hayas puesto uno antes que el otro) que cuando ficheroB.js llame a la función de ficheroA.js se produzca un error JavaScript por la sencilla razón de que ficheroA.js aún no se había descargado.

La gran mayoría de los navegadores lanzan el evento load cuando el script se ha descargado. Se puede por lo tanto escuchar dicho evento:

nuevoScript.onload = function(){
    iniciarAplicacion();
}

Internet Explorer gestiona la carga de los scripts de una forma alternativa y el evento que lanza es readystatechange.  El script descargado tiene la propiedad readyState que va cambiando durante la descarga. Nos interesa comprobar dos de los cinco valores que esta propiedad puede tener: «loaded» y «complete«.

var nuevoScript = document.createElement("script");
//Internet Explorer
nuevoScript.onreadystatechange = function(){
    if(nuevoScript.readyState == "loaded" || nuevoScript.readyState == "complete"){
        nuevoScript.onreadystatechange = null; //evita que se lance dos veces
        iniciarAplicacion();
    }
}

Así que si juntamos las dos implementaciones y utilizamos feature detection tendremos la siguiente función a la que le podemos pasar una función callback a ejecutar una vez descargado el fichero:


function cargarScript(url, callback){
var nuevoScript = document.createElement("script");
nuevoScript.type = "text/javascript";
if (nuevoScript.readyState){ //Internet Explorer
nuevoScript.onreadystatechange = function(){
if (nuevoScript.readyState == "loaded" || nuevoScript.readyState == "complete"){
nuevoScript.onreadystatechange = null; //evita que se llame mas de una vez
callback();
}
}
} else {//otros navegadores
nuevoScript.onload = function(){
callback();
}
}
nuevoScript.src = url;
document.getElementsByTagName("head")[0].appendChild(nuevoScript);
}

view raw

cargarScript.js

hosted with ❤ by GitHub


cargarScript("/ruta/al/ficheroA.js", function(){
cargarScript("/ruta/al/ficheroB.js", function(){
iniciarAplicacion(); // "iniciarAplicacion" está en ficheroB.js, pero en él hay referencias a ficheroA.js
//En este punto se pueden utilizar los dos JS sin miedo
})
});

Lo importante es darse cuenta que la función callback podría ser la misma cargarScript de nuevo e ir creando un hilo para asegurarnos que las funciones a las que se llaman están disponibles.

Antes de llamar a iniciarAplicacion() se espera a que ficheroA.js y ficheroB.js esten totalmente descargados. Y habremos descargados dos scripts de forma no bloqueante que era de lo que se trataba.

Si estás utilizando mucho este patrón de descarga de scripts, lo mejor es utilizar librerías como http://clickalicious.github.io/Lazyload.js/

Inyectar scripts de forma dinamica

scripts con defer

Ya en HTML4 se definía un atributo para los tags script llamado defer. El atributo defer indica que el script no va a modificar el DOM y por lo tanto su carga se puede realizar posteriormente a crear el árbol del DOM.

script con defer

El atributo defer no está ampliamente soportado en todos los navegadores por lo que su uso necesita de un testeo de las necesidades del desarrollo en concreto.

Un script con defer se puede incluir en cualquier parte del documento. El fichero empezará a descargarse en el punto en el que se encuentra en el código, pero no se ejecutará hasta que el DOM esté completado, justo antes de lanzarse el evento onload.

La descarga del fichero se realiza de forma no bloqueante y otras descargas pueden realizarse en paralelo, disminuyendo así el tiempo de carga de la página.


<html>
<head>
<meta charset="utf-8">
<title>Script con defer. Test</title>
</head>
<body>
<script type="text/javascript" defer>
alert("defer");
</script>
<script type="text/javascript">
alert("script");
</script>
<script type="text/javascript">
window.onload = function(){
alert("load");
}
</script>
</body>
</html>

El código de arriba muestra 3 alertas. Si el navegador soporta scripts en modo defer el orden de éstas será:

  1. «script»
  2. «defer»
  3. «load»

Pero como comentaba, es una pena que una vez más, el poco consenso entre navegadores no permita utilizar sin previos tests el uso del atributo defer.

scripts con defer