Como aproveitar as máquinas: ser produtivo com os executores de tarefas
Publicados: 2022-03-10Os executores de tarefas são os heróis (ou vilões, dependendo do seu ponto de vista) que trabalham silenciosamente por trás da maioria dos aplicativos móveis e da web. Os executores de tarefas fornecem valor por meio da automação de várias tarefas de desenvolvimento, como concatenar arquivos, ativar servidores de desenvolvimento e compilar código. Neste artigo, abordaremos os scripts Grunt, Gulp, Webpack e npm. Também forneceremos alguns exemplos de cada um para você começar. Perto do final, apresentarei algumas vitórias fáceis e dicas para integrar as ideias deste post em seu aplicativo.
Há um sentimento de que os executores de tarefas e os avanços do JavaScript em geral estão complicando demais o cenário do front-end. Concordo que passar o dia inteiro ajustando scripts de compilação nem sempre é o melhor uso do seu tempo, mas os executores de tarefas têm alguns benefícios quando usados corretamente e com moderação. Esse é o nosso objetivo neste artigo, cobrir rapidamente os conceitos básicos dos executores de tarefas mais populares e fornecer exemplos sólidos para impulsionar sua imaginação sobre como essas ferramentas podem se encaixar em seu fluxo de trabalho.
Leitura adicional no SmashingMag:
- Torne-se um usuário avançado de linha de comando com Oh-My-ZSH e Z
- Uma introdução ao PostCSS
- Levante-se e corra com o Grunt
- Construindo com gole
Uma nota sobre a linha de comando
Os executores de tarefas e as ferramentas de compilação são principalmente ferramentas de linha de comando. Ao longo deste artigo, assumirei um nível decente de experiência e competência em trabalhar com a linha de comando. Se você entende como usar comandos comuns como cd
, ls
, cp
e mv
, então você deve estar bem enquanto passamos pelos vários exemplos. Se você não se sentir confortável usando esses comandos, um ótimo post introdutório está disponível na Smashing Magazine. Vamos começar com o avô de todos eles: Grunt.
Grunhido
Grunt foi o primeiro executor de tarefas baseado em JavaScript popular. Eu tenho usado o Grunt de alguma forma desde 2012. A ideia básica por trás do Grunt é que você use um arquivo JavaScript especial, Gruntfile.js
, para configurar vários plugins para realizar tarefas. Possui um vasto ecossistema de plugins e é uma ferramenta muito madura e estável. Grunt tem um diretório web fantástico que indexa a maioria dos plugins (cerca de 5.500 atualmente). A genialidade simples do Grunt é sua combinação de JavaScript e a ideia de um arquivo de configuração comum (como um makefile), que permitiu que muitos outros desenvolvedores contribuíssem e usassem o Grunt em seus projetos. Isso também significa que o Grunt pode ser colocado sob o mesmo sistema de controle de versão que o resto do projeto.
Grunt é testado em batalha e estável. Na época em que escrevo, a versão 1.0.0 foi lançada, o que é uma grande conquista para a equipe do Grunt. Como o Grunt configura amplamente vários plugins para trabalharem juntos, ele pode ficar confuso (ou seja, confuso e confuso para modificar) muito rapidamente. No entanto, com um pouco de cuidado e organização (quebrando tarefas em arquivos lógicos!), você pode fazer maravilhas para qualquer projeto.
No caso raro de um plugin não estar disponível para realizar a tarefa que você precisa, o Grunt fornece documentação sobre como escrever seu próprio plugin. Tudo o que você precisa saber para criar seu próprio plugin é JavaScript e a API Grunt. Você quase nunca terá que criar seu próprio plugin, então vamos ver como usar o Grunt com um plugin bastante popular e útil!
Um exemplo

Vamos ver como o Grunt realmente funciona. Executar grunt
na linha de comando acionará o programa de linha de comando Grunt que procura Gruntfile.js
na raiz do diretório. O Gruntfile.js
contém a configuração que controla o que o Grunt fará. Nesse sentido, Gruntfile.js
pode ser visto como uma espécie de livro de receitas que o cozinheiro (ou seja, Grunt, o programa) segue; e, como qualquer bom livro de receitas, o Gruntfile.js
conterá muitas receitas (ou seja, tarefas).
Vamos testar o Grunt usando o plugin Grunticon para gerar ícones para um aplicativo web hipotético. Grunticon recebe um diretório de SVGs e cospe vários ativos:
- um arquivo CSS com os SVGs codificados em base 64 como imagens de fundo;
- um arquivo CSS com versões PNG dos SVGs codificados em base 64 como imagens de fundo;
- um arquivo CSS que faz referência a um arquivo PNG individual para cada ícone.
Os três arquivos diferentes representam os vários recursos de navegadores e dispositivos móveis. Dispositivos modernos receberão os SVGs de alta resolução como uma única solicitação (ou seja, um único arquivo CSS). Os navegadores que não lidam com SVGs, mas lidam com ativos codificados em base 64, obterão a folha de estilo PNG base 64. Finalmente, qualquer navegador que não consiga lidar com esses dois cenários obterá a folha de estilo “tradicional” que faz referência a PNGs. Tudo isso a partir de um único diretório de SVGs!
A configuração desta tarefa é assim:
module.exports = function(grunt) { grunt.config("grunticon", { icons: { files: [ { expand: true, cwd: 'grunticon/source', src: ["*.svg", ".png"], dest: 'dist/grunticon' } ], options: [ { colors: { "blue": "blue" } } ] } }); grunt.loadNpmTasks('grunt-grunticon'); };
Vamos percorrer as várias etapas aqui:
- Você deve ter o Grunt instalado globalmente.
- Crie o arquivo
Gruntfile.js
na raiz do projeto. É melhor também instalar o Grunt como uma dependência npm em seu arquivopackage.json
junto com o Grunticon vianpm i grunt grunt-grunticon --save-dev
. - Crie um diretório de SVGs e um diretório de destino (onde os ativos construídos irão).
- Coloque um pequeno script no
head
do seu HTML, que determinará quais ícones carregar.
Aqui está a aparência do seu diretório antes de você executar a tarefa Grunticon:
|-- Gruntfile.js |-- grunticon | `-- source | `-- logo.svg `-- package.json
|-- Gruntfile.js |-- grunticon | `-- source | `-- logo.svg `-- package.json
Depois que essas coisas estiverem instaladas e criadas, você poderá copiar o trecho de código acima para Gruntfile.js
. Você deve então ser capaz de executar o grunt grunticon
a partir da linha de comando e ver sua tarefa ser executada.
O trecho acima faz algumas coisas:
- adiciona um novo objeto de
config
ao Grunt na linha 32 chamadogrunticon
; - preenche as várias opções e parâmetros do Grunticon no objeto
icons
; - finalmente, puxa o plugin Grunticon via
loadNPMTasks
.
Aqui está a aparência do seu diretório pós-Grunticon:
|-- Gruntfile.js |-- dist | `-- grunticon | |-- grunticon.loader.js | |-- icons.data.png.css | |-- icons.data.svg.css | |-- icons.fallback.css | |-- png | | `-- logo.png | `-- preview.html |-- grunticon | `-- source | `-- logo.svg `-- package.json
|-- Gruntfile.js |-- dist | `-- grunticon | |-- grunticon.loader.js | |-- icons.data.png.css | |-- icons.data.svg.css | |-- icons.fallback.css | |-- png | | `-- logo.png | `-- preview.html |-- grunticon | `-- source | `-- logo.svg `-- package.json
Pronto, pronto! Em algumas linhas de configuração e algumas instalações de pacotes, automatizamos a geração de nossos ativos de ícones! Felizmente, isso começa a ilustrar o poder dos executores de tarefas: confiabilidade, eficiência e portabilidade.
Gulp: Blocos LEGO para o seu sistema de construção
O Gulp surgiu algum tempo depois do Grunt e aspirava a ser uma ferramenta de construção que não fosse toda configuração, mas código real. A ideia por trás do código sobre a configuração é que o código é muito mais expressivo e flexível do que a modificação de arquivos de configuração sem fim. O obstáculo com o Gulp é que ele requer mais conhecimento técnico do que o Grunt. Você precisará estar familiarizado com a API de streaming Node.js e se sentir à vontade para escrever JavaScript básico.
O uso de streams Node.js pelo Gulp é a principal razão pela qual ele é mais rápido que o Grunt. Usar streams significa que, em vez de usar o sistema de arquivos como o “banco de dados” para transformações de arquivos, o Gulp usa transformações na memória. Para obter mais informações sobre streams, confira a documentação da API de streams Node.js, juntamente com o manual de stream.
Um exemplo

Como na seção Grunt, vamos colocar o Gulp à prova com um exemplo direto: concatenar nossos módulos JavaScript em um único arquivo de aplicativo.
Executar o Gulp é o mesmo que executar o Grunt. O programa de linha de comando gulp
procurará o livro de receitas de receitas (ou seja, Gulpfile.js
) no diretório em que é executado.
Limitar o número de solicitações de cada página é considerado uma prática recomendada de desempenho da Web (especialmente em dispositivos móveis). No entanto, colaborar com outros desenvolvedores é muito mais fácil se a funcionalidade for dividida em vários arquivos. Digite os executores de tarefas. Podemos usar o Gulp para combinar os vários arquivos de JavaScript para nosso aplicativo para que os clientes móveis tenham que carregar um único arquivo, em vez de muitos.
O Gulp tem o mesmo ecossistema maciço de plugins que o Grunt. Então, para facilitar essa tarefa, vamos nos apoiar no plugin gulp-concat. Digamos que a estrutura do nosso projeto seja assim:
|-- dist | `-- app.js |-- gulpfile.js |-- package.json `-- src |-- bar.js `-- foo.js
Dois arquivos JavaScript estão em nosso diretório src
e queremos combiná-los em um arquivo, app.js
, em nosso diretório dist/
. Podemos usar a seguinte tarefa Gulp para fazer isso.
var gulp = require('gulp'); var concat = require('gulp-concat'); gulp.task('default', function() { return gulp.src('./src/*.js') .pipe(concat('app.js')) .pipe(gulp.dest('./dist/')); });
Os bits importantes estão no retorno de chamada gulp.task
. Lá, usamos a API gulp.src
para obter todos os arquivos que terminam com .js
em nosso diretório src
. A API gulp.src
retorna um fluxo desses arquivos, que podemos então passar (através da API pipe
) para o plugin gulp-concat. O plugin então concatena todos os arquivos no stream e os passa para a função gulp.dest
. A função gulp-dest
simplesmente grava a entrada que recebe no disco.
Você pode ver como o Gulp usa streams para nos dar “blocos de construção” ou “cadeias” para nossas tarefas. Um fluxo de trabalho típico do Gulp se parece com isso:
- Obtenha todos os arquivos de um determinado tipo.
- Passe esses arquivos para um plugin (concat!), ou faça alguma transformação.
- Passe esses arquivos transformados para outro bloco (no nosso caso, o bloco
dest
, que encerra nossa cadeia).
Como no exemplo do Grunt, simplesmente executar gulp
na raiz do diretório do nosso projeto acionará a tarefa default
definida no arquivo Gulpfile.js
. Essa tarefa concatena nossos arquivos e vamos continuar desenvolvendo nosso aplicativo ou site.
Webpack
A mais nova adição ao clube de execução de tarefas JavaScript é o Webpack. O Webpack se autodenomina um “empacotador de módulos”, o que significa que ele pode construir dinamicamente um pacote de código JavaScript a partir de vários arquivos separados usando padrões de módulos como o padrão CommonJS. O Webpack também possui plugins, que ele chama de loaders.

O Webpack ainda é bastante jovem e possui uma documentação bastante densa e confusa. Portanto, eu recomendaria o repositório Webpack de Pete Hunt como um ótimo ponto de partida antes de mergulhar na documentação oficial. Eu também não recomendaria o Webpack se você é novo em executores de tarefas ou não se sente proficiente em JavaScript. Deixando esses problemas de lado, ainda é uma ferramenta mais específica do que a amplitude geral do Grunt e do Gulp. Muitas pessoas usam o Webpack junto com o Grunt ou o Gulp por esse motivo, deixando o Webpack se destacar no agrupamento de módulos e deixando o Grunt ou o Gulp lidar com tarefas mais genéricas.
O Webpack finalmente nos permite escrever código no estilo Node.js para o navegador, uma grande vitória para a produtividade e fazer uma separação limpa de preocupações em nosso código por meio de módulos. Vamos usar o Webpack para obter o mesmo resultado que obtivemos com o exemplo do Gulp, combinando vários arquivos JavaScript em um arquivo de aplicativo.
Um exemplo

Webpack é frequentemente usado com Babel para transpilar código ES6 para ES5. A transpilação de código de ES6 para ES5 permite que os desenvolvedores usem o padrão ES6 emergente enquanto fornecem ES5 para navegadores ou ambientes que ainda não suportam totalmente o ES6. No entanto, neste exemplo, vamos nos concentrar na construção de um pacote simples de nossos dois arquivos do exemplo Gulp. Para começar, precisamos instalar o Webpack e criar um arquivo de configuração, webpack.config.js
. Veja como nosso arquivo se parece:
module.exports = { entry: "./src/foo.js", output: { filename: "app.js", path: "./dist" } };
Neste exemplo, estamos apontando o Webpack para nosso arquivo src/foo.js
para iniciar seu trabalho de percorrer nosso gráfico de dependência. Também atualizamos nosso arquivo foo.js
para ficar assim:
//foo.js var bar = require("./bar"); var foo = function() { console.log('foo'); bar(); }; module.exports = foo;
E atualizamos nosso arquivo bar.js
para ficar assim:
//bar.js var bar = function() { console.log('bar'); }; module.exports = bar;
Este é um exemplo muito básico do CommonJS. Você notará que esses arquivos agora “exportam” uma função. Essencialmente, CommonJS e Webpack nos permitem começar a organizar nosso código em módulos independentes que podem ser importados e exportados em todo o nosso aplicativo. O Webpack é inteligente o suficiente para seguir as palavras-chave de importação e exportação e agrupar tudo em um arquivo, dist/app.js
. Não precisamos mais manter uma tarefa de concatenação e simplesmente precisamos aderir a uma estrutura para nosso código. Muito melhor!
Extensão
O Webpack é semelhante ao Gulp no sentido de que “é apenas JavaScript”. Ele pode ser estendido para realizar outras tarefas do executor de tarefas por meio de seu sistema de carregador. Por exemplo, você pode usar css-loader e sass-loader para compilar Sass em CSS e até mesmo usar o Sass em seu JavaScript sobrecarregando o padrão require
CommonJS! No entanto, normalmente defendo o uso do Webpack apenas para construir módulos JavaScript e para o uso de outra abordagem mais geral para execução de tarefas (por exemplo, scripts Webpack e npm ou Webpack e Gulp para lidar com todo o resto).
Scripts npm
scripts npm são a última moda dos hipsters, e por boas razões. Como vimos com todas essas ferramentas, o número de dependências que elas podem introduzir em um projeto pode eventualmente sair do controle. O primeiro post que vi defendendo scripts npm como ponto de entrada para um processo de construção foi de James Halliday. Seu post resume perfeitamente o poder ignorado dos scripts npm (ênfase minha):
Existem algumas ferramentas sofisticadas para fazer automação de compilação em projetos JavaScript que eu nunca senti o apelo porque o comando npm run
menos conhecido foi perfeitamente adequado para tudo o que eu precisava fazer, mantendo uma pegada de configuração muito pequena .
Você pegou esse último pedaço no final? O principal apelo dos scripts npm é que eles têm uma “pegada de configuração muito pequena”. Esta é uma das principais razões pelas quais os scripts npm começaram a pegar (quase quatro anos depois, infelizmente). Com Grunt, Gulp e até Webpack, eventualmente começa-se a se afogar em plugins que envolvem binários e dobram o número de dependências em um projeto.
Keith Cirkel tem o tutorial sobre como usar o npm para substituir o Grunt ou o Gulp. Ele fornece o plano de como aproveitar totalmente o poder dos scripts npm e introduziu um plug-in essencial, o Parallel Shell (e vários outros semelhantes).
Um exemplo
Em nossa seção sobre o Grunt, pegamos o popular módulo Grunticon e criamos ícones SVG (com fallbacks PNG) em uma tarefa do Grunt. Este costumava ser o único ponto problemático com scripts npm para mim. Por um tempo, eu manteria o Grunt instalado para projetos apenas para usar o Grunticon. Eu literalmente “desembolsava” o Grunt na minha tarefa npm para alcançar o início do executor de tarefas (ou, como começamos a chamá-lo no trabalho, um turducken de ferramenta de construção). Felizmente, The Filament Group, o grupo fantástico por trás do Grunticon, lançou uma versão autônoma (ou seja, sem Grunt) de sua ferramenta, Grunticon-Lib. Então, vamos usá-lo para criar alguns ícones com scripts npm!
Este exemplo é um pouco mais avançado do que uma tarefa típica de script npm. Uma tarefa típica de script npm é uma chamada para uma ferramenta de linha de comando, com os sinalizadores ou arquivo de configuração apropriados. Aqui está uma tarefa mais típica que compila nosso Sass para CSS:
"sass": "node-sass src/scss/ -o dist/css",
Viu como é apenas uma linha com várias opções? Nenhum arquivo de tarefa necessário, nenhuma ferramenta de compilação para ativar - apenas npm run sass
a partir da linha de comando, e você é Sass agora é CSS. Um recurso muito bom dos scripts npm é como você pode encadear tarefas de script. Por exemplo, digamos que queremos executar alguma tarefa antes que nossa tarefa Sass seja executada. Criaríamos uma nova entrada de script como esta:
"presass": "echo 'before sass',
Isso mesmo: npm entende o prefixo pre-
. Ele também entende o prefixo post-
. Qualquer entrada de script com o mesmo nome de outra entrada de script com prefixo pre-
ou post-
será executada antes ou depois dessa entrada.
A conversão de nossos ícones exigirá um arquivo Node.js real. Não é muito sério, no entanto. Basta criar um diretório de tasks
e criar um novo arquivo chamado grunticon.js
ou icons.js
ou o que fizer sentido para aqueles que trabalham no projeto. Uma vez que o arquivo é criado, podemos escrever algum JavaScript para disparar nosso processo Grunticon.
Nota: Todos esses exemplos usam ES6, então usaremos babel-node para executar nossa tarefa. Você pode usar facilmente o ES5 e o Node.js, se for mais confortável.
import icons from "grunticon-lib"; import globby from "globby"; let files = globby.sync('src/icons/*'); let options = { colors: { "blue": "blue" } }; let icon = new icons(files, 'dist/icons', options); icon.process();
Vamos entrar no código e descobrir o que está acontecendo.
-
import
(ou seja, exigimos) duas bibliotecas,grunticon-lib
eglobby
. Globby é uma das minhas ferramentas favoritas e facilita muito o trabalho com arquivos e globs. Globby aprimora o Node.js Glob (selecione todos os arquivos JavaScript via./*.js
) com suporte a Promise. Neste caso, estamos usando para obter todos os arquivos no diretóriosrc/icons
. - Depois de fazer isso, definimos algumas opções em um objeto de
options
e chamamos Grunticon-Lib com três argumentos:- os arquivos de ícone,
- o destino,
- as opções. A biblioteca assume e mastiga esses ícones e eventualmente cria as versões SVGs e PNG no diretório que queremos.
- Estamos quase terminando. Lembre-se que isso está em um arquivo separado, e precisamos adicionar um “gancho” para chamar este arquivo do nosso script npm, assim:
"icons": "babel-node tasks/icons.js"
. - Agora podemos executar
npm run icons
, e nossos ícones serão criados todas as vezes.
Os scripts npm oferecem um nível semelhante de poder e flexibilidade como outros executores de tarefas, sem a dívida do plug-in.
Detalhamento dos Executores de Tarefas Cobertos Aqui
Ferramenta | Prós | Contras |
---|---|---|
Grunhido | Nenhum conhecimento real de programação necessário | O mais detalhado dos executores de tarefas abordados aqui |
Gole | Configure tarefas com JavaScript e fluxos reais | Requer conhecimento de JavaScript |
Adiciona código a um projeto (potencialmente mais bugs) | ||
Webpack | Melhor da classe em agrupamento de módulos | Mais difícil para tarefas mais genéricas (por exemplo, Sass to CSS) |
scripts npm | Interação direta com ferramentas de linha de comando. | Algumas tarefas não são possíveis sem um executor de tarefas. |
Algumas vitórias fáceis
Todos esses exemplos e executores de tarefas podem parecer esmagadores, então vamos detalhar. Primeiro, espero que você não tire deste artigo que qualquer executor de tarefas ou sistema de compilação que você esteja usando no momento precisa ser substituído instantaneamente por um mencionado aqui. A substituição de sistemas importantes como esse não deve ser feita sem muita consideração. Aqui está meu conselho para atualizar um sistema existente: Faça isso de forma incremental.
Scripts de wrapper!
Uma abordagem incremental é procurar escrever alguns scripts npm “wrapper” em torno de seus executores de tarefas existentes para fornecer um vocabulário comum para etapas de construção que estão fora do executor de tarefas real usado. Um script wrapper pode ser tão simples quanto isto:
{ "scripts": { "start": "gulp" } }
Muitos projetos utilizam os blocos de script npm start
e test
para ajudar os novos desenvolvedores a se acostumarem rapidamente. Um script wrapper introduz outra camada de abstração para sua cadeia de construção do executor de tarefas, mas acho que vale a pena padronizar em torno das primitivas npm (por exemplo test
). Os comandos npm têm uma longevidade melhor do que uma ferramenta individual.
Polvilhe em um pequeno Webpack
Se você ou sua equipe estão sentindo a dor de manter uma “ordem de pacote” frágil para seu JavaScript, ou você está procurando atualizar para o ES6, considere esta uma oportunidade de apresentar o Webpack ao seu sistema de execução de tarefas existente. O Webpack é ótimo porque você pode usar o quanto quiser e ainda assim obter valor dele. Comece apenas fazendo com que ele agrupe o código do seu aplicativo e, em seguida, adicione o babel-loader à mistura. O Webpack tem uma profundidade de recursos tão grande que será capaz de acomodar praticamente quaisquer adições ou novos recursos por algum tempo.
Use facilmente o PostCSS com scripts npm
PostCSS é uma ótima coleção de plugins que transformam e aprimoram o CSS depois de escrito e pré-processado. Em outras palavras, é um pós-processador. É bastante fácil aproveitar o PostCSS usando scripts npm. Digamos que temos um script Sass como em nosso exemplo anterior:
"sass": "node-sass src/scss/ -o dist/css",
Podemos usar as palavras-chave do lifecycle
de vida do script npm para adicionar um script para ser executado automaticamente após a tarefa Sass:
"postsass": "postcss --use autoprefixer -c postcss.config.json dist/css/*.css -d dist/css",
Este script será executado toda vez que o script Sass for executado. O pacote postcss-cli é ótimo, porque você pode especificar a configuração em um arquivo separado. Observe que neste exemplo, adicionamos outra entrada de script para realizar uma nova tarefa; este é um padrão comum ao usar scripts npm. Você pode criar um fluxo de trabalho que realize todas as várias tarefas de que seu aplicativo precisa.
Conclusão
Os executores de tarefas podem resolver problemas reais. Eu usei executores de tarefas para compilar diferentes compilações de um aplicativo JavaScript, dependendo se o destino era produção ou desenvolvimento local. Também usei executores de tarefas para compilar modelos de Handlebars, implantar um site para produção e adicionar automaticamente prefixos de fornecedores que são perdidos no meu Sass. Essas não são tarefas triviais, mas uma vez que estão embrulhadas em um gerenciador de tarefas, elas se tornam fáceis.
Os executores de tarefas estão em constante evolução e mudança. Eu tentei cobrir os mais usados no zeitgeist atual. No entanto, existem outros que nem mencionei, como Brócolis, Brunch e Harpa. Lembre-se de que estas são apenas ferramentas: use-as apenas se resolverem um problema específico, não porque todo mundo está usando. Boa tarefa em execução!