Mostrando entradas con la etiqueta javascript. Mostrar todas las entradas
Mostrando entradas con la etiqueta javascript. Mostrar todas las entradas

martes, 11 de marzo de 2014

Browser Sync: instalación y configuración

Browser Sync

Se trata de un plugin disponible para Grunt, mediante el cual podremos testear nuestra aplicación de manera simultánea, en múltiples dispositivos y navegadores.


Con él resolvemos la tediosa tarea de testear manualmente nuestra aplicación, navegador por navegador, y dispositivo por dispositivo. Ahora podremos hacerlo en paralelo, propagando eventos (click, touch, scroll, etc) a través de todos los dispositivos y navegadores que tengamos conectados, de tal modo que iremos viendo cómo se comporta la aplicación, a la vez con todos ellos.

¿Cómo instalarlo?

Simplemente debemos añadirlo al package.json del proyecto:

package.json

{
  ...

  "dependencies": {
    ...
  },
  "devDependencies": {
    ...
    "grunt-browser-sync": "~0.6.0",
    ...
  },

  ...
}

Configurar browser_sync en Grunt

Suponiendo que cada uno de los plugins lo configuras en un archivo .js diferente, haciendo uso de load-grunt-config, el archivo correspondiente a grunt-browser-sync, sería browser_sync.js:

browser_sync.js

module.exports = {
    dev: {
        files: {
            src : ['<%= myapp.rutadev %>/css/commons.css']
        },
        options: {
            watchTask: true
        } 
    },
    pro: {
        files: {
            src : ['<%= myapp.rutapro %>/css/commons.min.css']
        }
    }
}

En este ejemplo, le estamos indicando dos configuraciones diferentes: 'dev' y 'pro':

  • Para 'dev', le pedimos que se quede observando los cambios producidos en commons.css, y actualice la página si se detectan. También vemos la opción watchTask: true. Hay que ponerlo si ya tenemos nuestra propia tarea watch corriendo por otro lado.
  • En el caso de 'pro', sin embargo, no hemos añadido watchTask: true, ya que no ejecutaremos 'watch' con dicha tarea.

Todo depende de cómo tengamos nuestra configuración. Es importante por tanto, añadir watchTask: true, si vas a lanzar 'watch' también. Por ejemplo, si fuesemos a lanzar la tarea 'server_dev':

grunt.registerTask('server_dev', [
    'connect:livereload',
    'browser_sync:dev',
    'watch'
]);

Añadir scripts a nuestros HTML

Cuando lancemos la tarea, por consola nos aparecerá la lista de scripts que debemos inyectar en nuestros HTML, para que todos los dispositivos se sincronicen. Algo como esto:




Una vez añadidos los scripts, abrimos la web con dos navegadores diferentes, y comprobamos que se propaguen los eventos de uno a otro. Ahora si hacemos un cambio en commons.css, se verá el efecto en todos a la vez.

¡Feliz testeo!

lunes, 3 de marzo de 2014

Análisis de memoria 101

Este artículo es básicamente una traducción de dos documentos publicados por Google:

Para entenderla mejor, recomendamos leer antes, algo sobre Montículos y Teoría de Grafos.

Dicha traducción pretende servir de antesala para una serie de traducciones posteriores sobre depuración de memoria en Javascript. Agradezco cualquier comentario sobre errores o mejoras que hayáis podido encontrar en dicha traducción.

Análisis de memoria 101

Esta es una introducción al análisis de memoria. Los conceptos e ideas descritas aquí, son usadas en el Heap Profiler UI y en la correspondiente documentación. Debes conocerlos previamente, para poder usar la herramienta de un modo más efectivo.

Términos generales

Esta sección describe conceptos comunes usados en el análisis de memoria, y es aplicable a una variedad de herramientas de depuración de memoria, de diferentes lenguajes. Si ya tienes experiencia con, por ejemplo, depuradores para Java o .Net, es muy probable que te sientas familiarizado con ellas.

El montículo, es una red de objetos interconectados. En el mundo matemático, esta estructura es llamada grafo. Un grafo, es una red de nodos, unidos por vértices. Ambos, nodos y vértices, tienen nombres. Los nombres de los nodos usan el nombre de la función constructora que fue usada para crearlos, y los vértices usan los nombres de sus propiedades.

La secuencia de vértices que se necesita atravesar para alcanzar un objeto a partir de otro, es denominada "ruta". Normalmente, sólo estaremos interesados en rutas simples, por ejemplo, aquellas que no pasan por el mismo nodo dos veces.

Tamaño de objetos: shallow size y retained size

Piensa en la memoria como un grafo con tipos primitivos (números y cadenas), y objetos (arrays asociativos). Esto puede ser respresentado en una gráfica con un determinado número de nodos, que serían los objetos, interconectados por vértices, como hemos visto anteriormente.

La memoria puede ser utilizada de dos modos:

  • directamente por un objeto, 
  • o implícitamente almacenando referencias a otros objetos, y así, evitando que sean eliminados automáticamente con el recolector de basura (en adelante GC, por sus siglas en inglés de Garbage Collector).

El tamaño de la memoria que es utilizado por un objeto, es llamado "shallow size" (tamaño trivial o efímero). Los objetos propios de JavaScript tienen cierta memoria reservada para sus descripciones y para almacenar sus valores inmediatos.

Normalmente, sólo los arrays y strings, pueden necesitar un mayor tamaño: un mayor shallow size. De cualquier forma, los strings tienen sus propio lugar de almacenamiento en la memoria de renderizado, con lo cual, sólo exponen un pequeño wrapper object en el montículo JavaScript.

De cualquier forma, incluso un objeto pequeño puede requerir una gran cantidad de memoria de manera indirecta, al evitar que otros objetos sean eliminados por el GC. El tamaño de memoria que sería liberado si un objeto, y los que dependen de él, es liberado al borrarse, se denomita "retained size".

Cuando trabajamos con el Heap Profiler (depurador de montículos) en las DevTools de Google Chrome, verás varias columnas de información. Dos de ellas representan el shallow size y el retained size respectivamente.

Rutas retenidas de objetos (retaining paths)

Llamaremos "ruta retenida" (retaining path), a cualquier ruta desde la raíz del recolector de basura (GC) hasta un objeto en particular. Si no existe ruta alguna para poder llegar a un objeto, entonces ese objeto es llamado "inalcanzable", y se pone a disposición del GC para que sea eliminado.

Nuestras rutas empezarán siempre desde la raíz del GC. Existen muchas raíces internas del GC, las cuales no nos interesan demasiado. Como punto de partida para las aplicaciones, tenemos:

  • El objeto global Window, uno para cada iframe. Hay un campo "distancia" en los heap snapshots, que representan el número de propiedades referenciadas desde la raíz de window, yendo por el retaining path más corto.
  • El árbol del DOM, que consiste en todos los nodos alcanzables desde la raíz. No todos pueden tener wrapper objects asociados, pero en caso de que los tengan, dichos nodos permanecerán en memoria mientras permanezca el documento.
  • Objetos retenidos por el depurador. Algunas veces, los objetos pueden permanecer retenidos por el depurador, tras una evaluación en la consola. Por ello es recomendable realizar heap snapshots con la consola limpia y sin breakpoints en el depurador.

Una de las cosas a tener en cuenta en el depurador, es la distancia desde la raíz del GC. Si casi todos los objetos del mismo tipo, se encuentran a la misma distancia, y sólo unos pocos están a mayor distancia, es algo que susceptible de ser investigado.

Dominadores (Dominators)

El dominador de un objeto A, es otro objeto que existe en cada ruta simple desde la raíz del GC a dicho objeto A. Esto significa, que si eliminamos del montículo el dominador de un objeto, (junto a todas las demás referencias), el objeto A será inalcanzable, y por tanto, eliminado.

Los objetos dominadores tienen una estructura de árbol, ya que todo objeto tiene exactamente un dominador. El dominador de un objeto puede carecer de referencias directas al objeto que domina, es decir, el árbol de dominación no es una estructura que se expande.

Objetos a modo de colecciones, pueden retener una gran cantidad de memoria, cuando dominan otros objetos. Cada nodo del árbol es llamado "punto de acumulación" (accumulation point).

Particularidades del V8

En esta sección describiremos algunos temas relacionados con la memoria, que sólo corresponden a la máquina virtual V8 de Javascript. Su lectura debe ayudar a comprender por qué las capturas de montículos (heap snapshots), tienen dicha apariencia.

Representación de objetos en Javascript

Los números pueden ser presentados tanto como valores inmediatos enteros de 31 bits (small integers), o como objetos de un montículo (heap objects). Éstos últimos, son ulitizados por valores que no encajan dentro de los small integers, como por ejemplo: doubles, o para los casos en los que un valor necesita ser encapsulado, por ejemplo, al establecer propiedades en él.

El contenido de los strings, puede ser almacenado tanto en el montículo de la máquina virtual V8, como externamente, en la memoria de renderizado. El contenido recibido desde la Web (por ejemplo, desde un script), no es copiado dentro del montículo de la máquina virtual, y en su lugar, se crea un wrapper object, para acceder al contenido exterior.

Cuando dos cadenas son concatenadas, sus contenidos son inicialmente almacenados de forma separada, y sólo son unidas de manera lógica, usando un objeto llamado cons string. La unión de los contenidos de los cons strings, es ejecutado sólo cuando se precisa, por ejemplo, cuando necesitamos construir una subcadena resultado de la unión.

Los arrays son usados muy frecuentemente en la V8 para almacenar gran cantidad de datos. Los diccionarios (conjunto de pares llave-valor), son guardados en arrays. Por tanto, los arrays son el bloque de construcción básico de los objetos Javascript. Un objeto en Javascript posee dos arrays: uno para almacenar los nombres de las propiedades, y el otro para almecenar el elementos numéricos. Si el número de propiedades de un objeto es muy pequeño, entonces pueden ser almacenadas internamente en el propio objeto Javascript.

Un map object, describe el tipo de objeto y su estructura. Por ejemplo, dichos objetos se usan para describir las jerarquías implícitas de un objeto, como se describe aquí (enlace externo en inglés).

Grupos de objetos

Cada grupo de objeto nativo se crea a partir de objetos que mantienen una referencia mutua, el uno al otro. Si consideramos por ejemplo un subárbol del DOM: cada uno de sus nodos tiene un enlace a su padre, y enlaces al siguiente hijo y siguiente hermano, lo cual conforma un grafo. Aclarar que los objetos nativos (String, Number, Boolean, Array, Date, Math y RegExp) no son representados en el montículo Javascript - esta es la razón por la que tienen un tamaño igual a 0. En lugar de eso, son creados varios wrapper objects. Cada wrapper object, guarda una referencia de su correspondiente objeto nativo, para órdenes de redirección sobre él. Por lo tanto, un grupo de objetos almacena varios wrapper objects. De cualquier forma, esto no crea un ciclo no recolectable, ya que el GC es lo suficientemente inteligente como para liberar grupos de objetos cuyos wrappers no son referenciados por nadie. Sin embargo, si durante el proceso olvida liberar un simple wrapper, mantendrá a todo el grupo de objetos y sus respectivos wrappers.

¿Qué son los wrapper objects en javascript?

Esta es una traducción del libro "Javascript, The Definitive Guide", Ed. 6th, escrito en inglés por David Flanagan:

Wrapper Objects

Los objetos en JavaScript, tienen valores compuestos: son una colección de propiedades, o nombres de valores. Nos referimos al valor de una propiedad, usando la notación ".". Cuando el valor de una propiedad es una función, lo llamamos método. Para invocar el método m del objeto o, escribimos o.m()

Sabemos que los strings también tienen propiedades y métodos:

var s = "Hola mundo"; // Un string
var word = s.substring(s.indexOf(" ")+1, s.length); // Uso de las propiedades (métodos) del objeto String.

Los strings, en Javascript son valores primitivos, al igual que los números y booleanos, y por tanto, no son objetos. ¿Por qué entonces tienen propiedades, como los objetos? Cada vez que intentamos referirnos a una propiedad del string s, JavaScript convierte el valor real de dicho string en un objeto, como si lo hubiésemos declarado con el constructuor new String(s). Dicho objeto, hereda métodos, y es usado para resolver las referencias a sus propiedades (métodos). Una vez que el valor de la propiedad (método) ha sido resuelto, el nuevo objeto es declarado.

Los números y booleanos, tienen métodos por la misma razón que las cadenas: un objeto temporal se crea cuando los declaras, usando los constructores Number() o Boolean(), y el método que invoquemos, es resuelto usando dicho objeto temporal. A estos objetos temporales que envuelven a los valores primitivos, son llamados Wrapper Objects. No hay wrapper objects para null ni undefined: cada vez que intentemos acceder a una propiedad o método de éstos, nos devolverá un TypeError.

Para esclarecer un poco lo dicho anteriormente, supongamos el siguiente código, y piensa qué sucederá cuando sea ejecutado:

var s = "test";
s.len = 4;
var t = s.len;

Nada más ejecutar el código, el valor de t es undefined. En la segunda línea de código, se crea un objeto String, de modo temporal: un wrapper object, a partir de s, y le añadimos la propiedad len, con valor 4, y después, eliminamos dicho objeto temporal. En la tercera línea volvemos a crear un nuevo objeto temporal, a partir del original s (no modificado), e intentamos leer la propiedad len. Pero no existe, porque es un objeto temporal recién creado, y por tanto, la expresión se resuelve como undefined. Por tanto, el valor de t al final de la ejecución, será undefined. Este código demuestra que los valores primitivos (strings, números y cadenas), se comportan como objetos cuando intentas acceder a sus propiedades o métodos, pero si intentas especificar el valor de una de sus propiedades, dicho intento es ignorado, ya que el cambio es realizado sobre un objeto temporal, o wrapper object. Es decir, sus propiedades son de sólo lectura: inmutables.

lunes, 16 de diciembre de 2013

Testeo automático con Phantom.js

Introducción

Esta artículo pretende explicar cómo Phanjom.js puede ayudarnos a comprobar de manera automática, la adaptabilidad de nuestra aplicación web a diversas resoluciones de pantalla, generando capturas de las páginas HTML de nuestra aplicación.

¿Qué es Phantom.js?

PhantomJS es un navegador sin interfaz gráfica basado en Webkit, es decir, no nos sirve para testear nuestra aplicación con IE ni Firefox, y sólo podemos manejarlo con un terminal, o la consola MSDOS, usando un API Javascript.

¿Qué podemos hacer con Phantom.js?

  • Lanzar test unitarios con Jasmine, QUnit, Mocha, y muchos otros.
  • Simular acciones del usuario, antes de realizar la captura de pantalla: click, mouseover, keypress, etc...
  • Podemos manejar el DOM con librerías con jQuery, si lo necesitamos para testear nuestras páginas.
  • Automatizar la eficiencia de nuestras páginas con YSlow y Jenkins

Prepara el entorno

Lo único que necesitamos es un servidor web, ya sea Apache, Nginx o Node.js, con el que servir los estáticos de nuestra aplicación. Si usamos Grunt, podemos lanzar la tarea grunt-connect, o simplemente podemos crearnos un script para lanzar un servidor web bajo Node.js.

Añadir Express a package.json

Para que el anterior script funcione, será necesario que instaléis antes Express, como un módulo de Node.js. Para ello, simplemente editamos nuestro archivo package.json y añadimos la dependencia:

{
  "name": "hello-world",
  "description": "hello world test app",
  "version": "0.0.1",
  "private": true,
  "dependencies": {
    "express": "3.x"
  }
}

De este modo, una vez añadida, para instalarla ejecutamos:

npm install

Arrancar el servidor Node

El siguiente código de ejemplo, nos sirve para arrancar un servidor Node en el puerto 3000, y suponiendo que los estáticos que queremos testear están en el directorio /dev, y dentro de él, que nos redireccione a /html/index.html. Este código lo guardamos como web.js, por ejemplo:

var express = require( "express" ),
    app = express(),
    port = process.env.PORT || 3000;

process.env.PWD = process.cwd()

app.configure(function(){

    // Directorio donde estará la raíz de nuestro servidor
    app.use(express.static( process.env.PWD + '/dev' ));
    app.use(express.bodyParser());
    app.use(express.methodOverride());

    //Error Handling
    app.use(express.logger());
    app.use(express.errorHandler({
            dumpExceptions: true, 
            showStack: true
    }));

    app.use(app.router);
});

// Redireccionamos a /dev/html/index.html
app.get('/', function(req, res){
    res.redirect( "/html/index.html" );
});

app.listen(port, function() {
    console.log( "Listening on " + port );
});

Para hacerlo funcionar, lo ejecutamos desde la consola:

node web

Añadiendo Phantom.js al PATH

Lo primero que debemos hacer es, obviamente, descargarnos Phantom.js. Una vez descargado, lo guardamos en el directorio C:/phantomjs

Después editamos la variable de entorno PATH, y añadimos dicha ruta, para que phantomjs.exe, esté disponible desde la consola de MSDOS, o terminal. Es posible que tengas que reiniciar para que la variable de entorno la coja bien Windows.

Definiendo los test y capturas de pantalla a realizar con Phantom.js

Como Phantom.js se maneja sólo por comandos, si queremos simular la acción del usuario, o capturar pantallas, lo siguiente que debemos hacer es crearnos un archivo javascript con el que definir las acciones a realizar con dicho navegador. Este código lo guardamos con el nombre phantomtest.js (por ejemplo):

(function(){

    "no strict";

    var resourceWait  = 300,
        maxRenderWait = 10000,
        forcedRenderTimeout,
        clickTimeout,
        renderTimeout,
        url  = 'http://localhost:3000/html/index.html',
        page = require('webpage').create();

    page.onConsoleMessage = function (msg, line, source) {
        console.log( 'console> ' + msg );
    };

    page.onAlert = function (msg) {
        console.log( 'alert!!> ' + msg );
    };

    page.onResourceRequested = function (req) {
        //console.log( 'request> ' + req.id + ' - ' + req.url );
        clearTimeout(renderTimeout);
    };
     
    page.onResourceReceived = function (res) {
        if (!res.stage || res.stage === 'end') {
            console.log( 'loaded> ' + res.id + ' ' + res.status + ' - ' + res.url);
        }
    };

    page.open(url, function (status) {

        if (status !== 'success') {
            console.log('Error al cargar la URL: ' + url);
        } else {

            forcedRenderTimeout = setTimeout(function () {

                page.viewportSize = { width: 1024, height : 512 };
                page.render( 'test-1024.png' );

                page.viewportSize = { width: 768, height : 512 };
                page.render( 'test-768.png' );

                page.viewportSize = { width: 320, height : 512 };
                page.render( 'test-320.png' );

                phantom.exit();

            }, maxRenderWait);
        }     
    });

}());

Ejecutamos los test con Phantom.js

Una vez que tengamos nuestro servidor funcionando en el puerto 3000, sólo nos queda ejecutar phantomtest.js con el navegador. Para hacerlo funcionar, iniciamos otra consola nueva, y escribimos:

phantomjs phantomtest

Lo que sucede a continuación, es que Phantom.js va a ejecutar las acciones que le hayamos indicado en phantomtest.js: tendremos 3 capturas de pantalla, a diferentes resoluciones, de una de las páginas de nuestra aplicación. Si quisiésemos simular acciones de usuario, o modificar el DOM, podemos usar jQuery. En dicho caso, las acciones las debemos englobar dentro de page.evaluate. Nuestro page.open quedaría ahora así:

page.open(url, function (status) {

        if (status !== 'success') {
            console.log('Error al cargar la URL: ' + url);
        } else {

            forcedRenderTimeout = setTimeout(function () {

                page.viewportSize = { width: 1024, height : 512 };
                page.evaluate(function () {
                    var $elm = $("#test3");
                    if( $elm.length > 0 ) {
                        $elm.trigger("click");
                    }
                });
                page.render( 'test-1024-click.png' );

                page.viewportSize = { width: 768, height : 512 };
                page.render( 'test-768.png' );

                page.viewportSize = { width: 320, height : 512 };
                page.render( 'test-320.png' );

                phantom.exit();

            }, maxRenderWait);
        }     
    });

De este modo hemos simulado, antes de hacer la captura de pantalla, que el usuario ha hecho click en el elemento cuyo id es #test3.

Ejemplo de código

Si quieres ver todo junto funcionando, puedes encontrar el código al que hago referencia en este artículo, aquí: https://github.com/alfonsomartinde/prettychecks.

martes, 10 de diciembre de 2013

Deploy de una app hecha con Grunt y Bower, desde Cloud9, en Heroku

¿Qué es Heroku?

Heroku es una plataforma como servicio (PaaS) en la nube, que nos permite arrancar un entorno de desarrollo para el lenguaje que queramos, de un modo transparente, y sin tener que perder tiempo por nuestra parte en configurar nada en nuestro sistema local. Podríamos decir que es un entorno de desarrollo "llave en mano". Al crear una aplicación en Heroku, éste nos ofrece un entorno sobre Debian, preparado ya para programar con ruby, php, java, node, o el lenguaje que necesitemos.

Nos ofrece también un repositorio Git, donde alojar nuestros fuentes. Cada vez que hagamos un push, Heroku compilará nuestro proyecto, y un servidor web al que podremos acceder para ver nuestra aplicación online: http://nuestraapp.herokuapp.com/

Para este pequeño "manual", voy a suponer que ya estás familiarizado con el desarrollo de aplicaciones con Grunt, Less, Jade, etc. Por tanto, entiendo que tu aplicación ya tiene un archivo package.json. Lo que voy a explicar aquí, es cómo modificarla para poder hacer deploys en Heroku, desde Cloud9:

Instalar el motor Node

Para que Node funcione correctamente en Heroku, debemos especificar en package.json que vamos a usar Node.js junto con su versión: algo que no era necesario para compilar en local, pero sí lo es en Heroku. Para ello, debemos añadir la propiedad "engines" a nuestro package.json:

"engines": {
     "node": "0.10.x"
}

No es necesario especificar que necesitaremos npm, ni su versión, ya que npm viene en el lote del motor Node.

Instalar Express, Grunt y Bower en nuestra app de Heroku

Por defecto, cuando compilamos una aplicación en Heroku, ésta se compila en modo producción, por lo tanto, lo que pongamos en "devDependencies" de nuestro package.json será ignorado. Llegados a este punto podemos hacer dos cosas:

  • o cambiamos el entorno a modo desarrollo, de tal modo que se instalen las dependencias definidas en "devDependencies",
  • o especificamos en "dependencies" las dependencias para producción.

Personalmente prefiero esta última opción, es decir, usar el entorno de producción en Heroku. Por tanto, copiamos los paquetes de "devDependencies" a "dependencies", y además, le añadimos también algunos paquetes que por defecto en Heroku no están instalados: express, bower, grunt y grunt-cli. Nuestro package.json quedaría así (por ejemplo):

"dependencies": {
  "express": "~3.3.4",
  "bower": "latest",
  "grunt": "latest",
  "grunt-cli": "latest",
  ...
  resto de paquetes
  ...
},
"devDependencies": {
  "grunt": "latest",
  ...
  resto de paquetes
  ...
}

Ejecutar Bower install en Heroku

Si en nuestra app hemos usado Bower, necesitaremos ejecutar el comando 'bower install' para instalar los repositorios al compilar. Para ello, añadimos la instrucción al bloque "scripts" en package.json:

"scripts": {
    "postinstall": "./node_modules/bower/bin/bower install"
  }

Crear el servidor de archivos estáticos

Imaginemos que nuestra app tiene la siguiente estructura:

/app  --> Archivos fuente
  |--/fonts
  |--/img
  |--/jade
  |--/js
  |--/less
  \--/test
/dev  --> Compilación de desarrollo
  |--/css
  |--/fonts
  |--/html
  |--/img
  |--/js
  \--/test
/pro  --> Compilación de producción (concatenada, minificada y ofuscada)
  |--/css
  |--/fonts
  |--/html
  |--/img
  |--/js
  \--/test

Por defecto, Heroku no nos crea un servidor HTTP. Debemos crearlo nosotros con Node. Para ello nos creamos un archivo javascript al que llamaremos por ejemplo "web.js".

Suponiendo que hemos configurado nuestra app, para que compile los estáticos en la carpeta /dev, y dentro de ella nuestro archivo index se encuentra en html/index.html, este sería nuestro archivo "web.js" para arrancar un servidor de estáticos en Heroku:

web.js

var express = require("express"),
    app = express.createServer(),
    port = process.env.PORT || 3000;

process.env.PWD = process.cwd();

app.configure(function(){
    app.use(express.static(process.env.PWD + '/dev'));
    app.use(express.bodyParser());
    app.use(express.methodOverride());

    //Error Handling
    app.use(express.logger());
    app.use(express.errorHandler({
        dumpExceptions: true, 
        showStack: true
    }));

    app.use(app.router);
});

app.get('/', function(req, res){
    res.redirect("html/index.html");
    res.render("index.html");
});

app.listen(port, function() {
    console.log("Listening on " + port + "\nDirectory: " + process.env.PWD + "/dev");
});

Ahora nos falta decirle a Heroku que ejecute dicho archivo. Para ello debemos crearnos un archivo llamado Procfile. Esto lo explico en el siguiente apartado.

Crear el archivo Procfile

Una vez configurado correctamente package.json, nos creamos un archivo nuevo llamado Procfile, en el que indicamos a Heroku lo que ejecutar al realizar el deploy. Es aquí debemos decirle a Heroku que arranque el servidor web, para que sirva los archivos estáticos generados previamente con grunt. Este sería nuestro Procfile:

web: node web.js

Instalar cliente Heroku en Cloud9

Para poder ejecutar comandos de Heroku, y manejar nuestro entorno desde la terminal de Cloud9, necesitamos instalar el cliente. Para ello escribimos, en el terminal de Cloud9:

wget http://assets.heroku.com/heroku-client/heroku-client.tgz
tar xzfv heroku-client.tgz
cd heroku-client/bin
PATH=$PATH:$PWD

Acceso al bash de Heroku desde Cloud9

Una vez instalado el cliente, desde el terminal de Cloud9 tendremos acceso al bash Heroku. Para ello abrimos un terminal de Cloud9, y escribimos:

heroku run bash -a nombre-de-nuestra-app

Configuramos el Buildpack

Cuando hacemos un git push a nuestro repositorio de Heroku, éste prepara los archivos para ser compilados. El conjunto de acciones que se realizan para compliar, se define en un Buildpack. Heroku dispone de varios Buildpacks, dependiendo del lenguaje y lo que queramos hacer. Sin embargo en este caso, necesitamos un BUILDPACK "no oficial", para que de soporte a Grunt. Para instalar dicho buildpack, escribimos en el terminal:

heroku config:add BUILDPACK_URL=https://github.com/mbuchetics/heroku-buildpack-nodejs-grunt.git -a nombre-de-nuestra-app

En este momento se compilarán nuestros archivos, tal y como lo hubiéramos compilado en Gruntfile.js, y tendremos acceso a ellos desde el servidor web de estáticos, en la URL: http://nombredenuestraapp.herokuapp.com

Añadir deploy en Cloud9

En este apartado veremos cómo lanzar un deploy desde Cloud9:

Después elegimos el servicio Heroku.com:

Configurar el entorno a "development" (opcional)

Este paso es opcional, y explica cómo proceder si quisiéramos cambiar el entorno a desarrollo, en vez de producción que es como lo tiene Heroku por defecto. Desde la terminal de Cloud9 escribimos:

heroku config:set NODE_ENV=development -a nombre-de-nuestra-app
heroku labs:enable user-env-compile -a nombre-de-nuestra-app

Más información

viernes, 26 de julio de 2013

Validación de NIE, NIF y CIF con jQuery Validator

Introducción

El plugin jQuery Validation, fue escrito y es mantenido por Jörn Zaefferer, miembro del equipo jQuery, desarrollador jefe del equipo jQuery UI, y uno de los responsables del mantenimiento de QUnit. Lo que dicho plugin nos ofrece, es validación de formularios en cliente, con una alta capacidad de personalización.

Una de las ventajas, es que nosotros mismos podemos extender la clase y añadir nuestros propios métodos a la librería. Esto lo hacemos creando e importando un .js con nuestros métodos de validación, justo después de hacer la llamada al plugin.

¿Cómo añadir un nuevo método?

El formato que debe tener dicho js, para añadir un nuevo método es el siguiente:

;(function($) {

 jQuery.validator.addMethod(
  "NOMBRE_REGLA", 
  function(value, element) {
   
   return false;
  },
  "MENSAJE DE ERROR"
 );
}(jQuery));

Validación de NIF, NIE y CIF:

Volviendo al tema inicial, así quedaría nuestro archivo js para validar NIF, NIE y CIF:

jQuery.validator.addMethod( "nifES", function ( value, element ) {
 "use strict";

 value = value.toUpperCase();

 // Basic format test 
 if ( !value.match('((^[A-Z]{1}[0-9]{7}[A-Z0-9]{1}$|^[T]{1}[A-Z0-9]{8}$)|^[0-9]{8}[A-Z]{1}$)') ) {
  return false;
 }

 // Test NIF
 if ( /^[0-9]{8}[A-Z]{1}$/.test( value ) ) {
  return ( "TRWAGMYFPDXBNJZSQVHLCKE".charAt( value.substring( 8, 0 ) % 23 ) === value.charAt( 8 ) );
 }
 // Test specials NIF (starts with K, L or M)
 if ( /^[KLM]{1}/.test( value ) ) {
  return ( value[ 8 ] === String.fromCharCode( 64 ) );
 }

 return false;

}, "Please specify a valid NIF number." );


jQuery.validator.addMethod( "nieES", function ( value, element ) {
 "use strict";

 value = value.toUpperCase();

 // Basic format test 
 if ( !value.match('((^[A-Z]{1}[0-9]{7}[A-Z0-9]{1}$|^[T]{1}[A-Z0-9]{8}$)|^[0-9]{8}[A-Z]{1}$)') ) {
  return false;
 }

 // Test NIE
 //T
 if ( /^[T]{1}/.test( value ) ) {
  return ( value[ 8 ] === /^[T]{1}[A-Z0-9]{8}$/.test( value ) );
 }

 //XYZ
 if ( /^[XYZ]{1}/.test( value ) ) {
  return ( 
   value[ 8 ] === "TRWAGMYFPDXBNJZSQVHLCKE".charAt( 
    value.replace( 'X', '0' )
     .replace( 'Y', '1' )
     .replace( 'Z', '2' )
     .substring( 0, 8 ) % 23 
   ) 
  );
 }

 return false;

}, "Please specify a valid NIE number." );



jQuery.validator.addMethod( "cifES", function ( value, element ) {
 "use strict";
 
 var sum,
  num = [],
  controlDigit;
 
 value = value.toUpperCase();
 
 // Basic format test 
 if ( !value.match( '((^[A-Z]{1}[0-9]{7}[A-Z0-9]{1}$|^[T]{1}[A-Z0-9]{8}$)|^[0-9]{8}[A-Z]{1}$)' ) ) {
  return false;
 }
 
 for ( var i = 0; i < 9; i++ ) {
  num[ i ] = parseInt( value.charAt( i ), 10 );
 }
 
 // Algorithm for checking CIF codes
 sum = num[ 2 ] + num[ 4 ] + num[ 6 ];
 for ( var count = 1; count < 8; count += 2 ) {
  var tmp = ( 2 * num[ count ] ).toString(),
   secondDigit = tmp.charAt( 1 );
  
  sum += parseInt( tmp.charAt( 0 ), 10 ) + ( secondDigit === '' ? 0 : parseInt( secondDigit, 10 ) );
 }
 
 // CIF test
 if ( /^[ABCDEFGHJNPQRSUVW]{1}/.test( value ) ) {
  sum += '';
  controlDigit = 10 - parseInt( sum.charAt( sum.length - 1 ), 10 );
  value += controlDigit;
  return ( num[ 8 ].toString() === String.fromCharCode( 64 + controlDigit ) || num[ 8 ].toString() === value.charAt( value.length - 1 ) );
 }
 
 return false;
 
}, "Please specify a valid CIF number." );

Test

He creado unos sencillos test con QUnit para comprobar el correcto funcionamiento de dichos archivos, dada la importancia de dichos valores: no es nada deseable que un usuario introduzca un NIF, o CIF inválido, y el sistema no lo detecte. Los resultados han sido satisfactorios:

A continuación os dejo el código de los test:

;(function($) {

function methodTest( methodName ) {
 var v = jQuery("#form").validate();
 var method = $.validator.methods[methodName];
 var element = $("#test")[0];
 return function(value, param) {
  element.value = value;
  return method.call( v, value, element, param );
 };
}

module("methods");

test("nifES", function() {
 var method = methodTest("nifES");
 ok( method( "11441059P" ), "NIF valid" );
 ok( method( "80054306T" ), "NIF valid" );
 ok( method( "76048581R" ), "NIF valid" );
 ok( method( "28950849J" ), "NIF valid" );
 ok( method( "34048598L" ), "NIF valid" );
 ok( method( "28311529R" ), "NIF valid" );
 ok( method( "34673804Q" ), "NIF valid" );
 ok( method( "92133247P" ), "NIF valid" );
 ok( method( "77149717N" ), "NIF valid" );
 ok( method( "15762034L" ), "NIF valid" );
 ok( method( "05122654W" ), "NIF valid" );
 ok( method( "05122654w" ), "NIF valid: lower case" );
 ok(!method( "1144105R" ), "NIF invalid: less than 8 digits without zero" );
 ok(!method( "11441059 R" ), "NIF invalid: white space" );
 ok(!method( "11441059" ), "NIF invalid: no letter" );
 ok(!method( "11441059PR" ), "NIF invalid: two letters" );
 ok(!method( "11440059R" ), "NIF invalid: wrong number" );
 ok(!method( "11441059S" ), "NIF invalid: wrong letter" );
 ok(!method( "114410598R" ), "NIF invalid: > 8 digits" );
 ok(!method( "11441059-R" ), "NIF invalid: dash" );
 ok(!method( "asdasdasd" ), "NIF invalid: all letters" );
 ok(!method( "11.144.059R" ), "NIF invalid: two dots" );
 ok(!method( "05.122.654R" ), "NIF invalid: starts with 0 and dots" );
 ok(!method( "5.122.654-R" ), "NIF invalid:  dots and dash" );
 ok(!method( "05.122.654-R" ), "NIF invalid: starts with zero and dot and dash" );
});

test("nieES", function() {
 var method = methodTest("nieES");
 ok( method( "X0093999K" ), "NIE valid" );
 ok( method( "X1923000Q" ), "NIE valid" );
 ok( method( "Z9669587R" ), "NIE valid" );
 ok( method( "Z8945005B" ), "NIE valid" );
 ok( method( "Z6663465W" ), "NIE valid" );
 ok( method( "Y7875935J" ), "NIE valid" );
 ok( method( "X3390130E" ), "NIE valid" );
 ok( method( "Y7699182S" ), "NIE valid" );
 ok( method( "Y1524243R" ), "NIE valid" );
 ok( method( "X3744072V" ), "NIE valid" );
 ok( method( "X7436800A" ), "NIE valid" );
 ok( method( "y7875935j" ), "NIE valid: lower case" );
 ok(!method( "X0093999 K" ), "NIE inválido: white space" );
 ok(!method( "X 0093999 K" ), "NIE inválido:  white space" );
 ok(!method( "11441059" ), "NIE inválido: no letter" );
 ok(!method( "11441059PR" ), "NIE inválido: two letters" );
 ok(!method( "11440059R" ), "NIE inválido: wrong number" );
 ok(!method( "11441059S" ), "NIE inválido: wrong letter" );
 ok(!method( "114410598R" ), "NIE inválido: > 8 digits" );
 ok(!method( "11441059-R" ), "NIE inválido: dash" );
 ok(!method( "asdasdasd" ), "NIE inválido: all letters" );
 ok(!method( "11.144.059R" ), "NIE inválido: two dots" );
 ok(!method( "05.122.654R" ), "NIE inválido: starts with 0 and dots" );
 ok(!method( "5.122.654-R" ), "NIE inválido: dots and dash" );
 ok(!method( "05.122.654-R" ), "NIE inválido: starts with zero and dot and dash" );
});

test("cifES", function() {
 var method = methodTest("cifES");
 ok( method( "A79082244" ), "CIF valid" );
 ok( method( "A60917978" ), "CIF valid" );
 ok( method( "A39000013" ), "CIF valid" );
 ok( method( "B43522192" ), "CIF valid" );
 ok( method( "B38624334" ), "CIF valid" );
 ok( method( "G72102064" ), "CIF valid" );
 ok( method( "F41190612" ), "CIF valid" );
 ok( method( "J85081081" ), "CIF valid" );
 ok( method( "S98038813" ), "CIF valid" );
 ok( method( "G32937757" ), "CIF valid" );
 ok( method( "B46125746" ), "CIF valid" );
 ok( method( "C27827559" ), "CIF valid" );
 ok( method( "E48911572" ), "CIF valid" );
 ok( method( "s98038813" ), "CIF valid: lower case" );
 ok(!method( "K48911572" ), "CIF invalid: starts with K" );
 ok(!method( "L48911572" ), "CIF invalid: starts with L" );
 ok(!method( "M48911572" ), "CIF invalid: starts with M" );
 ok(!method( "X48911572" ), "CIF invalid: starts with X" );
 ok(!method( "Y48911572" ), "CIF invalid: starts with Y" );
 ok(!method( "Z48911572" ), "CIF invalid: starts with Z" );
 ok(!method( "M15661515" ), "CIF invalid" );
 ok(!method( "Z98038813" ), "CIF invalid: wrong letter" );
 ok(!method( "B 43522192" ), "CIF invalid: white spaces" );
 ok(!method( "43522192" ), "CIF invalid: missing letter" );
 ok(!method( "BB43522192" ), "CIF invalid: two letters" );
 ok(!method( "B53522192" ), "CIF invalid: wrong number" );
 ok(!method( "B433522192" ), "CIF invalid: > 8 digits" );
 ok(!method( "B3522192" ), "CIF invalid: < 8 digits" );
 ok(!method( "B-43522192" ), "CIF invalid: dash" );
 ok(!method( "Basdasdas" ), "CIF invalid: all letters" );
 ok(!method( "B43.522.192" ), "CIF invalid: dots" );
 ok(!method( "B-43.522.192" ), "CIF invalid: dots and dash" );
});

})(jQuery);

jueves, 25 de julio de 2013

Iniciar un proyecto con Grunt

Introducción

En esta ocasión voy a explicar brevemente los pasos a seguir para realizar un proyecto HTML, CSS, Javascript desde cero. Más concretamente, voy a explicar cómo lo hago yo, que no es ni la mejor ni la peor manera de hacerlo. Simplemente a la que estoy acostumbrado y me va bien. Espero que os ayude a crear vuestra propia metodología de trabajo.

He dejado los archivos en un repositorio de BitBucket: base-grunt. Espero que os sirva!

Voy a suponer que tenemos instalado en nuestro sistema:

  • Bower: gestor de librerías, con el que podemos descargar automáticamente las que necesitemos para el proyecto (bootstrap, jquery, etc.)
  • Grunt; automatizador de tareas
  • Growl: notificaciones de nuestros watchers (para MacOS) (para Windows)
  • Node.js. Sin él, nada sería posible.

Si todo esto te suena a chino, te recomiendo entonces que leas antes el artículo donde explico más en detalle cada una de estas herramientas: Node.js, Grunt.js, Bower y Yeoman.

No comento nada acerca de Yeoman (generador de estructuras de directorios), porque no he sido capaz de hacerlo funcionar correctamente bajo Windows 7, con un generator personalizado.

Creamos el árbol de directorios

Supongamos que tenemos dos entornos: dev (desarrollo) y pro (producción). En desarrollo, guardaremos nuestros archivos ya compilados (CSS, HTML, imágenes y javascript), pero sin minificar, de tal modo que al ejecutarlos, podamos ver fácilmente en qué linea del código se producen los errores. Y también en desarrollo será donde guardemos nuestros test. En producción, guardamos los compilados pero en este caso, con un formato minificado, y a ser posible, concatenando ficheros en uno sólo (todos los CSS en un único archivo), para minificar el número de peticiones al servidor. En dicho caso, la estructura sería:

/proyecto/
|-- app/ <- fuentes
|   |-- coffee/
|   |   `-- commons.coffee
|   |
|   |-- jade/
|   |   |-- inc/
|   |   |   |-- footer.jade
|   |   |   `-- header.jade
|   |   |
|   |   |-- layouts/
|   |   |   `-- main.jade
|   |   |
|   |   `-- index.jade
|   |
|   |-- js/ <- Custom JS sin preprocesar
|   |   `-- app.js
|   |
|   |-- less/
|   |   `-- *.less
|   |
|   `-- test/
|       |-- js/
|       |   `-- test.js
|       |
|       `-- index.jade
|
|-- dev/ <- compilados para desarrollo (sin minificar ni ofuscar)
|
`-- pro/ <- compilados para producción (minificados)

Configuramos las librerías a usar con Bower

Para ello, nos creamos dos ficheros: .bowerrc y component.json. Con el primero, especificamos el directorio donde se desarcarán los repositorios de cada librería, y con el segundo, especificamos qué librerías descargamos, y qué versión de cada una.

En el siguiente ejemplo, indico que vamos a necesitar la última versión de jquery, y la última de QUnit:

.bowerrc

{
  "directory": "bower",
  "json": "bower.json"
}

bower.json

{
  "name": "base-grunt",
  "version": "0.1.0",
  "dependencies": {
    "qunit": "latest",
    "jquery": "latest"
  },
  "ignore": [
    "**/.*",
    ".jshintrc",
    "node_modules",
    "components",
    "bower",
    "test",
    "tests"
  ]
}

Configuramos nuestro package.json y gruntfile.js

El siguiente paso, es añadir nuestro gruntfile.js al proyecto, con la configuración y tareas que deseemos automatizar:

  • Copy: para copiar carpetas y archivos de un lugar a otro
  • JSHint: para revisar nuestro código javascript en busca de errores y malas prácticas
  • Uglify: para minificar código javascript con UglifyJS
  • Notify: para lanzar avisos a Grow
  • Coffeescript: preprocesador javascript
  • Coffeelint: lo mismo que jshint, pero para coffescript: nos ayudará a evitar errores en nuestros archivos coffeescript.
  • Sass: preprocesador CSS, como Less o Stylus. Ya no es necesario tener WinLess corriendo sobre nuestra máquina.
  • Jade, Yaml o Haml: preprocesador HTML. En este caso, voy a utilizar Jade, por la potencia que dan los includes y layouts, que Haml y Yaml no te dan.
  • Watcher: para realizar el compilado automático al guardar el archivo fuente
  • DevTools: para activar las notificaciones con la consola de Chrome

Para ello, tenemos dos archivos: package.json y gruntfile.js. Con el primero, especificamos variables que utilizaremos luego en gruntfile.js, así como las librerías que utilizaremos con Grunt, y detalles del proyecto como el nombre, autor, repositorio, licencias, etc. Con el segundo archivo, gruntfile.js, configuramos las tareas a automatizar, las notificaciones, los entornos, etc.

package.json

{
  "name": "base-grunt",
  "version": "0.1.0",
  "description": "Basic template for build projects with Bower + Grunt + Growl + Less + Jade + CoffeeScript",
  "devDependencies": {
    "matchdep": "latest",
    "grunt": "latest",
    "grunt-coffeelint": "latest",
    "grunt-contrib-jshint": "latest",
    "grunt-contrib-copy": "latest",
    "grunt-contrib-jade": "latest",
    "grunt-contrib-watch": "latest",
    "grunt-notify": "latest",
    "grunt-contrib-less": "latest",
    "grunt-contrib-uglify": "latest",
    "grunt-contrib-coffee": "latest",
    "grunt-devtools": "latest"
  },
  "main": "gruntfile.js",
  "repository": {
    "type": "git",
    "url": "ssh://git@bitbucket.org:alfonsomartinde/base-grunt.git"
  },
  "author": "alfonsomartide",
  "license": "BSD"
}

gruntfile.js

// Lee las propiedades definidas en packaje.json
// Las podemos usar aquí así: <%= pkg.name %>

;(function () {
    "use strict";
    
    module.exports = function(grunt) {

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

            coffeelint: {
                files: {
                    src: ['app/coffee/*.coffee']
                },
                options: {
                    'no_tabs': {'level': 'ignore'},
                    'no_trailing_whitespace': {'level': 'ignore'}
                }
            },

            // app/coffee > dev/js
            coffee: {
                dev: {
                    files: { 
                        'dev/js/commons.js': 'app/coffee/commons.coffee'
                    }
                }
            },

            // dev/js > pro/js
            uglify: {
                pro: {
                    files: {
                        'pro/js/all.min.js':['dev/js/*.js']
                    }
                }
            },

            // Comprobamos errores
            jshint: {
                files: ['gruntfile.js','app/js/**/*.js','app/test/**/*.js'],
                options: {
                    ignores: ['app/js/vendors/*.js'],
                    globals: {
                        jQuery: true,
                        console: true,
                        module: true
                    }
                }
            },


            copy: {
                gen: {
                    files: [
                        // generator > dev
                        {
                            expand: true,
                            cwd: 'app/generator/',
                            src: ['**/*'],
                            dest: 'dev/'
                        },
                        // generator > pro
                        {
                            expand: true,
                            cwd: 'app/generator/',
                            src: ['**/*'],
                            dest: 'pro/'
                        }
                    ]
                },
                dev: {
                    files: [
                        // app/test/js > dev/test/js
                        {
                            expand: true,
                            cwd: 'app/test/js/',
                            src: ['**/*'],
                            dest: 'dev/test/js/'
                        },
                        // app/js/vendors > dev/js/vendors
                        {
                            expand: true,
                            cwd: 'app/js/vendors/',
                            src: ['**/*'],
                            dest: 'dev/js/vendors/'
                        },
                        // app/js > dev/js
                        {
                            expand: true,
                            cwd: 'app/js/',
                            src: ['*.*'],
                            dest: 'dev/js/'
                        },
                        // bower/jquery > dev/js/vendors
                        {
                            expand: true,
                            cwd: 'bower/jquery/',
                            src: ['jquery.min.js'],
                            dest: 'dev/js/vendors/'
                        },
                        // bower/qunit > dev/test/qunit
                        {
                            expand: true,
                            cwd: 'bower/qunit/qunit/',
                            src: ['**/*'],
                            dest: 'dev/test/qunit/'
                        }
                    ]
                },
                pro: {
                    files: [
                        // dev/js/vendors > pro/js/vendors
                        {
                            expand: true,
                            cwd: 'dev/js/vendors/',
                            src: ['**/*'],
                            dest: 'pro/js/vendors/'
                        },
                    ]
                }
            },

            less: {
                dev: {
                    files: {
                        'dev/css/all.css': 'app/less/all.less'
                    }
                },
                pro: {
                    options: {
                        compress: true
                    },
                    files: {
                        'pro/css/all.min.css': 'app/less/all.less'
                    }
                },
            },
    
            jade: {  
                dev: {  
                    options:{  
                        pretty: true,  
                        data: function(){
                            return {developing: "true"};
                        }  
                    },  
                    files:[
                        // app/jade > dev/html
                        {
                            expand: true, 
                            src: "*.jade", 
                            dest: "dev/html/", 
                            ext: ".html", 
                            cwd: "app/jade/"
                        },
                        // app/test > dev/test
                        {
                            expand: true, 
                            src: "*.jade", 
                            dest: "dev/test/", 
                            ext: ".html", 
                            cwd: "app/test/"
                        },
                    ]
                },  
                pro: {  
                    options:{  
                        pretty: false,  
                        data: function(){
                            return {developing: "false"};
                        }
                    },
                    // app/jade > pro/html
                    files:[{
                        expand: true,
                        src: "*.jade",
                        dest: "pro/html/", 
                        ext: ".html", 
                        cwd: "app/jade/"
                    }]
                }  
            }, 

            notify: {
                coffeelint : {
                    options: {
                        enabled: true,
                        max_jshint_notifications: 2,
                        message: "coffeelint iniciado!"
                    }
                },
                coffee : {
                    options: {
                        enabled: true,
                        max_jshint_notifications: 2,
                        message: "coffee iniciado!"
                    }
                },
                jshint: {
                    options: {
                        enabled: true,
                        max_jshint_notifications: 2,
                        message: "jshint iniciado!"
                    }
                },
                uglify: {
                    options: {
                        enabled: true,
                        max_jshint_notifications: 2,
                        message: "uglify iniciado!"
                    }
                },
                jade: {
                    options: {
                        enabled: true,
                        max_jshint_notifications: 2,
                        message: "jade iniciado!"
                    }
                },
                less: {
                    options: {
                        enabled: true,
                        max_jshint_notifications: 2,
                        message: "less iniciado!"
                    }
                }
            },

            watch: {
                coffeelint: {
                    files: ["app/coffee/{,*/}*.coffee"], 
                    tasks: ["notify:coffeelint","coffeelint"]
                },
                coffee: {
                    files: ["app/coffee/{,*/}*.coffee"], 
                    tasks: ["notify:coffee","coffee:dev"]
                },
                jshint: {
                    files: ["app/js/{,*/}*.js","app/test/js/{,*/}*.js"], 
                    tasks: ["notify:jshint","jshint","copy:dev"]
                },
                jade: {
                    files: ["app/jade/{,*/}*.jade","app/test/{,*/}*.jade"],
                    tasks: ["notify:jade","jade:dev"]
                },
                less: {
                    files: ["app/less/{,*/}*.less"],
                    tasks: ["notify:less","less:dev"]
                }
            }

        });


        /**
         * Cargamos todos los tasks declarados en package.json
         *
         */

        require('matchdep')
            .filterDev('grunt-*')
            .forEach(grunt.loadNpmTasks);




        /**
         * Definimos las tareas
         *
         */
        grunt.registerTask("gen", function (target) {
            grunt.task.run([
                "copy:gen"
            ]);
        });

        grunt.registerTask("default", function (target) {
            grunt.task.run([
                "coffeelint",
                "coffee",
                "jshint",
                "uglify", 
                "copy",
                "jade",
                "less",
                "watch",
            ]);
        });

        grunt.registerTask("dev", function (target) {
            grunt.task.run([
                "coffeelint",
                "coffee:dev", 
                "jshint",
                "copy:dev",
                "jade:dev",
                "less:dev",
                "watch",
            ]);
        });

        grunt.registerTask("pro", function (target) {
            grunt.task.run([
                "uglify:pro", 
                "copy:pro",
                "jade:pro",
                "less:pro"
            ]);
        });

    };
}());

Instalando todo

A continuación abrimos la consola MSDOS, nos metemos en el directorio raíz de nuestro proyecto y ejecutamos el comando:

npm install

Hecho esto, se nos habrán descargado las librerías que especificamos en package.json a la carpeta node_modules. El siguiente paso, es descargar las librerías de Bower:

bower install

Si estamos detrás de un proxy, deberemos especificar la variable HTTP_PROXY antes de ejecutar el bower install:

set HTTP_PROXY=http://user:pass@server.url:port

En nuestra carpeta de bower, tendremos descargadas las librerías que hemos indicado. Y con esto, si todo ha ido bien, ya tendremos preparado nuestro entorno de trabajo.

Últimos pasos

Para empezar a programar, sólo nos queda:

  1. Arrancar Growl
  2. Ejecutar la tarea grunt dev, grunt pro, o grunt default, según queramos generar un entorno u otro:
    grunt dev
    

miércoles, 10 de julio de 2013

Nested Revealing Module Pattern

Uno de los patrones que más utilizo para el desarrollo de pequeñas aplicaciones, es el patrón "Revealing Module". Me gusta porque te permite encapsular funciones públicas y privadas, y dejar el global scope limpio.

Sin embargo, bajo determinadas circunstancias, dicho patrón puede quedarse pequeño, especialmente si nos gusta modularizar, u organizar el código de un modo más fácil de cara a mantenerlo y a futuras expansiones.

Imaginemos que tenemos una web con tabs, menus y un par de carruseles. Podríamos organizar nuestro código de muchas maneras. Una de ellas es usando el patrón "Module":

var AppUI = {

  Tabs: {
    show: function(){},
    hide: function(){},
    init: function(){}
  },

  Menus: {
    show: function(){},
    toggle: function(){},
    Options: {
      add: function(){},
      remove: function(){}
    },
    init: function() {}
  },

  Carruseles: {
    start: function(){},
    stop: function(){},
    next: function(){},
    prev: function(){},
    init: function(){}
  }

};

La ventaja de este sistema, es que tengo el código organizado, y el global scope sólo se ve afectado por una nueva variable, o namespace, llamada AppUI, pero todas las funciones dentro de AppUI son públicas, lo que no es muy correcto. Fíjate que por error, yo podría escribir AppUI.Tabs.show(), en vez de AppUI.Menus.show(), y estar obteniendo una funcionalidad indeseada. Lo cual, podría arreglarlo escribiendo nombres de propiedades más descriptivas, como Tabs.showTabs(), o Menus.showMenus(), de tal modo que no pudiese cometer el error anterior, pero esto no deja de tener cierta redundancia, y con objetos con muchas propiedades, me obligaría a poner nombres quizá demasiado extensos, para hacerlos más descriptivos, y puede que perdiese algo de legibilidad.

No digo que lo anterior sea incorrecto, todo depende de la solución que más se adapte a nuestras necesidades. Si lo que queremos es evitar que todo sea público dentro de AppUI, lo anterior no es la mejor manera.

Me gusta que el global scope sólo se vea afectado por una única variable nueva, AppUI. Pero quiero evitar que dentro de ella, todas las funciones sean públicas. Es decir, quiero mantener lo más limpio posible, el scope de AppUI. ¿Cómo resolverlo? Pues aquí es donde entran en juego otros patrones, en concreto el "Revealing Module".

Refactorizando el anterior objeto AppUI, para adaptarlo a dicho patrón, nos quedaría algo como esto:

var AppUI = {};

AppUI = (function(){

  function init(){}

  return {
    init:init
  }

})();

AppUI.Tabs = (function(){

  function show(){}
  function hide(){}
  function init(){}

  return {
    init:init
  }

})();

AppUI.Menus = (function(){

  function show(){}
  function toggle(){}
  function init(){}

  return {
    init:init
  }

})();

AppUI.Menus.Options = (function(){

  function add(){}
  function remove(){}

  return {
    add:add,
    remove:remove
  }

})();

AppUI.Carruseles = (function(){

  function start(){}
  function stop(){}
  function next(){}
  function prev(){}
  function init(){}

  return {
    start:start,
    stop:stop,
    init:init
  }

})();

Lo que tenemos ahora es un patrón "Module", con varios patrones "Revealing Module" en su interior. Nos vamos acercando. Mantenemos la modularidad, ya que AppUI.Carruseles, así como el resto, los puedo definir en archivos distintos. Y hemos conseguido ocultar algunas funciones, haciéndolas privadas para el global scope y nuestro namespace. Por ejemplo, AppUI.Carruseles.next() es totalmente invisible. Sin embargo, me sigue sin gustar... Desde el global scope puedo acceder a todos los objetos de AppUI: Tabs, Menus y Carruseles. Desde AppUI.Carruseles, puedo acceder a AppUI.Menus.Options.add(), etc., y puede que no me interese.

Entonces se me ocurrió anidar el patrón "Revealing Module", dentro de otro "Revealing Module". Esto solo puede ser fruto de una mente retorcida... Veamos el resultado:

var AppUI = (function(){

  var Tabs = (function(){

    function show(){}
    function hide(){}
    function init(){
      show();
    }

    return {
      init:init
    }

  })();

  var Menus = (function(){

    var Options = (function(){

      function add(){}
      function remove(){}
      function init(){
        add(4);
      }

      return {
        init:init
      }

    })();

    function show(){}
    function toggle(){}
    function init(){
      show();
    }

    return {
      init:init
    }

  })();

  var Carruseles = (function(){

    function show(){}
    function start(){}
    function stop(){}
    function next(){}
    function prev(){}
    function init(){
      show();
      start();
    }

    return {
      init:init
    }

  })();

  function init(){
    Tabs.init();
    Menus.init();
    Carruseles.init();
  }

  return {
    init:init
  }

})();

De este modo, lo único que es visible desde el global scope, es AppUI.init(). Y por supuesto, desde Carruseles, no puedo ver a Menu.Options, como sucedía antes. A lo sumo, desde AppUI, tendría acceso únicamente a las funciones de Tabs, Menus y Carruseles, que hayamos querido hacer públicas, como por ejemplo, Menus.init(). Pero nada más.

El inconveniente, es que no podría dividirlo en varios archivos, por lo que hemos perdido toda modularidad: mal. Me sigue sin servir. Aunque puede que esto no suponga un inconveniente para el proyecto en el que estés...

Este patrón anidado, no es ni mejor ni peor que otros. Todo depende de lo que necesites, y supone simplemente una vuelta de tuerca más, para aquellos que sean un poco maniáticos con la limpieza del scope, en todos sus ámbitos. A mi personalmente me gusta más un namespece muuuuy limpio.

Si lo único que necesitamos es un lugar donde encapsular e inicializar todo un conjunto de funciones estancas entre ellas, pero mantener el scope lo más limpio posible, nos sobra más de la mitad del código anterior, y nos serviría con:

var AppUI = function(){
  
  (function Tabs (){
    function show(){}
    function hide(){}
    show()
  })();

  (function Menus (){
    (function Options (){
      function add(){}
      function remove(){}
      add(4);
    })();
    function show(){}
    function toggle(){}
    show();
  })();

  (function Carruseles (){
    function show(){}
    function start(){}
    function stop(){}
    function next(){}
    function prev(){}
    show();
    start();
  })();

}

Y cuando queramos iniciarlizar nuestra función, por ejemplo, una vez cargado el DOM:

AppUI();

domingo, 7 de julio de 2013

Node.js: qué es y cuándo utilizarlo

Se trata de una "nueva" tecnología del lado del servidor, como pueda ser PHP, Ruby, .NET, etc., basada en el motor de JavaScript V8 de Google: una potente máquina virtual creada por Lars Bak

Ventajas de Node.js a nivel de servidor

Una ventaja de Node.js es el lenguaje con el que se crean sus aplicaciones: JavaScript. Si! Ahora podemos realizar consultas a base de datos, lectura de ficheros, procesamiento de imágenes, todo con javascript en el lado del servidor. Esto está haciendo que JavaScript, sea el lenguaje del momento, como lo fue PHP en su día, o Ruby.

Utilizando un servidor web tradicional, como pueda ser Apache, cada vez que solicitas un recurso web, éste crea un hilo separado, o invoca un nuevo proceso, para atender dicha solicitud. A pesar de que Apache responde rápidamente a las solicitudes, y limpia el proceso una vez que ha terminado, este sistema puede necesitar un montón de recursos. Una aplicación web con muchas visitas, puede tener serios problemas de rendimiento.

Node.js, por el contrario, utiliza un sistema de E/S asíncrono y basado en eventos y callbacks de funciones. Es decir, todo lo realiza en un único hilo: se queda escuchando a ciertos eventos que ocurran en nuestra aplicación, y cuando ocurren, responde en consecuencia, de modo asíncrono, es decir, un evento no bloquea a otro. Dicho de otro modo, crea miles de subprocesos y trata a sus outputs como streams. Esto supone una mayor velocidad de respuesta. Os dejo un enlace donde podéis comprobar vosotros mismos, que Node.js es muchísimo más rápido sirviendo peticiones que Apache: https://code.google.com/p/node-js-vs-apache-php-benchmark/wiki/Tests

Ventajas de Node.js a nivel de desarrollador

Dejo para el final, la que para mi supone a día de hoy la principal ventaja: tener Node.js instalado en tu entorno de desarrollo local, te abre la puerta a una inmensa cantidad de herramientas, con las que automatizar tediosos procesos y mejorar tu rendimiento como desarrollador: es decir, te permite centrarte en el desarrollo y olvidarte de lo demás. Independientemente del lenguaje que uses después, con Node.js instalado en local, puedes utilizar:

  • Grunt.js: imprescindible procesador de tareas, y watchers, con el que automatizar el compilado de tus archivos a varios entornos. Es decir, podemos trabajar con preprocesadores como Sass, Haml, CoffeScript, etc., con la ventaja que esto ya supone, pero además, unificar todos sus watchers en un único lugar, y compilar varias salidas al mismo tiempo, en diferentes formatos, según lo necesitemos. Por ejemplo, si trabajas con Sass, Less o Stylus, puedes decidir compilar para producción un CSS minificado, y al mismo tiempo, compilar para un entorno de desarrollo, otro sin minificar en otra carpeta. Con Grunt puedes también usar JSHint, que en tiempo real irá diciéndote si en tus .js estás cometiendo errores sintácticos, en qué linea, y cómo resolverlos. Y si a la vez le pones Haml, Yaml, Ugligy, etc. pues lo mismo, puedes minificar la salida para producción, pero para tu entorno de pruebas, dejarlo sin minificar. Más información sobre Grunt.
  • Bower: gestor de paquetes o librerías desarrollado por Twitter. Cada vez que inicias un proyecto, Bower se encargará por ti, de descargar las versiones que necesites de las librerías javascript que necesites, o si lo prefieres, que descargue siempre la última versión, y coloque los archivos donde tu le digas: jQuery, bootstrap, CoffeeScript, etc.: aquí tenéis toda la lista de paquetes: http://sindresorhus.com/bower-components/. Más información sobre Bower.
  • Yo: un paquetizador de entornos, por decirlo de alguna manera, que se integra con Grunt. Te permite configurar el árbol de directorios de cada nuevo proyecto como a ti te guste: él te crea la estructura por ejemplo, para un proyecto con HTML5, o PHP. Aún no he conseguido que funcione demasiado bien bajo Windows, y estoy en búsqueda de alternativas... Más información sobre Yo.
  • All together now!: Yeoman, un conjunto de herramientas integradas entre sí: el gestor de entornos Yo, el gestor de librerías Bower, y el automatizador de tareas Grunt. Más información sobre Yeoman.

¿Cúando utilizar Node.js?

Existen varios casos donde no tiene sentido utilizar Node.js. Por ejemplo, cuando nuestra aplicación necesite muchísima CPU, y tenga pocos procesos de E/S, como podría ser, codificando vídeo. Para eso, casi mejor utilizar otros lenguajes como C o C++.

Otro ejemplo donde no vamos a ver una gran ventaja utilizando Node.js, es si estamos programando una aplicación clásica contra base de datos: alta, baja, edición y modificación de registros. Aquí, Node.js no te va a dar más ventajas de las que te pueda dar PHP, o Ruby, ya que trabajarás con un único canal de E/S.

Donde si es adecuado Node.js, es en aplicaciones de una sola página, con mucho AJAX, o cuando necesitas hacer muchas cosas al mismo tiempo, sobre todo muchas operaciones E/S (acceso a ficheros, bases de datos,...) a la vez.

También obtendrás una gran ventaja en velocidad y rendimiento, con aplicaciones en tiempo real, que necesitan mantener una conexión persistente entre el navegador y el servidor (juegos online, chats, herramientas de colaboración, etc ).

Otro punto interesante, es que para Node.js, las peticiones y respuestas HTTP son streams, lo que supone poder parsear subidas de archivo en tiempo real: http://nodejs.org/api/stream.html

Conclusiones

Por supuesto, no a todo el mundo le gusta Node.js, especialmente cuando se convierte en un lenguaje de moda. Hace poco leí en un post que las gráficas de rendimiento y velocidad, se hacen contra Apache, y no contra PHP con Nginx, para favorecer a Node.js, y que éste último, era inseguro. Obviamente, sus conclusiones tampoco eran del todo ciertas: Nginx es un servidor web/proxy inverso, y puede ser usado también con Node.js. Es decir, para ser justos, habría que comparar PHP/Nginx, con Node/Nginx. Y respecto a la seguridad, depende del desarrollador, no de la herramienta.

Está claro, que cada tecnología, tiene su sentido en un determinado contexto, y hay que saber aplicar la mejor según las necesidades del proyecto. Supongo que por esto es importante conocer bien qué opciones tenemos, para ofrecer siempre el mejor servicio, y no quedarse atascado en tecnologías clásicas. Dicho de otro modo: todos los caminos conducen a Roma, pero por unos llegas antes que por otros. Cuantas más opciones conozcas, mejor para ti como desarrollador.