No meu primeiro contato com o Grunt, ele não me convenceu. Qual era a
necessidade de um task runner se eu já tinha o Makefile
? O mesmo valia
para o build de estáticos... Frameworks como o Django já possuíam um
pipeline de concatenação e minificação, não sendo necessário que um processo
externo interferisse em algo que (até então) funcionava muito bem.
Foi ao trabalhar com Single Page Applications que o Grunt me conquistou. Coisas que iam da otimização de imagens a deploy para ambientes passaram a ser responsabilidade da ferramenta, e a partir desse momento eu a carreguei para todo projeto que participei.
Mas o Grunt não é "bala de prata". Se para determinados problemas ele funciona
muito bem, para outros ele representa um "peso" questionável na sua stack. Aumentando
tempo de desenvolvimento (alguém aí já sofreu com o grunt-contrib-watch
?),
build e setup do seu ambiente.
É baseado nesse contexto que faço coro com alguns developers espalhados por aí: Talvez o seu projeto não precise do Grunt.
Nada!
Grunt, Gulp, Brocolli, Brunch, etc. são ferramentas super bacanas que cumprem com louvor o seu objetivo. Só que assim como o jQuery, para determinados casos elas podem ser "too much".
Assim como há uma frente defendendo o uso de microlibs ao invés de fat frameworks, há uma frente defendendo o uso do NPM como ferramenta de build. E por mais que possa parecer "mimimi", alguns argumentos fazem certo sentido. Como por exemplo, o do Keith Cirkel no "Why we should stop using Grunt & Gulp":
None of these build tools work without plugins. Just found an awesome new tool which will revolutionise your build process? Great! Now just wait for someone to write a wrapper for Grunt/Gulp/Brocolli, or write it yourself. Rather than just learning the command line for the tool you want to use, you now have to learn its programatic API, plus the API for your build tool.
Flamewars e "discursos de ódio" à parte, a pergunta que fica é: Será que é tão difícil assim montar um pipeline de build sem o uso de Grunt e Gulp?
Ter uma ferramenta a menos na stack do projeto pode tornar-ser um diferencial ao reduzir atrito e curva de aprendizado. Mas tirar o Grunt da jogada não significa necessariamente ter menos complexidade no seu projeto... Apenas significa fazer a mesma coisa com uma dependência a menos.
O Livereload é uma biblioteca escrita em Node que levanta um servidor que monitora alterações no seu projeto e promove um refresh no navegador web.
Podemos executá-lo através da linha de comando:
$ livereload meuprojeto/static/css
É possível usar o package.json
como um "centralizador" de operações, assim
como fazemos com o nosso Makefile
:
$ npm run livereload
Para tanto, no arquivo package.json
, precisamos adicionar a chave scripts
com as instruções de execução para o comando livereload
:
// package.json
...
"scripts": {
"livereload": "livereload meuprojeto/static/css"
}
...
Talvez haja a necessidade da execução de um comando mais complexo. Para isso, podemos utilizar um script Node como ajuda:
// build/livereload.js
var livereload = require("livereload");
var PATH_CSS = "meuprojeto/build/static/css";
var PATH_JS = "meuprojeto/build/static/js";
var server = livereload.createServer();
server.watch([PATH_CSS, PATH_JS]);
E o nosso arquivo de configuração ficaria assim:
// package.json
...
"scripts": {
"livereload": "node buid/livereload"
}
...
Vale lembrar que o npm
já possui alguns comandos padrões, que não necessitam
da instrução run
. Por exemplo, temos a execução de testes através do comando npm test
.
O exemplo acima é simples e questionável. Vamos partir de uma necessidade mais palpável e complexa: Quero compilar componentes escritos em React e ES6.
Para ilustrar, usaremos as bibliotecas React, Babelify e UglifyJS:
$ npm install react --save
$ npm install babelify babel-preset-react babel-preset-es2015 uglify-js --save-dev
Podemos criar dois comandos diferentes na nossa chave scripts
, um para
transpiling de Javascript e outro para minificação:
// package.json
"compile-js": "browserify meuprojeto/js/script.js -o meuprojeto/build/static/js/bundle.js -t [ babelify --presets [ es2015 react ] ]",
"minify-js": "uglifyjs meuprojeto/build/static/js/bundle.js -o meuprojeto/build/static/js/bundle.min.js"
Grande demais? É possível isolar esses comandos em scripts, como no exemplo do livereload:
// package.json
"compile-js": "node build/compile-js",
"minify-js": "node build/minify-js"
Para tornar mais simples a execução, vamos criar uma task genérica de build:
// package.json
"compile-js": "node build/compile-js",
"minify-js": "node build/minify-js",
"build": "npm run compile-js && npm run minify-js"
Com uma ajudinha da lib Watch, podemos incrementar ainda mais o nosso processo:
// package.json
...
"scripts": {
"compile-js": "node build/compile-js",
"minify-js": "node build/minify-js",
"build": "npm run compile-js && npm run minify-js",
"watch": "watch 'npm run build' meuprojeto/static/js"
}
...
Pronto! Agora temos os comandos npm run build
e npm run watch
que nos ajudarão na demanda
de "transpilar" e minificar componentes escritos em React.
Não foi tããããão difícil assim... Certo? Bastou abrir a documentação de cada ferramenta e perder alguns minutinhos lendo.
"Mas você teve que escrever mais linhas que escreveria utilizando um plugin do Grunt."
Possivelmente. Bem como é possível que eu nunca mais vá mexer nessas linhas escritas, uma vez que o processo já esteja montado e operacional.
O mérito dos exemplos acima não está necessariamente na utilização do npm
como ferramenta de build, mas sim nos incríveis pacotes que a comunidade
Javascript tem construído para agilizar a construção de aplicações.
Ferramentas como o Grunt, em seu tempo, fizeram uma revolução no que tange o desenvolvimento de aplicações web (e não há dúvida que ainda o fazem). Mas com o advento do webpack, Browserify e PostCSS, o seu uso passou de essencial para opcional.
Até a próxima.