Grunt: Automatizando tarefas de build

Grunt: Automatizando tarefas de build

Você já parou para pensar em quantas tarefas repetidas você executa na hora de fazer build/deploy do seu projeto de Frontend?

Vamos pensar em algumas mais comuns:

  • concatenar arquivos css/js;
  • minificar css/js;
  • processar arquivos sass/less;
  • minificar imagens;
  • minificar html;
  • fazer upload dos arquivos para o servidor;

Agora tente pensar em quanto tempo você gasta diariamente na realização dessas tarefas. Por alto, podemos dizer que perde-se tempo suficiente para até 5 xícaras de café na copa :D

Mas então você deve se perguntar, como agilizar esse processo e ganhar essas preciosas 5 doses diárias de café a mais? Eu te respondo, com o Grunt!.

O que é Grunt?

O que é Grunt?

O Grunt é uma ferramenta de linha de comando criada pelo Ben Alman, e é basicamente um executor tarefas. Sabe todas as tarefas repetitivas listadas agora a pouco, além de todas as outras que você realiza mas não foram listadas? Você pode deixar que o Grunt execute elas para você.

Por que usar um executor de tarefas?

Em uma palavra: automação. Quanto menos trabalho você tem executando tarefas repetitivas como minificação, compilação, teste de unidade, validação, etc, mais fácil se torna o seu trabalho.

Depois de configurado, um executor de tarefas pode realizar boa parte do trabalho para você com basicamente nenhum esforço.

Por que usar o Grunt?

O ecossistema do Grunt é enorme e continua crescendo diariamente. Com literalmente centenas de plugins disponíveis, você pode usar o Grunt para automatizar praticamente qualquer coisa com o mínimo de esforço.

Se ninguém tiver desenvolvido o que você precisa, criar e publicar o seu próprio plugin do Grunt é extremamente fácil. Aqui você encontra mais detalhes de como criar o seu plugin para o Grunt.

Quem usa Grunt?

Atualmente o Grunt é utilizado em uma grande quantidade de projetos Web pelo mundo afora. Projetos como jQuery e Bootstrap utilizam o Grunt para automatizar o build. A imagem abaixo demonstra algumas empresas/projetos que utilizam o Grunt.

Empresas/Projetos que utilizam Grunt.
Empresas/Projetos que utilizam Grunt.

Enfim, atualmente considero o Grunt como uma ferramenta indispensável para qualquer projeto de Frontend Web. Se você ainda não está usando, está perdendo tempo (literalmente)!

Mais detalhes sobre a ferramenta podem ser encontrados na página oficial do Grunt.

Grunt: Primeiros Passos

Grunt: Primeiros Passos

Então, vou usar o Grunt, do que eu preciso?

O primeiro passo é entender como o Grunt funciona. O Grunt é implementado em Javascript e roda no Node.js, então antes de instalar o Grunt você vai precisar ter o node e o npm instalados. Caso você não tenha seguem alguns links de como instalar o node e npm no Windows e como instalar o node e npm no Ubuntu.

Instalação do Grunt

Tendo instalado o node e o npm na sua máquina, o único trabalho para instalar o Grunt é rodar o seguinte comando:

  npm install -g grunt-cli

Isto vai habilitar o comando grunt no terminal.

Como você já deve ter notado, instalamos o grunt-cli e não o grunt, a função do grunt-cli é executar o grunt configurado para o projeto. Dessa forma é possível ter vários projetos, cada um rodando uma versão diferente do grunt sem problemas.

Configurando o projeto para utilizar o Grunt

Para um projeto utilizar o Grunt, são necessários pelo menos dois arquivos, o package.json e o Gruntfile.js.

package.json

O arquivo package.json é utilizado pelo npm para armazenar os meta-dados do seu projeto. Informações como nome do projeto, autor, url, licença, repositório e dependências, são todas armazenadas neste arquivo.

Então é por isso que o Grunt precisa dele, ao instalar um plugin do grunt no projeto, esse plugin é registrado como dependência no arquivo package.json.

O arquivo package.json deve estar no diretório raiz da sua aplicação. Você pode criar um através do comando npm init.

Ao rodar o comando npm init ele vai solicitar os dados do seu projeto, nem todos são obrigatórios. Executei o comando aqui, preenchi algumas informações e gerei o seguinte arquivo package.json:

{
  "name": "projeto",
  "version": "1.0.0",
  "description": "Meu Projeto",
  "main": "package.json",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Andrew Cavalcante Pacifico",
  "license": "Apache"
}

Para instalar o Grunt e os plugins dele no seu projeto, utilize sempre o comando npm install <dependência> --save-dev, dessa forma a dependência é automaticamente adicionada à sessão devDependencies do arquivo package.json.

A importância de adicionar a dependência à sessão devDependencies do package.json, é que caso você precise configurar o projeto novamente em uma outra máquina, basta rodar o comando npm install, e todas as dependências do projeto (incluindo o Grunt e seus plugins) serão automaticamente instaladas.

Então vamos instalar o Grunt localmente em nosso projeto. Para isso basta executar o seguinte comando:

  npm install grunt --save-dev

Um diretório chamado node-modules deve ter sido adicionado ao seu projeto, este diretório conterá todas as dependências do seu projeto instaladas via npm.

Este diretório deve conter um outro diretório chamado grunt, o que indica que o Grunt foi instalado localmente no seu projeto.

Se você der uma olhada no arquivo package.json verá que ele conterá agora a seguinte sessão:

"devDependencies": {
  "grunt": "~0.4.5"
}

Isso significa que o grunt foi adicionado como dependência do projeto.

Caso você esteja utilizando algum software de controle de versão para o seu projeto, é importante ressaltar que apenas o arquivo package.json precisa ser versionado, o diretório node_modules deve ser deixado de fora, uma vez que ele é gerado automaticamente.

Gruntfile.js

O Gruntfile.js é o arquivo onde você vai definir e as tarefas e configurar os plugins do Grunt. Ele é um arquivo Javascript que deve ser colocado na raiz do seu projeto junto com o package.json, e assim como ele, também deve ser versionado.

O Gruntfile é formado basicamente pela configuração do projeto/plugins, pelo carregamento dos plugins do Grunt, definição das tarefas, e uma função que engloba tudo isso. Abaixo temos um exemplo de um Gruntfile básico.

  module.exports = function(grunt) {
    // configuração dos plugins
    grunt.initConfig({
    });

    // carregamento dos plugins
    grunt.loadNpmTasks('nome-do-plugin');

    // definição das tarefas
    grunt.registerTask('default', []);
  };

No código acima é possível perceber as três seções principais mencionadas.

Toda a configuração dos plugins e tarefas do Grunt deve ser feita através de um objeto passado como parâmetro para a função grunt.initConfig().

Todos os plugins instalados devem ser carregados através da chamada da função grunt.loadNpmTasks().

E por fim, você pode definir as suas tarefas através da função grunt.registerTask(). Cada plugin instalado automaticamente cria uma tarefa com o seu nome, mas se o objetivo é automatizar, não faz sentido termos que chamar todas as tarefas uma por uma, então você pode definir uma tarefa default que é executada sempre que o comando grunt for executado, e pode configurar esta tarefa para executar todas as outras.

Plugins do Grunt

Plugins do Grunt

Agora que você já sabe como instalar e configurar o Grunt, só o que falta é começar instalar os plugins. Como eu mencionei no início do artigo, o Grunt já tem plugin pra praticamente tudo, então vamos configurar alguns dos mais comuns para o nosso projeto.

Concat

Geralmente quando trabalhamos em um projeto mais complexo, o ideal é modularizar o nosso código Javascript, e dividir em diferentes arquivos para facilitar a manutenção. Porém para melhorar a performance do projeto, quanto menos arquivos carregados melhor. Então como proceder?

Neste caso podemos utilizar o plugin grunt-contrib-concat, que como o próprio nome já diz, é utilizado para concatenar arquivos. Dessa forma é possível possuir diversos arquivos com Javascript, e configurar o Grunt para juntar todos esses arquivos em um só.

Para instalar o plugin, basta executar o comando abaixo:

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

Depois disso, vamos criar o nosso Gruntfile.js, com o seguinte conteúdo:

module.exports = function(grunt) {
  // configuração dos plugins
  grunt.initConfig({
    concat: {
      dist: {
        src: [ 'js/src/functions.js', 'js/src/core.js' ],
        dest: 'js/dist/projeto.js',
      }
    }
  });

  // carregamento dos plugins
  grunt.loadNpmTasks('grunt-contrib-concat');

  // definição das tarefas
  grunt.registerTask('default', ['concat']);
};

No código acima carregamos o plugin grunt-contrib-concat, definimos que a tarefa default deverá executar a tarefa referente ao plugin, e definimos as configurações do plugin.

Nas configurações do plugin o que fizemos foi definir como source os arquivos functions.js e core.js ambos localizados no diretório js/src, e definimos como destino um único arquivo chamado projeto.js que deverá ser gerado no diretório js/dist (que é criado caso não exista).

Portanto, ao executar o comando grunt os arquivos functions.js e core.js serão automaticamente concatenados em um novo arquivo chamado projeto.js.

Mágica não é? :D

O plugin concat possui uma série de outras configurações possíveis, mais informações é só consultar a documentação do plugin.

Uglify

Uma das práticas mais conhecidas de quem trabalha com frontend é a de minificar arquivos Javascript e CSS, dessa forma é possível diminuir o tempo de carregamento da página melhorando a performance e a experiência do usuário.

Durante algum tempo, o que eu fazia em meus projetos era procurar algum site de minificação como o http://jscompress.com/, copiar e colar o meu código lá, pegar o código minificado e salvar em um arquivo .min.js. E quando eu alterava alguma coisa no arquivo? Exato, tinha que fazer tudo isso de novo.

O grunt-contrib-uglify é um plugin para o Grunt que faz exatamente isso pra você. Nele você pode especificar uma lista de arquivos js de entrada e um arquivo js de saída que terá o conteúdo dos arquivos de entrada, e já minificados.

Sabendo disso, então qual a necessidade do concat? Bem, geralmente em ambiente de desenvolvimento nós não queremos os arquivos minificados, então o ideal é configurar o grunt para executar o concat em desenvolvimento, e o uglify em uma task de build/deploy.

Então vamos lá, para instalar o plugin basta rodar o comando abaixo:

npm install grunt-contrib-uglify --save-dev

Após a instalação vamos adicionar a seguinte configuração ao nosso Gruntfile no objeto passado para a função grunt.initConfig:

uglify: {
  dist: {
    files: {
      'js/dist/projeto.js': [
        'js/src/functions.js', 
        'js/src/core.js'
      ]
    }
  }
}

Então adicionamos a linha para carregar o plugin:

grunt.loadNpmTasks('grunt-contrib-uglify');

E vamos definir uma nova task chamada deploy, que irá executar o uglify.

grunt.registerTask('deploy', ['uglify']);

Dessa maneira, ao executar o comando grunt no terminal, os seus arquivos Javascript serão concatenados em um único arquivo chamado projeto.js, e ao executar o comando grunt deploy eles serão concatenados e minificados.

CSSMin

Beleza, falei que é essencial minificar Javascript e CSS, e mostrei o Uglify que minifica o Javascript. Mas e o CSS?

Pra minificar o CSS tem esse cara aí, CSSMin.

A instalação do plugin segue o padrão dos plugins anteriores:

npm install grunt-contrib-cssmin --save-dev

A configuração do plugin no Gruntfile vai ficar assim:

cssmin: {
  dist: {
    files: {
      'css/dist/projeto.css': [
        'css/src/components.css',
        'css/src/core.css'
      ]
    }
  }
}

Então adicionamos o carregamento do plugin no Gruntfile:

  grunt.loadNpmTasks('grunt-contrib-cssmin');

E alteramos a definição da task de deploy para executar além do uglify, o cssmin:

  grunt.registerTask('deploy', ['uglify', 'cssmin']);

Nessa configuração do exemplo, assumimos que o nosso projeto possui dois arquivos css, um contendo as definições dos componentes utilizados na página, e outro com as configurações mais genéricas. No build nós vamos juntar esses arquivos e minifica-los em um único arquivo chamado projeto.css.

A mesma estratégia do Javascript pode ser utilizada também aqui, usar o concat em ambiente de desenvolvimento, e o cssmin para deploy.

Watch

O ser humano é sempre acha um problema em tudo :D , digo isso pois quando eu comecei a usar o Grunt, mesmo com todas as vantagens, eu reclamei do fato de precisar ir até o terminal e rodar o comando grunt todas as vezes que fizesse alguma alteração no meu projeto.

Se você estiver achando que vai precisar fazer a mesma coisa, adianto logo que não.

uhu

O Grunt possui um plugin chamado Watch responsável por ficar observando os seus arquivos e aguardando que alguma alteração aconteça, e quando acontecer, executar uma determinada tarefa.

Então, o primeiro passo como sempre, instalar o plugin:

npm install grunt-contrib-watch --save-dev

E vamos adicionar a configuração do plugin, que vai ser a seguinte:

watch: {
  scripts: {
    files: [
      'js/src/functions.js',
      'js/src/core.js'
    ],
    tasks : [ 'concat' ]
  }
}

Nessa configuração, nós definimos para o Watch ficar observando alterações nos nossos arquivos Javascript, e caso algum deles seja alterado, automaticamente a task concat será executada.

Adicionamos o carregamento do plugin:

grunt.loadNpmTasks('grunt-contrib-watch');

E dessa vez não vamos adicionar a task do plugin a nenhuma das tasks que nós definimos, pois uma vez que o watch gera uma task que fica escutando alterações, ele ocupa o terminal, então deve ser executado sozinho.

Portanto, após a instalação, carregamento e configuração do watch no nosso Gruntfile, só o que precisamos fazer é rodar o comando grunt watch. Ao fazer isso, o terminal deve exibir algo mais ou menos assim:

Grunt Watch aguardando alterações.
Grunt Watch aguardando alterações.

Isso indica que o grunt watch está executando e aguardando alguma alteração nos arquivos configurados para serem observados.

Ao realizar uma alteração em um dos arquivos, o que deve acontecer é mais ou menos isso:

Grunt Watch executando uma tarefa.
Grunt Watch executando uma tarefa.

Percebam o que aconteceu. O grunt watch detectou uma alteração no arquivo core.js então executou a task concat conforme foi definido na configuração.

Outros Plugins

Nós demos uma olhada por alto em alguns dos plugins mais básicos, e mais comuns. Deixo claro que todos eles têm ainda uma porção de outras configurações possíveis, portanto vale a pena dar uma olhada na documentação detalhada de cada um e adapta-los para a sua necessidade.

Mas agora que você já conhece o Grunt, o próximo passo é buscar outros plugins que atendam às suas necessidades, como eu falei, tem plugin pra praticamente tudo. Abaixo listo alguns dos plugins que eu utilizo nos meus projetos:

  • ImageMin: Compressão de imagens;
  • Copy: Copia arquivos e diretórios;
  • Exec: Executa comandos shell;
  • HTMLMin: Minificação de HTML;
  • Replace: Alteração de trechos de um arquivo seguindo um padrão;
  • Sass: Compila arquivos do SASS para Css;
  • Rsync: Utiliza o rsync para sincronizar diretórios. Excelente como alternativa ao FTP para fazer deploy de aplicações.

Enfim, existem centenas de outros plugins disponíveis, a página oficial do Grunt oferece uma lista de plugins disponíveis, é só ir lá e dar uma olhada.

Conclusão

Conclusão

Então pessoal, com isso já dá pra ter uma boa noção de como usar o Grunt nos seus projetos. Lembrando que o Gruntfile é um arquivo javascript padrão, logo você pode programar lá dentro conforme as suas necessidades, sem problema algum.

Caso alguém queira dar uma olhada no “projeto” exemplo completo, é só conferir aqui: https://github.com/ProgramAi/tutorial-grunt.

Vale mencionar que o Grunt não é a única ferramenta de automação de build disponível no mercado, uma outra ferramenta que tem muitos adeptos é o Gulp. Tem diversos artigos na Web comparando os dois. Vale a pena dar uma avaliada também.

Então, por enquanto é isso. Dúvidas, sugestões, opiniões, por favor mandem nos comentários.

Não esqueçam de acompanhar o blog nas redes sociais.

Valeu e até a próxima.

Brax!