Dezembro 2015

Volume 30 – Número 13

Visual Studio – Ferramentas modernas de desenvolvimento para a Web: Grunt e Gulp

Por Adam Tuliper | Dezembro de 2015

Existem muitas ferramentas disponíveis para o desenvolvedor da Web moderno. Duas delas que são frequentemente encontradas nos projetos Web atuais são os executores de tarefas JavaScript Grunt e Gulp. Usar JavaScript para executar uma tarefa pode parecer um conceito estranho se você nunca fez isso antes, ou se você está acostumado com o desenvolvimento simples do Visual Studio Web, mas existem bons motivos para você experimentar esses executores. Com os executores de tarefas JavaScript, que trabalham fora do navegador e normalmente usam Node.js na linha de comando, é possível executar tarefas relacionadas com desenvolvimento front-end, como minificação, concatenar de vários arquivos, determinar dependências de script e inserir referências de script na ordem certa para páginas HTML, criar unidades de agentes de teste, processar scripts de compilação de front-end como TypeScript ou CoffeeScript e muito mais.

Qual escolher: Grunt ou Gulp?

Escolher um executor de tarefas é sobretudo uma preferência pessoal ou de um determinado projeto, a não ser que exista um plug-in que você deseja usar que suporte apenas um executor de tarefas específico. As principais diferenças são: o Grunt é acionado por ajustes de configuração JSON e cada tarefa do Grunt deve normalmente criar arquivos intermediários a fim de passar para outras tarefas, enquanto o Gulp é acionado por código JavaScript executável (ou seja, não só JSON) e pode transmitir resultados de uma tarefa para a outra sem precisar usar arquivos temporários. O Gulp é a novidade mais recente e, por isso, é normal vermos projetos mais novos usando este executor. No entanto, o Grunt conta com muitos defensores conhecidos, como o jQuery, que o usa para compilar o próprio jQuery. Tanto o Grunt quanto o Gulp trabalham por meio de plug-ins, que são módulos instalados para lidar com uma tarefa específica. Existe um vasto ecossistema de plug-ins disponível, e você encontrará com frequência pacotes de tarefas que suportam tanto o Grunt quanto o Gulp, por isso, a escolha de um ou de outro se baseia normalmente em uma questão de gosto pessoal.

Instalando e usando o Grunt

O instalador tanto para o Grunt como o Gulp é o Node Package Manager (npm), sobre o qual eu falei resumidamente em meu artigo de outubro (msdn.com/magazine/mt573714). O comando para instalar o Grunt conta na verdade com duas partes. A primeira é uma instalação única da interface de linha de comando do Grunt. A segunda instala o Grunt em sua pasta de projeto. Esta instalação de duas partes permite que você use várias versões do Grunt em seu sistema e use a interface de linha de comando do Grunt a partir de qualquer caminho:

#only do this once to globally install the grunt command line runner
npm install –g grunt-cli
#run within your project folder one time to create package.json
#this file will track your installed npm dependencies
#like grunt and the grunt plug-ins, similar to bower.json (see the last article)
npm init
#install grunt as a dev dependency into your project (again see last article)
#we will still need a gruntfile.js though to configure grunt
npm install grunt –save-dev

Configuração do Grunt

O arquivo de configuração do Grunt é simplesmente um arquivo JavaScript com uma função de encapsulamento que contém a configuração, o carregamento do plug-in e a definição da tarefa, conforme mostrado na Figura 1.

Figura 1 - Arquivo de configuração do Grunt

module.exports = function (grunt) {
  // Where does uglify come from? Installing it via:
  // npm install grunt-contrib-uglify --save-dev
  grunt.initConfig({
    uglify: {
      my_target: {
        files: {
          'dest/output.min.js': '*.js'
        }
      }
    }
  });
  // Warning: make sure you load your tasks from the
  // packages you've installed!
  grunt.loadNpmTasks('grunt-contrib-uglify');
  // When running Grunt at cmd line with no params,
  // you need a default task registered, so use uglify
  grunt.registerTask('default', ['uglify']);
  // You can include custom code right inside your own task,
  // as well as use the above plug-ins
  grunt.registerTask('customtask', function () {
    console.log("\r\nRunning a custom task");
  });
};

É possível executar tarefas na Figura 1 na linha de comando com uma simples chamada:

#no params means choose the 'default' task (which happens to be uglify)
grunt
#or you can specify a particular task by name
grunt customtask

Após fazer isso, você encontrará o resultado minificado (uglify) e concatenado em wwwroot/output-min.js. Se você usou minificação e agrupamento ASP.NET, perceberá que este processo é diferente, pois ele não está vinculado com a execução ou a compilação de seu aplicativo, além de contar com muitas outras opções de escolha para tarefas como uglify. Pessoalmente, acho esse fluxo de trabalho mais simples e fácil de entender.

É possível encadear tarefas junto com o Grunt para que elas sejam dependentes umas das outras. Essas tarefas serão executadas de forma síncrona, para que uma só comece quando outra termina.

#Specify uglify must run first and then concat. Because grunt works off
#temp files, many tasks will need to wait until a prior one is done
grunt.registerTask('default', ['uglify', 'concat']);

Instalando e usando o Gulp

A instalação do Gulp é parecida com a do Grunt. Eu vou entrar mais em detalhe com o Gulp, mas é possível fazer coisas semelhantes com os dois; só não quero ser muito repetitivo. O Gulp tem uma instalação global, que pode ser usada a partir de qualquer caminho em seu sistema, e uma instalação local situada em sua pasta de projeto, com uma versão para o seu projeto específico. A instalação global do Gulp passa o controle para a versão instalada em seu projeto local, se a encontrar, respeitando assim a versão Gulp do projeto:

#Only do this once to globally install gulp
npm install –g gulp
#Run within your project folder one time to create package.json
#this file will track your installed npm dependencies
#like gulp and the gulp plug-ins
npm init
#Install gulp as a dev dependency into your project
#we will still need a gulpfile.js to configure gulp
npm install gulp --save-dev

Configuração e API do Gulp

A configuração do Gulp é bastante diferente da do Grunt. O arquivo de configuração gulpfile.js, que normalmente tem a estrutura mostrada na Figura 2, contém os “requisitos” para o carregamento dos plug-ins e para definir as tarefas. Observe que eu não estou usando os ajustes de configuração JSON aqui; em vez disso, as tarefas são acionadas por código.

Figura 2 - Arquivo de configuração do Gulp

// All the 'requires' to load your
// various plug-ins and gulp itself
var gulp = require('gulp');
var concat = require('gulp-concat');
// A custom task, run via: gulp customtask
gulp.task('customtask', function(){
  // Some custom task
});
// Define a default task, run simply via: gulp
gulp.task('default', function () {
  gulp.src('./lib/scripts/*.js')
    .pipe(concat('all-scripts.js'))
    .pipe(gulp.dest('./wwwroot/scripts'));
});

O Gulp trabalha com várias APIs e conceitos fundamentais: src, dest, pipe, task e globs. A API gulp.src diz ao Gulp quais arquivos devem ser abertos para serem usados. Normalmente, esses arquivos são enviados por pipe a uma outra função, em vez de gerarem arquivos temporários. Esta é a principal diferença do Grunt. O exemplo a seguir mostra alguns exemplos básicos do gulp.src sem usar pipe nos resultados, assunto que iremos abordar mais adiante. Esta chamada à API obtém o que chamamos de glob como parâmetro. Um glob é basicamente um padrão que pode ser inserido (como uma espécie de expressão regular), por exemplo, para especificar um caminho a um ou mais arquivos (saiba mais sobre globs em github.com/isaacs/node-glob):

#Tell gulp about some files to work with
gulp.src('./input1.js');
#This will represent every html file in the wwwroot folder
gulp.src('./wwwroot/*.html')
#You can specify multiple expressions
gulp.src(['./app/**/*.js', './app/*.css']

A API dest (destino), como você pode imaginar, especifica um destino e também usa um glob. Como os globs são tão flexíveis para definir caminhos, é possível transferir arquivos individuais ou transferir para uma pasta:

#Tell dest you'll be using a file as your output
gulp.dest ('./myfile.js');
#Or maybe you'll write out results to a folder
gulp.dest ('./wwwroot');

As tarefas no Gulp consistem basicamente no código que você escreve para fazer algo. O formato das tarefas é bastante simples, mas elas podem ser usadas de várias maneiras. A maneira mais simples é ter uma tarefa padrão e uma ou mais tarefas:

gulp.task('customtask', function(){
  // Some custom task to ex. read files, add headers, concat
});
gulp.task('default', function () {
  // Some default task that runs when you call gulp with no params
});

As tarefas podem ser executadas em paralelo ou podem ser dependentes umas das outras. Se você não se importar com a ordem, é possível apenas encadeá-las umas com as outras, da seguinte forma:

gulp.task('default', ['concatjs', 'compileLess'], function(){});

Este exemplo define a tarefa padrão, que não faz nada mas executa tarefas separadas para concatenar arquivos JavaScript e compilar arquivos LESS (considerando que o código estava nas tarefas nomeadas aqui). Se os requisitos ditarem a conclusão de uma tarefa antes de a outra ser executada, é necessário tornar uma tarefa dependente de outra e, em seguida, executar várias tarefas. No código seguinte, a tarefa padrão espera que a concatenação seja concluída primeiro, que por sua vez espera pela conclusão da tarefa uglify:

gulp.task('default', ['concat']);
gulp.task('concat', ['uglify'], function(){
  // Concat
});
gulp.task('uglify', function(){
  // Uglify
});

A API pipe é usada para redirecionar resultados de uma função para outra usando a API de transmissão Node.js. O fluxo de trabalho normalmente é: ler src, redirecionar para uma tarefa, redirecionar os resultados para um destino. Este exemplo de gulpfile.js completo lê todos os arquivos JavaScript em /scripts, concatena-os em um único arquivo e grava a saída para outra pasta:

// Define plug-ins – must first run: npm install gulp-concat --save-dev
var gulp = require('gulp');
var concat = require('gulp-concat');
gulp.task('default', function () {
  #Get all .js files in /scripts, pipe to concatenate, and write to folder
  gulp.src('./lib/scripts/*.js')
    .pipe(concat('all-scripts.js'))
    .pipe(gulp.dest('./wwwroot/scripts'));
}

Aqui está um exemplo real e prático. Você desejará frequentemente concatenar arquivos e/ou adicionar cabeçalhos informativos aos seus arquivos de código-fonte. Isto pode ser feito com facilidade em apenas alguns passos adicionando dois arquivos à raiz de seu site (você também poderá realizar toda esta tarefa em código executar em etapas – consulte os documentos de cabeçalho gulp). Primeiro, crie um arquivo chamado copyright.txt que contém os cabeçalhos a serem adicionados, da seguinte forma:

/*
MyWebSite Version <%= version %>
https://twitter.com/adamtuliper
Copyright 2015, licensing, etc
*/

Em seguida, crie um arquivo chamado version.txt contendo o número de versão atual (também existem plug-ins para incrementar os números de versões como o gulp-bump e o grunt-bump):

1.0.0

Agora, instale os plug-ins gulp-header e gulp-concat na raiz de seu projeto:

npm install gulp-concat gulp-header --save-dev

Em alternativa, você pode adicioná-los manualmente ao arquivo package.json e deixar que o Visual Studio restaure o pacote para você.

Por fim, você só precisa que o gulpfile.js diga ao Gulp o que fazer, como mostrado na Figura 3. Se você não quiser concatenar todos os scripts juntos e, em vez disso, quiser apenas adicionar cabeçalhos a cada um dos arquivos, basta colocar um comentário na linha pipe(concat). Não é simples?

Figura 3 - Gulpfile.js

var gulp = require('gulp');
var fs = require('fs');
var concat = require("gulp-concat");
var header = require("gulp-header");
// Read *.js, concat to one file, write headers, output to /processed
gulp.task('concat-header', function () {
  var appVersion = fs.readFileSync('version.txt');
  var copyright =fs.readFileSync('copyright.txt');
  gulp.src('./scripts/*.js')
  .pipe(concat('all-scripts.js'))
  .pipe(header(copyright, {version: appVersion}))
  .pipe(gulp.dest('./scripts/processed'));
});

Em seguida, basta executar a tarefa através do seguinte comando e, pronto, você concatenou todos os arquivos .js, adicionou um cabeçalho padrão e escreveu a saída na pasta ./scripts/processed:

gulp concat-header

O Task Runner Explorer

O Visual Studio fornece suporte para o Gulp e o Grunt através do Task Runner Explorer, que está incluído no Visual Studio 2015 e está disponível como uma Extensão do Visual Studio. Você pode encontrá-lo em Exibir | Outras Janelas | Task Runner Explorer. O Task Runner Explorer é bem bacana, pois ele detecta se você tem um gulpfile.js ou gruntfile.js no projeto, analisa as tarefas e fornece uma Interface do Usuário para executar as tarefas que ele encontrar, como mostrado na Figura 4. Além disso, você pode definir que as tarefas sejam executadas quando ocorrerem ações predefinidas em seu projeto, assunto sobre o qual eu falarei a seguir, pois o ASP.NET 5 usa esta funcionalidade em seus modelos padrão. Basta clicar com o botão direito do mouse para executá-la ou vinculá-la a uma ação específica, por exemplo, para executar uma tarefa na Abertura de um Projeto.

O gerenciador do executor de tarefas mostrando as tarefas Grunt e Gulp com opções
Figura 4 - O gerenciador do executor de tarefas mostrando as tarefas Grunt e Gulp com opções

Como mostra a Figura 5, o Grunt fornece opções quando você destaca uma tarefa do Grunt que não consegue ver no Gulp, especialmente as opções Force (para ignorar avisos) e Verbose (F e V no lado superior esquerdo). Estes são simplesmente parâmetros que passaram para a linha de comando do Grunt. O lado bom do Task Runner Explorer é que ele mostra os comandos que ele passa para as linhas de comando do Grunt e do Gulp (na janela de saída da tarefa), assim não há mistério sobre o que está acontecendo nos bastidores.

Opções adicionais do Grunt e a Linha de comando
Figura 5 - Opções adicionais do Grunt e a Linha de comando

Gulp e ASP.NET 5

Os modelos do ASP.NET 5 incluídos no Visual Studio 2015 usam o Gulp, que é instalado na pasta node_components de seu projeto, deixando tudo pronto para você usá-lo em seu projeto. É claro que você ainda pode usar o Grunt em um projeto ASP.NET 5; só não se esqueça de instalá-lo em seu projeto através de npm ou adicionando-o ao packages.json em devDependencies e deixando a funcionalidade de restauração automática do pacote no Visual Studio fazer o seu trabalho. Gostaria de enfatizar o seguinte: Você pode fazer tudo isso através da linha de comando ou dentro do Visual Studio, como preferir.

A partir desta gravação, os modelos ASP.NET 5 atuais incluem duas tarefas que minificam e concatenam os arquivos .css e .js. Nas versões anteriores do ASP.NET, essas tarefas foram tratadas em código compilado durante o tempo de execução, o que em condições ideais não é o local ou o momento em que estes tipos de tarefas devem ser realizados. Como pode ser visto na Figura 6, as tarefas chamadas clean e min chamam seus métodos css e js para minificar esses arquivos ou para limpar arquivos minificados anteriormente.

Tarefas prontas nos modelos do ASP.NET 5 Preview
Figura 6 - Tarefas prontas nos modelos do ASP.NET 5 Preview

A linha Gruntfile.js a seguir mostra outro exemplo de execução de múltiplas tarefas ao mesmo tempo:

gulp.task("clean", ["clean:js", "clean:css"]);

Você tem a opção de vincular as tarefas do Grunt e do Gulp a quatro ações diferentes no Visual Studio. Com o MSBuild, era comum definir uma linha de comando de pré-compilação ou pós-compilação para executar várias tarefas. Com o Task Runner Explorer, você pode definir eventos de projeto aberto antes da compilação, após a compilação e simples para executar essas tarefas. Esta ação apenas adiciona comentários aos arquivos gulpfile.js ou gruntfile.js sem afetar a execução, mas que são procurados pelo Task Runner Explorer. Para ver a vinculação “simples” no ASP.NET 5 gulpfile.js, dê uma olhada nesta linha na parte superior do arquivo:

// <binding Clean='clean' />

Basta isso para vincular o evento.

Conclusão

Tanto o Grunt quanto o Gulp são ótimos complementos para seu arsenal Web. Ambos contam com muito suporte e um vasto ecossistema de plug-ins disponíveis. Todos os projetos Web podem se beneficiar de algo que eles oferecem. Para obter mais informações, certifique-se de verificar o seguinte:


Adam Tuliper é um evangelista técnico sênior da Microsoft na ensolarada California. Ele é desenvolvedor Web, de jogos, autor do Pluralsight e apaixonado por tecnologia. Encontre-o no Twitter: @AdamTuliper ou em seu blog Adam’s Garage no seguinte link bit.ly/1NSAYxK.

Agradecemos ao seguinte especialista técnico da Microsoft pela revisão deste artigo: Michael Palermo