Iniciando com Gulp

gulp_logo

Nesse próximo artigo vamos entender melhor para que server e como podemos utilizar essa ferramenta de automação de tarefas que tem feito bastante barulho no cenário atual de desenvolvimento.

O que é?

O Gulp nada mais é que um build-tool and task-runner, ou seja, a grosso modo é uma ferramenta de automação de tarefas para build. Com a ajuda dessa ferramenta podemos criar tarefas que nos auxiliam durante o processo de desenvolvimento de uma aplicação javascript. Existem outras ferramentas com o mesmo propósito, uma delas é o popular Grunt. Podemos fazer uma pequena comparação entre essas duas ferramentas.

Grunt Gulp
Usa sintaxe literal Usa o conceito de Node Streams e Pipes
Uma infinidade de plugins disponíveis A quantidade de plugins vem aumentando de forma considerável
Grande suporte da comunidade, mesmo em ambiente Windows Um grande aumento na quantidade de desenvolvedores que migram do Grunt para Gulp vem sendo notado
Tasks escritas baseadas em configurações Tasks escritas de forma imperativa usando o conceito de code over configuration
Executa tasks de forma síncrona Executa tasks de forma assíncrona

Infelizmente não podemos avaliar uma ferramenta como sendo “melhor” do que a outra, ambas tem o seu propósito e resolvem os mesmos tipos de problemas de formas diferentes. É uma questão de gosto do desenvolvedor, se você prefere escrever as suas tasks de build de forma literal parecida com uma configuração, Grunt é a melhor escolha para você, no entanto se você não abre mão do conceito de code over configuration, nesse caso você vai se dar muito bem com o Gulp.

Para que eu poderia usá-lo?

São muitas as possibilidades de uso dessa ferramenta, mas vamos citar as mais usadas entre os desenvolvedores.

  • Concatenar arquivos
  • Minificar arquivos
  • Compilar arquivos
  • Renomear arquivos
  • Analisar a qualidade do código fonte
  • Executar testes unitários
  • Mover arquivos para outros diretórios

Desenvolvimento tradicional

Antes das ferramentas de build-tool and task-runner o desenvolvimento, principalmente do front-end era um tanto quanto trabalhoso.
Vamos imaginar o seguinte cenário, você é um(a) experiente desenvolvedor(a) front-end e está preocupado com a escalabilidade e organização da sua aplicação. Você tem notado que a sua aplicação vem crescendo de forma exponencial e já está se tornando algo inviável a adição de tantos scripts nas suas páginas html.

<script src="src/vendors/jquery.js"></script>
<script src="src/vendors/jquery-validation.js"></script>
<script src="src/vendors/jquery.jcarousel.js"></script>
<script src="src/vendors/underscore.js"></script>
<script src="src/core/helpers.js"></script>
<script src="src/core/client.js"></script>
<script src="src/core/login.js"></script>
...

Podemos notar que a medida que a nossa aplicação vai crescendo a tendência é adicionarmos novas chamadas para esses scripts. Isso nos gera um outro problema que é a impossibilidade de servirmos para as nossas páginas, arquivos únicos e minificados. Ou seja, não podemos ter algo como:

<script src="src/vendors.js"></script>
<script src="src/core.min.js"></script>

Onde vendors.js é a junção de todas as nossas bibliotecas de terceiros:

<script src="src/vendors/jquery.js"></script>
<script src="src/vendors/jquery-validation.js"></script>
<script src="src/vendors/jquery.jcarousel.js"></script>
<script src="src/vendors/underscore.js"></script>

E o arquivo core.min.js contém os nossos códigos de aplicação.

<script src="src/core/helpers.js"></script>
<script src="src/core/client.js"></script>
<script src="src/core/login.js"></script>
...

Esse é só um dos inúmeros problemas que a abordagem de desenvolvimento tradicional nos traz, não vou exemplificar mais cenários pois o post ficaria extremamente extenso. O importante aqui é entender o ganho ao se usar um build-tool and task-runner e como usá-lo.

Como eu resolvo esses problemas usando o Gulp?

Como o Gulp é desenvolvido em javascript e roda diretamente em cima do Node.js primeiramente vamos nos certificar de que o mesmo esteja devidamente instalado em nosso computador, caso esteja podemos seguir com o processo de instalação.

Para instalar o Gulp de forma global, executamos o seguinte comando no terminal:

$ npm install -g gulp

Com o comando a seguir é possível ver a versão instalada:

$ gulp -v

Após concluída a instalação, vamos criar na raiz do nosso projeto o arquivo package.json, nesse arquivo vamos adicionar as nossas dependências de desenvolvimento.

{  
   "name":"gulptest",
   "version":"0.0.1",
   "devDependencies":{  
      "gulp"          : "~3.9.0",
      "gulp-concat"   : "~2.6.0",
      "gulp-uglify"   : "~1.2.0",
      "gulp-filesize" : "~0.0.6",
      "gulp-jshint"   : "~1.11.2"
   }
}

Para instalar as dependências vamos executar:

$ npm install

Agora na raiz do nosso projeto, vamos criar um arquivo chamado gulpfile.js onde iremos implementar as nossas tarefas.

Com todas as dependências instaladas, vamos dar uma olhada na nossa estrutura de diretórios e como a nossa aplicação está distribuída.

Estrutura de diretórios e arquivos

As nossas bibliotecas de terceiros vão ficar no diretório src/vendors/.
O diretório src/core/ será responsável por armazenar os nossos arquivos de aplicação.
No diretório public/ armazenamos os nossos assets.
Atenção ao diretório public/dist/, é nele que vamos adicionar os arquivos processados durante o nosso build. Esses arquivos serão disponibilizados para a página index.html.

Para tornar o nosso exemplo mais fácil de ser compreendido, vamos adicionar um pequeno trecho de código em nossos arquivos de aplicação.

src/core/client.js

/**
 * The client entity.
 */
var Client = (function(id, name) {
  "use strict";

  function getId() {
    return id;
  }

  function getName() {
    return name;
  }

  return {
    getId: getId,
    getName: getName
  };

});

src/core/helpers.js

/**
 * Some useful methods.
 */
var Helpers = {
  isEmpty: function(value) {
    return value === undefined || value === null || value === '';
  },
  isNumber: function(value) {
    return (typeof value === 'number');
  }
};

src/core/login.js

/**
 * Authenticator object.
 */
var Login = (function() {
  "use strict";

  return {
    // do the authentication
    authenticate: function(email, password) {
      console.log("Authenticating: " + email)
    }
  };
});

Pronto, já temos tudo o que precisamos para começar a escrever as nossas tasks.
Primeiramente, iremos criar uma única task que imprime uma mensagem no terminal, essa é a coisa mais básica que podemos fazer usando o Gulp, porém será um feedback para sabermos se tudo está funcionando corretamente.

Vamos editar o arquivo gulpfile.js e adicionar o seguinte trecho de código:

var gulp = require('gulp');


gulp.task('default', function() {
  console.log('Hello World!');
});

Agora vamos executar o comando abaixo.

$ gulp

A saída desse comando deve ser algo como:

[09:36:36] Using gulpfile ~/gulptest/gulpfile.js
[09:36:36] Starting 'default'...
Hello World!
[09:36:36] Finished 'default' after 88 μs

Se você visualizou algo parecido com a saída mostrada acima, parabéns, você está com o ambiente pronto para criarmos as tasks que realmente serão úteis.

Juntando arquivos

Para a nossa primeira task, vamos implementar uma funcionalidade capaz de concatenar os nossos arquivos da pasta src/vendors/, transformando-os em um único arquivo chamado vendors.min.js. Após a concatenação vamos mover o arquivo gerado para o diretório public/dist/. Novamente abra o gulpfile.js e adicione a nova task.

var gulp = require('gulp');
var concat = require('gulp-concat');


gulp.task('buildVendors', function() {
    return gulp.src(['./src/vendors/*.js'])
        .pipe(concat('vendors.js'))
        .pipe(gulp.dest('./public/dist/'))
});

gulp.task('default', ['buildVendors']);

Ao executar o comando:

$ gulp

Espera-se que a saída seja algom como:

[21:18:49] Using gulpfile ~/gulptest/gulpfile.js
[21:18:49] Starting 'buildVendors'...
[21:18:49] Finished 'buildVendors' after 19 ms
[21:18:49] Starting 'default'...
[21:18:49] Finished 'default' after 9.06 μs

Abrindo o arquivo public/dist/vendors.js podemos notar que todos os nossos arquivos de terceiros estão agora contidos em um único arquivo.

Minificando arquivos

Essa funcionalidade é bastante importante quando estamos falando de assets para produção, é uma boa prática fornecer ao navegador do nosso usuário arquivos minificados. A minificação de arquivos traz algumas vantagens relacionadas a performance. Ao removermos espaços desnecessários, tabulações, quebras de linha e comentários do nosso código, estamos diminuindo o tamanho do arquivo final que o navegador do usuário terá de baixar.

Então vamos adicionar mais essa task:

var gulp = require('gulp');
var concat = require('gulp-concat');
var uglify = require('gulp-uglify');


gulp.task('buildVendors', function() {
    return gulp.src(['./src/vendors/*.js'])
        .pipe(concat('vendors.js'))
        .pipe(gulp.dest('./public/dist/'))
});

gulp.task('buildScripts', function() {
    return gulp.src(['./src/core/*.js'])
        .pipe(concat('core.min.js'))
        .pipe(uglify({mangle: false}))
        .pipe(gulp.dest('./public/dist/'))
});

gulp.task('default', ['buildVendors', 'buildScripts']);

Ao executar o comando gulp novamente o arquivo public/dist/core.min.js será gerado. Abrindo esse arquivo podemos notar que o conteúdo do mesmo está minificado, ou seja, todo o código está em uma linha só, sem tabulações, sem quebras de linha e sem comentários. Isso aconteceu porque usamos o plugin gulp-uglify. Um detalhe bastante interessante sobre esse plugin é a possibilidade de tornar todo o seu código mais simples para o interpretador. Para isso basta trocar o valor do parâmetro mangle de false para true, dessa forma todo o nosso código será simplificado, trocando nomes de funções como por exemplo: getName() para algo como: f(). Isso torna ainda menor o tamanho final do arquivo, aumentando a velocidade de carregamento do mesmo. O grande problema dessa abordagem é debugar o arquivo em produção, pois quando uma função apresentar algum problema o erro apresentado vai ser referenciado a função f() e não a função getName(). Portanto, enquanto estiver desenvolvendo a sua aplicação mantenha o parâmetro mangle como false, e quando o seu código estiver homologado pronto para produção, altere o parâmetro para true.

Validando a qualidade do nosso código

Uma outra coisa bastante legal que podemos fazer é validar a qualidade do código que estamos escrevendo.
Para isso usaremos o plugin gulp-jshint.
JSHint é uma ferramenta que nos ajuda a detectar erros e potenciais problemas no nosso código javascript.

Vamos fazer com que antes que a nossa task buildScripts execute os passos já descritos, o nosso código seja validado pelo JSHint.

Note que a função authenticate contida no arquivo src/core/login.js, usa a declaração “use strict”, forçando-nos a adicionar “;” (ponto e vírgula) a cada final de instrução.

/**
 * Authenticator object.
 */
var Login = (function() {
  "use strict";

  return {
    // do the authentication
    authenticate: function(email, password) {
      console.log("Authenticating: " + email)
    }
  };
});

Usando o JSHint, a ausência desse “;” no final da linha provocará um erro quando a task for executada.
Vamos dar uma olhada como ficaria essa task;

var gulp = require('gulp');
var concat = require('gulp-concat');
var uglify = require('gulp-uglify');
var jshint = require('gulp-jshint');


gulp.task('buildVendors', function() {
    return gulp.src(['./src/vendors/*.js'])
        .pipe(concat('vendors.js'))
        .pipe(gulp.dest('./public/dist/'))
});

gulp.task('buildScripts', function() {
    return gulp.src(['./src/core/*.js'])
        .pipe(jshint())
        .pipe(jshint.reporter('default'))
        .pipe(concat('core.min.js'))
        .pipe(uglify({mangle: false}))
        .pipe(gulp.dest('./public/dist/'))
});

gulp.task('default', ['buildVendors', 'buildScripts']);

Quando tentamos executar essa task, o seguinte erro é exibido:

[09:01:25] Using gulpfile ~/gulptest/gulpfile.js
[09:01:25] Starting 'buildVendors'...
[09:01:25] Starting 'buildScripts'...
~/gulptest/src/core/login.js: line 10, col 52, Missing semicolon.

1 error
[09:01:25] Finished 'buildScripts' after 65 ms
[09:01:25] Finished 'buildVendors' after 72 ms
[09:01:25] Starting 'default'...
[09:01:25] Finished 'default' after 8.17 μs

Ao adicionar o “;” que está faltando, a nossa task volta a ser executada normalmente :)

Exibindo o tamanho dos arquivos processados

Por fim, podemos adicionar uma funcionalidade para exibir o tamanho dos nossos arquivos finais a cada build executado. Isso pode nos ajudar a identificar scripts que estão ficando muito “pesados” e causaram problemas de performance.

Segue as alterações necessárias:

var gulp = require('gulp');
var concat = require('gulp-concat');
var uglify = require('gulp-uglify');
var jshint = require('gulp-jshint');
var filesize = require('gulp-filesize');


gulp.task('buildVendors', function() {
    return gulp.src(['./src/vendors/*.js'])
        .pipe(concat('vendors.js'))
        .pipe(gulp.dest('./public/dist/'))
        .pipe(filesize())
});

gulp.task('buildScripts', function() {
    return gulp.src(['./src/core/*.js'])
        .pipe(jshint())
        .pipe(jshint.reporter('default'))
        .pipe(concat('core.min.js'))
        .pipe(uglify({mangle: false}))
        .pipe(gulp.dest('./public/dist/'))
        .pipe(filesize())
});

gulp.task('default', ['buildVendors', 'buildScripts']);

Vejamos agora o resultado quando executamos as nossas tasks novamente:

[09:09:58] Using gulpfile ~/gulptest/gulpfile.js
[09:09:58] Starting 'buildVendors'...
[09:09:58] Starting 'buildScripts'...
[09:09:58] Size core.min.js : 249 B
[09:09:58] Finished 'buildScripts' after 66 ms
[09:09:58] Size vendors.js : 23.46 kB
[09:09:58] Finished 'buildVendors' after 73 ms
[09:09:58] Starting 'default'...
[09:09:58] Finished 'default' after 5.46 μs

Conclusão

Com o uso dessa ferramenta aumentamos muito a produtividade no desenvolvimento, além de melhorar a arquitetura das nossas aplicações, tornando-as mais robustas e escaláveis. Outros problemas podem surgir a medida que a sua aplicação vai crescendo, para contornar esses problemas, possivelmente você encontrará um plugin para auxilia-lo. Experimente esse tipo de ferramenta, não só o Gulp, mas todas as outras que tem como propósito resolver esses pequenos problemas do dia-a-dia.

Dúvidas ou sugestões, favor deixar nos comentários, responderei assim que possível :)

Criando objetos com Module Pattern

Neste post iremos falar um pouco a respeito de um Design Pattern que pode nos ajudar bastante na hora de definirmos um objeto em javascript. Esse pattern é o Module, com ele conseguimos definir o encapsulamento do nosso objeto, além de protegermos as variáveis não deixando-as expostas fora do contexto do objeto. Para usarmos esse pattern da maneira correta, antes é interessante entender alguns conceitos que estão por trás do mesmo.

O Module se apoia no uso de Closures, anonymous functionself-executing ou self-invoking functions do Javascript.

Anonymous function

Vamos começar falando sobre o anonymous function, a maneira como definimos uma função anonima no javascript segue a seguinte sintaxe:

var foo = function() {
  console.log("Calling foo");
};

Agora se chamarmos essa função:

foo();

Teremos a saída:

> Calling foo

Como toda função em javascript é um objeto, podemos atribuir sua definição a uma variável e depois chamá-la usando o mesmo nome.

Self-executing

Agora, e se quiséssemos que toda vez que esse código fosse interpretado pelo navegador o nosso método foo fosse chamado?.
Para que isso seja possível, recorremos ao uso da técnica self-executing, que resume-se em adicionar um parênteses no final da definição do método, forçando-o a ser invocado ao término de sua definição, veja o exemplo abaixo:

var foo = function() {
  console.log("Calling foo");
}();

Teremos como saída:

> Calling foo

Observe que não foi necessário chamar a função foo.

Closures

Closures são comumente usadas para fazer wrapper de objetos em javascript, com essa técnica criamos escopos dentro de nossas funções, a visibilidade de variáveis e funções representam uma certa hierarquia. Por conta dessa característica podemos criar variáveis e métodos “privados”.
Basicamente uma Closure é uma função dentro de outra função e que tem acesso as variáveis definidas pela função hospedeira mas que não expõem suas variáveis e funções internas para o escopo acima.
Parece confuso? Vamos ver alguns exemplos dos escopos de uma variável dentro de uma Closure para entendermos melhor tudo isso:

function outside() {
  var myName = "Rafael";
  console.log(myName);

  function inside() {
    var myName = "Anthony";
    console.log(myName);
  }

  inside();
}

Ao chamarmos a função outside.

outside();

Teremos a seguinte saída.

> Rafael
> Anthony

Como podemos notar, existem duas variáveis com o mesmo nome, ou seja, temos dois myName, no entanto existe uma hierarquia na definição. A variável dentro do escopo outside define a variável myName, e na função inside uma variável de mesmo nome é criada só que para o escopo interior. Portanto a função inside, consegue enxergar as variáveis definidas dentro de outside, mas como temos uma variável de mesmo nome no escopo inside ela é usada por padrão, o inverso não é possível.
Vejamos mais um exemplo:

function outside() {
  var myName = "Rafael";
  console.log(myName);

  function inside() {
    var myName = "Anthony";
    console.log(myName);
  }

  inside();

  console.log("My name is " + myName);
}

outside();

Saída:

> Rafael
> Anthony
> My name is Rafael

A função outside não consegue enxergar o escopo da função inside, portanto ela só conhece a variável myName definida por ela mesma.
Agora se não definirmos a variável myName dentro do escopo da função inside e imprimirmos a variável, podemos ver que myName definida dentro do escopo outside é acessível dentro do escopo inside.

function outside() {
  var myName = "Rafael";

  function inside() {
    console.log(myName);
  }

  inside();
}

outside();

Saída:

> Rafael

Já o inverso resulta em um erro de referência pois tentamos chamar uma variável indefinida.

function outside() {

  function inside() {
    var myName = "Rafael";
  }

  inside();

  console.log(myName);
}

outside();

Saída:

> ReferenceError: myName is not defined

Agora que já entendemos os conceitos de cada técnica por trás do Module Pattern, vamos dar uma olhada em algumas das inúmeras maneiras de criar um objeto em javascript:

Usando funções

function Client() {
  this.id = 123;
  this.name = "Rafael";
  this.active = true;
  this.getName = function() {
    return this.name;
  };
}

var client = new Client();
console.log(client.id);
console.log(client.getName());

Object Literals

var client = {
  id: 123,
  name: "Rafael",
  active: true,
  getName: function() {
    return this.name;
  }
};

console.log(client.id);
console.log(client.getName());

Singleton usando uma função

var client = new function() {
  this.id = 123;
  this.name = "Rafael";
  this.active = true;
  this.getName = function() {
    return this.name;
  };
};

console.log(client.id);
console.log(client.getName());

Usando prototype:

var Client = function(){};
Client.prototype.id = 123;
Client.prototype.name = "Rafael";
Client.prototype.active = true;
Client.prototype.getName = function() {
  return this.name;
};

var client = new Client();

console.log(client.id);
console.log(client.getName());

Ou então criando uma instância diretamente:

var client = new Object();
client.id = 123;
client.name = "Rafael";
client.active = true;
client.getName = function() {
  return client.name;
};

console.log(client.id);
console.log(client.getName());

Não vamos entrar nos detalhes e diferenças de cada implementação para não deixar o post extenso demais.

Vamos entender porque essas implementações não são as melhores abordagens.
Como podemos notar, em todos os exemplos a variável id, name e active estavam expostas permitindo um acesso externo através da instância ou simplesmente pelo nome de referência do objeto.
O Module Pattern nos ajudará a criar escopos “privados”, ou seja, vamos ter a oportunidade de impedir o acesso a variáveis diretamente, evitando assim que o nosso encapsulamento seja quebrado.
Muitas bibliotecas como o jQuery por exemplo, usam Module Pattern para determinar quais métodos devem ser expostos aos usuários e quais métodos devem permanecer em um contexto privado para uso interno de suas funções.

Então como é um objeto definido usando o Module Pattern? Vejamos:

var client = (function() {
  var id = 123,
      name = "Rafael",
      active = true;

  function getName() {
    return name;
  }

  return {
    getName: getName
  };
})();

console.log(client.id);
console.log(client.getName());

O modulo é definido usando um parênteses envolvendo a função principal, isso faz um Wrapper do objeto fechando o escopo interno.
As variáveis definidas dentro do modulo, ficam num contexto privado sendo acessadas somente pelas funções internas e nunca por alguém de fora.
Outro detalhe importante que devemos entender é a maneira como expomos uma variável ou um método para acesso externo.
Adicionando uma função e/ou variável dentro do return tornamos esses recursos públicos, permitimos que essa variável e/ou função estejam disponíveis para acesso externo.

Vejamos a saída desse código:

> undefined
> Rafael

Como a variável id não está dentro do return, ela não é visível no contexto externo, quem o chamar diretamente receberá um undefined como retorno.
Com isso em mente podemos criar métodos e/ou variáveis publicas e também privadas, vamos ver um exemplo mais completo:

var client = (function() {
  var id = 123,
      name = "Rafael",
      active = true;

  function getName() {
    return name;
  };

  function getInfo() {
    return "Id: " + id + ", Name: " 
        + name + ", active: " + active;
  };

  return {
    active: active,
    getName: getName
  };
})();

console.log(client.id);
console.log(client.active);
console.log(client.getName());
console.log(client.getInfo());

Saída:

> undefined
> true
> Rafael
> TypeError: undefined is not a function

Injeção de dependências

Podemos tirar inúmeras vantagens de adotar esse pattern, uma delas é a injeção de dependências. Através dessa técnica conseguimos passar para o nosso objeto um referência de um recurso do qual faremos uso.
Vamos imaginar que você precise fazer uso do jQuery dentro do seu objeto, se você já possuí o script em sua página, bastaria você fazer algo como:

var loginForm = (function() {

  function getFormReference() {
    return $("#signIn");
  }

  function validateForm() {
    var $form = getFormReference(),
        $user = $form.find('#user'),
        $password = $form.find('#user');

    return ($user.text() != '' && $password.text() != '');
  }

  function doTheAuthentication() {
    // ajax to authenticate user
    console.log('Authenticating the user...');
  }

  return {
    validate: validateForm,
    authenticate: doTheAuthentication
  };

})();

Repare que dentro do nosso método getFormReference(), fazemos uso de um seletor para encontrar o elemento de id signIn, essa abordagem funciona bem desde que você não possua outras bibliotecas de manipulação de DOM além do jQuery. Imagine que por algum motivo você precise usar além do jQuery a biblioteca Prototype.js. Como essas bibliotecas usam o $ como referência para criar os seletores, você precisaria de alguma forma dizer para o objeto loginForm que dentro do método getFormReference(), você quer usar o jQuery para fazer a seleção do elemento e não outra biblioteca.
Para resolver esse problema, podemos passar para o nosso objeto loginForm uma referência da biblioteca que queremos usar. A injeção dessa dependência é feita da seguinte maneira:

var loginForm = (function($) {

  function getFormReference() {
    return $("#signIn");
  }

  function validateForm() {
    var $form = getFormReference(),
        $user = $form.find('#user'),
        $password = $form.find('#user');

    return ($user.text() != '' && $password.text() != '');
  }

  function doTheAuthentication() {
    // ajax to authenticate user
    console.log('Authenticating the user...');
  }

  return {
    validate: validateForm,
    authenticate: doTheAuthentication
  };

})(jQuery.noConflict());

Agora dentro do nosso objeto a referência para $ é exatamente a biblioteca jQuery, pois passamos ela como parâmetro para o nosso objeto.

A maneira como usamos o objeto loginForm continua a mesma:

loginForm.validate();
loginForm.authenticate();

Saída:

> false
> Authenticating the user...

Conclusão

Existem inúmeros Patterns em Javascript que podem nos ajudar muito com problemas corriqueiros, esse é só um deles.
Usando essas técnicas tornarmos o nosso código mais coeso, seguro e de fácil manutenibilidade.

Dúvidas ou sugestões, favor deixar nos comentários, responderei assim que possível :)