GulpJS - Um gole de otimização no seu workflow

Em 2014 palestrei em alguns eventos evangelizando a galera sobre o uso do GulpJS. Hoje resolvi transformar essa palestra em post, para facilitar futuras consultas e poder fazer outros posts falando mais a fundo sobre GulpJS e seus plugins!

Mas precisamos começar de algum lugar! Vem comigo? :D

O que é o GulpJS?

O GulpJS é um task runner, assim como o Grunt.

E o que é um task runner?

Task Runner é o nome dado à uma ferramenta que centraliza e executa alguma tarefa repetitiva automaticamente. Eles existem para facilitar o seu dia-a-dia como desenvolvedor.

Elas são sinônimo de performance.

Performance

A ideia é dar performance ao desenvolvedor na hora de escrever códigos, e também performance ao entregar o projeto ao cliente final, deixando-o mais leve e rápido.

Um task runner pode executar tarefas rotineiras como minificação de arquivos, compilação de CSS com pré-processadores, minificação de imagens, deploy, etc., tudo isso de forma automatizada. Cool, hum? ;)

Para facilitar o entendimento, vou mostrar alguns exemplos de ferramentas que você precisa usar diariamente para executar essas tarefas:

Javascript Compressor

Após escrever seu JS, você cola seu código nessa ferramenta, e ela te devolve o código todo minificado, pronto para entregar em produção!

Lint de JS

O JSHint busca no seu código erros comuns, garantindo que você esteja usando as melhores práticas nas escrita do seu Javascript.

Existe também o JSLint, criado por Douglas Crockford. É uma ferramenta que tem um pouco mais de restrições que o JSHint, - JSHint é mais “maleável” - e é recomendado para quem gosta de “desafios maiores” :P

Testes (TDD/BDD/E2E)

Testar seu código não pode ser uma tarefa difícil, muito menos lenta, senão você acaba deixando eles de lado. Algumas ferramentas facilitam o trabalho, como o Mocha, Should, Jasmine, QUnit, Chai, Sinon, etc.

Pré-processadores

Com eles você “programa” dentro do CSS. Pode usar estruturas condicionais, funções, variáveis, includes, mixins, e ainda diminui muito a repetição de código. Os mais conhecidos são: Stylus, Sass e LESS.

Compressão de imagens

Ao exportar suas imagens, elas precisam ser tão leves quanto possível. Existem vários softwares que fazem esse trabalho, diminuindo o tamanho da imagem, mas mantendo a mesma qualidade. Uma ferramenta legal (e online) para fazer isso é o Compressor.io.

Sistema de controle de versão

Git, Mercurial, SVN são sistemas que ajudam a controlar a versão do seu projeto. Você pode desenvolver por partes e ter o controle de cada feature implementada no seu sistema.

Então você pensa: Nossa, eu posso usar milhões de ferramentas para otimizar meu projeto!

Passarinho locão

Mas ao mesmo tempo, você percebe que é muita coisa! Muita ferramenta para lembrar de usar, muito site para favoritar, e desiste de tudo isso! Vou entregar o projeto assim mesmo! Não vou conseguir lembrar de usar todas essas ferramentas!

Panda derrubando carrinho

E se pudéssemos automatizar esse processo?

Automatização

Uma vez vi uma palestra do Addy Osmani, onde ele disse que:

Automation isn’t about being lazy, It’s about being efficient.

Mas como é possível ser eficiente tendo que lembrar de milhões de coisas pra usar? Não dá pra centralizar tudo isso em uma única ferramenta? Será que é possível?

SIM! Nós podemos fazer isso usando NodeJS!

Instalando o NodeJS

A instalação do NodeJS é bastante simples: acesse o site do NodeJS, baixe a versão para o seu sistema operacional e siga as instruções.

Fazendo isso, você terá no seu terminal o comando node disponível!

O Node também traz junto com ele o gerenciador de pacotes NPM. Logo, você também já deve ter o comando npm disponível no seu terminal para gerenciar os pacotes do Node! :D

Para quem não sabe, um gerenciador de pacotes é o cara que vai centralizar os pacotes de software disponíveis para a ferramenta em questão.

É como os gerenciadores de pacote do seus sistema operacional:

No Linux, nós temos o apt-get nos linux Debian Like, yum do Fedora;
No Mac, brew ou ports;
No Windows, bem… no Windows não tem! Mas se tivesse, acho que seria algo assim:

Gerenciador de pacotes no Windows

:P

Mas zueiras à parte, vamos ver como utilizar o NPM:

Instalação de pacotes

Para instalar algum módulo NodeJS com o NPM, você pode usar o comando:

1
npm install <module name>

Ou ainda:

1
npm i <module name>

O i é apenas um alias para o install, ou seja, eles fazem a mesma coisa.

E <module name> é o nome do seu módulo. Você pode encontrar os módulos disponíveis no NPM aqui.

Instalando o GulpJS

Depois do Node e NPM instalados e funcionando, precisamos começar instalando o GulpJS:

1
npm i -g gulp

Com esse comando, instalamos o GulpJS globalmente (repare no -g do comando acima). Quando instalamos um módulo do Node globalmente, temos disponíveis no nosso terminal o comando que geralmente é o mesmo nome do módulo. Nesse caso, temos o comando gulp disponível :)

Vamos ver um exemplo na prática de como usá-lo. Crie a seguinte estrutura de arquivos:

1
2
3
.
├── gulpfile.js
└── package.json

No package.json ficarão todas as informações do nosso módulo. Podemos criá-lo manualmente, ou através do comando:

1
npm init

Ao utilizar o comando, serão feitas algumas perguntas relacionadas ao projeto. Vá dando enter para ignorar se não quiser preencher. Essas informações não serão relevantes para o nosso caso, usando um task runner.

Se não quiser usar o npm init, crie manualmente o package.json, com uma estrutura básica para um JSON, para que possamos começar a utilizá-lo. No arquivo package.json, adicione:

1
{}

É tudo o que precisamos para começar no package.json. Vamos agora instalar nossas dependências.

Dependências

Para que possamos usar o Gulp no nosso projeto, precisamos instalar algumas dependências. Uma delas é o próprio Gulp, mas dessa vez, instalado de forma local:

1
npm i --save-dev gulp

Repare na flag --save-dev. Na primeira vez, instalamos o Gulp globalmente (-g) para termos o comando gulp no terminal. Agora, instalamos ele localmente, em modo de desenvolvimento (--save-dev), para utilizar os métodos dele dentro do nosso gulpfile.js.

Usando essa flag, a dependência é salva automaticamente no nosso package.json. O porquê disso explicarei mais à frente.

Agora nosso arquivo package.json está assim:

1
2
3
4
5
{
"devDependencies": {
"gulp": "^3.8.10"
}

}

Todas as dependências que precisaremos para utilizar enquanto estivermos desenvolvendo devem ser instaladas com a flag --save-dev, que o NPM automaticamente vai salvar dentro dessa diretiva devDependencies.

Perceba também que foi criado um diretório node_modules na raiz do nosso projeto. Nesse diretório temos a seguinte estrutura:

1
.
└── /node_modules
    └── /gulp
        └── // ... arquivos do gulp ...

Ou seja, quando instalamos algum módulo com o comando npm install ou npm i, o módulo fica no diretório node_modules (salvo quando instalamos globalmente).

Agora, no gulpfile.js, adicione:

1
2
3
4
5
var gulp = require( 'gulp' );

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

Aqui é puro Javascript (no servidor, com NodeJS). Criamos uma variável gulp, que receberá o módulo gulp, que foi instalado localmente. Se você passa como parâmetro da função require somente uma string, sem definir caminho de arquivos (como ./, / ou ../, por exemplo), o NodeJS procura essa dependência dentro da pasta node_modules.

O .js no final do arquivo é opcional. E se vamos fazer require de um arquivo chamado index.js, o nome desse arquivo também é opcional.

Sabendo disso, podemos concluir que o var gulp = require( 'gulp' ); na verdade poderia ter sido chamado dessa forma:

1
var gulp = require( './node_modules/gulp/index.js' );

O método task() do objeto gulp cria uma task que será executada. Como primeiro parâmetro, passamos o nome da task. Depois, uma função de callback que será executada quando a task for chamada.

Para chamar essa task, vamos executar no terminal:

1
gulp default

Ou somente:

1
gulp

Rodando o comando gulp sem nenhum parâmetro, ele deduz que você tem uma task chamada default, que é a task padrão do Gulp. Ou seja: para a task default, o nome da task, na chamada pelo terminal, é opcional.

Executando esse comando, temos o seguinte resultado:

1
2
3
4
5
$ gulp
[16:20:50] Using gulpfile /var/www/01-testes/test-gulp/gulpfile.js
[16:20:50] Starting 'default'...
Hello Gulp!
[16:20:50] Finished 'default' after 476 μs

Na linha 2, ele mostra que encontrou o arquivo gulpfile.js e mostra o caminho do arquivo;
Na linha 3, a task que está sendo executada;
Na linha 4, printa a mensagem que colocamos em console.log();
Na linha 5 é mostrado o tempo que demorou para finalizar a task.

Reparou no tempo? Sabe o que significa o símbolo μs? MICROSSEGUNDOS!

Isso mesmo: microssegundos! A task roda muito rápido mesmo.

Mas essa task não faz nada.. só executa um console.log() ¬¬

Não faz nada

Concordo! Mas você verá que, quando estivermos executando tarefas de verdade, o tempo ainda assim será muito baixo! :D

Agora que já aprendemos como criar uma task no Gulp, que tal fazer algo realmente útil?

Minificar CSS

Vamos ver como podemos automatizar a minificação dos nossos arquivos CSS. Primeiro vamos instalar o módulo de minificação:

1
npm i --save-dev gulp-minify-css

Agora vamos adicionar o módulo ao nosso gulpfile.js:

1
2
3
4
5
6
7
8
var gulp = require( 'gulp' );
var minifycss = require( 'gulp-minify-css' );

gulp.task( 'default', function() {
gulp.src( './src/css/**/*.css' )
.pipe( minifycss() )
.pipe( gulp.dest( './public/css' ) );
});

Perae, tem muita coisa acontecendo aqui! Como vou saber tudo isso?

Calma gafanhoto, vou explicar linha por linha:

Já sabemos como funciona o gulp.task().
A task vai definir uma ação que queremos fazer. A nossa ação será minificar o CSS.

Para isso, precisamos ter um CSS para minificar. Crie um diretório src, e dentro dele um diretório css. Esse será o nosso CSS fonte, onde iremos escrever nosso CSS todo organizado, com espaços necessários para fazer leitura, etc.

Esse CSS será minificado, e a versão minificada ficará no diretório public/css (não precisa criar esse diretório).

Agora, crie um arquivo chamado style.css no diretório src/css e coloque o seguinte código:

1
2
3
4
5
body {
color: purple;
background: #fc0;
font: 4rem fantasy;
}

Voltando ao gulpfile.js: com o gulp.src(), você passa o source do CSS, ou seja, os arquivos que você está editando. Você pode passar um array de arquivos ou então utilizar o glob pattern para percorrer recursivamente o diretório ./src/css em busca de todos os arquivos .css.

Usar o .pipe(), é o mesmo que você usar o | (pipe) na linha de comando do Unix: a saída do primeiro comando é a entrada do próximo.

Então, pegamos os arquivos .css que estão em ./src/css, e mandamos executar o minifycss() em todos eles. A saída será todos os arquivos minificados. Mas eles ainda estão em memória, e precisam ser salvos em algum lugar.

É aí que vamos passá-los novamente para frente com o .pipe() para o comando gulp.dest(), que vai definir o caminho de destino dos nossos arquivos minificados.

Estamos dizendo ao Gulp que queremos que esses arquivos sejam salvos (fisicamente) no diretório ./public/css.

Ao executar novamente o comando gulp no terminal, olhe agora como ficou nossa estrutura de diretórios:

1
2
3
4
5
6
7
8
9
10
11
.
├── gulpfile.js
├── /node_modules
│   └── // ... dependências ...
├── package.json
├── /public
│   └── /css
│   └── style.css
└── /src
└── /css
└── style.css

Não precisamos criar o diretório public, pois ele foi automaticamente criado pelo Node.

E o nosso arquivo style.css, no diretório gerado public, ficou assim:

1
body{color:purple;background:#fc0;font:4rem fantasy}

Coisa linda, não? Sem precisar lembrar de passar em nenhum minify online xD

E olha o tempo que foi executado:

1
2
3
4
$ gulp
[18:49:35] Using gulpfile /var/www/01-testes/test-gulp/gulpfile.js
[18:49:35] Starting 'default'...
[18:49:35] Finished 'default' after 16 ms

O Gulp sempre vai executar muito rápido, independente da quantidade de tasks que você tiver, pois ele usa strems e trabalha com alta concorrência, ou seja, ele executa muitas coisas em paralelo, deixando tudo muito mais rápido.

Usando pré-processadores

Para usar pré-processadores com Gulp também é bastante simples. Vou mostrar como utilizá-lo com o Stylus.

Primeiro, precisamos instalar o módulo do Stylus. Execute no terminal:

1
npm i --save-dev gulp-stylus

Vamos tirar o minifycss por enquanto, para facilitar a visualização do nosso exemplo. No seu gulpfile.js:

1
2
3
4
5
6
7
8
var gulp = require( 'gulp' );
var stylus = require( 'gulp-stylus' );

gulp.task( 'default', function() {
gulp.src( './src/stylus/**/*.styl' )
.pipe( stylus() )
.pipe( gulp.dest( './public/css' ) );
});

Agora nós buscamos todos os arquivos em ./src/stylus, com a extensão .styl. Vamos criar esse diretório, e um arquivo chamado style.styl, com o seguinte conteúdo:

1
2
3
4
body
background purple
color #fc0
font 4rem fantasy

No Stylus, não precisamos de brackets ({}), nem dois-pontos (:) para separar as propriedades. E ele funciona com base na indentação.

Mas não é obrigatório remover. Você pode escrever no formato do CSS também, sem problemas.

Agora, execute novamente o gulp no terminal. Você terá no diretório public/css um arquivo .css compilado:

1
2
3
4
5
body {
background: #800080;
color: #fc0;
font: 4rem fantasy;
}

Normalmente, os módulos retornam funções, que podem receber parâmetros para configurar o que eles devem fazer. Nesse caso do Stylus, nós só passamos a função stylus(), para que o arquivo .styl transforme corretamente o código em CSS válido. Mas e se quisermos minificar?

Temos duas formas de fazer isso: a primeira é passando parâmetros na função stylus(). No gulpfile.js:

1
2
3
4
5
6
7
8
var gulp = require( 'gulp' );
var stylus = require( 'gulp-stylus' );

gulp.task( 'default', function() {
gulp.src( './src/stylus/**/*.styl' )
.pipe( stylus({ compress: true }) )
.pipe( gulp.dest( './public/css' ) );
});

Passamos como objeto da função stylus() o parâmetro compress: true. Executando o gulp novamente no terminal, agora temos nosso CSS em public/css minificado:

1
body{background:#800080;color:#fc0;font:4rem fantasy}

A outra forma de minificar, é usando o minifycss (que já vimos no primeiro exemplo), junto com o Stylus. Ficaria assim o seu gulpfile.js:

1
2
3
4
5
6
7
8
9
10
var gulp = require( 'gulp' );
var minifycss = require( 'gulp-minify-css' );
var stylus = require( 'gulp-stylus' );

gulp.task( 'default', function() {
gulp.src( './src/stylus/**/*.styl' )
.pipe( stylus() )
.pipe( minifycss() )
.pipe( gulp.dest( './public/css' ) );
});

É interessante lembrar que a ordem que você coloca as coisas interfere no resultado final. Porque colocamos o minifycss() depois do stylus()?

Por que a função stylus() vai retornar o CSS gerado. Se colocarmos o minifycss() antes dele, o CSS ainda não foi gerado, e o arquivo final não será minificado. Muita atenção com isso, ok? ;)

Mas qual seria a vantagem de utilizar o minifycss() junto com o stylus(), sendo que o stylus() já tem uma opção de comprimir os arquivos?

O Stylus tem uma diretiva chamada @css, onde você pode colocar códigos CSS “brutos”. Imagine que você baixou um plugin para usar no seu app ou site, e esse veio com um CSS. Se você está assistindo arquivos .styl, não é legal misturar as coisas, e colocar um arquivo .css junto.

O que você pode fazer é usar a extensão .styl e colocar todo o seu CSS na diretiva @css:

1
2
3
4
5
6
@css {
.plugin {
color: #000;
background: #ff0;
}
}

Só que, tudo o que está dentro dessa diretiva, o Stylus ignora. Ele não compila, nem ao menos minifica. Assim, seu código ficaria com parte dele sem minificar. Então você pode usar em conjunto o compress: true do stylus() e o minifycss() para minificar o que restar dessas diretivas ;)

Ok, já sei como minificar CSS e também como utilizar um pré-processador. Mas toda vez que eu alterar um arquivo, vou precisar ficar rodando o comando gulp no terminal? Não tem como o Gulp ficar assistindo meus arquivos, como o SASS faz?

O método watch()

Claro que tem! Você pode usar o método watch() pra isso! Vamos mudar um pouco a estrutura do nosso gulpfile.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var gulp = require( 'gulp' );
var minifycss = require( 'gulp-minify-css' );
var stylus = require( 'gulp-stylus' );

gulp.task( 'css', function() {
gulp.src( './src/stylus/**/*.styl' )
.pipe( stylus() )
.pipe( minifycss() )
.pipe( gulp.dest( './public/css' ) );
});

gulp.task( 'default', function() {
gulp.watch( './src/stylus/**/*.styl', [ 'css' ] );
});

O que eu fiz foi, basicamente, renomear a task default para css e criar uma nova task default, com o watch().

No primeiro parâmetro, você passa os arquivos que você quer assistir. E no segundo, você passa um array de tasks que precisam ser executadas toda vez que você fizer alguma alteração nesses arquivos do primeiro parâmetro! Bem simples!

Agora, executando o comando gulp no terminal, ele não vai te devolver o prompt para voltar a digitar, pois estará assistindo às alterações dos arquivos. Para parar o watch(), use o Ctrl + C.

Esses são os métodos que você precisa conhecer do Gulp:

Cada um desses métodos tem mais opções além das que mostrei. A documentação completa está no links de cada um, acima. Mas para começar a brincar com Gulp, isso que eu mostrei já vai resolver!

Mais ainda temos outro problema: toda vez que estivermos compilando os arquivos, precisamos olhar no terminal para ver se já compilou, voltar no browser e recarregar a tela. Olhar no terminal, ir até o browser, recarregar a tela… infinitamente. Não dá pra melhorar isso?

Assistindo

Notificações

O Gulp tem vários plugins que você pode utilizar. Um que é muito útil, é o gulp-notify. Ao finalizar uma task (ou no momento em que você escolher que quer executá-lo), ele utiliza as notificações do seu sistema para dizer que aquele momento chegou! Vamos instalar ele agora mesmo!

1
npm i --save-dev gulp-notify

Agora vamos configurar nosso gulpfile.js, adicionando o notify() para mostrar uma mensagem assim que o nosso arquivo CSS for gerado:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var gulp = require( 'gulp' );
var minifycss = require( 'gulp-minify-css' );
var stylus = require( 'gulp-stylus' );
var notify = require( 'gulp-notify' );

gulp.task( 'css', function() {
gulp.src( './src/stylus/**/*.styl' )
.pipe( stylus() )
.pipe( minifycss() )
.pipe( gulp.dest( './public/css' ) )
.pipe( notify( 'CSS OK!' ) );
});

gulp.task( 'default', function() {
gulp.watch( './src/stylus/**/*.styl', [ 'css' ] );
});

Agora, executando o comando gulp novamente, temos a notificação:

Notificação com gulp-notify

Uhuull!!

Uhuull!!

Plugins

O Gulp tem milhões de outros plugins que você pode usar. Para encontrar todos os plugins homologados, acesse: http://gulpjs.com/plugins/

Se não encontrar um plugin que você precisa, você pode facilmente usar algum módulo do NodeJS para criar sua tarefa, já que você escreve o seu gulpfile.js em puro NodeJS. Todos os pacotes do Node você encontra em https://www.npmjs.com/

Na página do plugin, tanto no NPM como no Github, você vai encontrar a documentação de como utilizá-lo. Não tem segredo.

A flag –save-dev

Lembra que instalamos todas as nossas dependências com a flag --save-dev? Porque isso é importante?

Como você pôde perceber, todas as dependências ficam listadas no arquivo package.json. Se você quiser começar um novo projeto, e for utilizar as mesmas configurações que você utilizou da última vez, você só precisa executar dois simples passos:

  1. Copiar os arquivos package.json e gulpfile.js para o diretório do novo projeto;
  2. Executar o comando npm i.

Fazendo isso, o NPM irá ler o seu package.json e, todos os módulos encontrados em dependencies e devDependencies serão automaticamente instalados. Você não precisa nem copiar a pasta node_modules (que ficará bem grande com o tempo), pois ela será criada automaticamente ao executar o npm i :D

Conclusão

Existem vários plugins que irão facilitar o seu dia-a-dia usando o GulpJS. Nos próximos artigos, vou mostrar alguns deles, que acho bastante importante utilizar, mostrando também algumas boas práticas de como você poderá organizar suas tasks para que seu gulpfile.js não fique gigantesco. Fique ligado!

O que achou do GulpJS? Já conhecia? Utiliza outro task runner? Gostaria de compartilhar como é seu workflow? Comente! :D

Sobre o #1postperday: https://blog.da2k.com.br/2014/12/31/um-post-por-dia/

Tem alguma sugestão para os próximos posts do #1postperday? Deixe ela aqui: https://github.com/fdaciuk/fdaciuk.github.io/issues/1