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 :)