O que é Gulp?
Para que serve o Gulp?
Como usar Gulp?
Vou tentar ajudar a explicar porque usar Gulp e quais são as principais vantagens do Gulp em relação aos seus similares.
Muitos de nós têm que lidar com projetos baseados na web que são usados na produção, que fornecem vários serviços ao público. Ao lidar com esses projetos, é importante poder construir e implantar nosso código rapidamente. Fazer algo rapidamente pode levar a erros, especialmente se um processo repetitivo, portanto, é uma boa prática automatizar esse processo o máximo possível.
Neste post, estaremos olhando para uma ferramenta que pode ser uma parte do que nos permitirá alcançar tal automação. Esta ferramenta é um pacote npm chamado Gulp.js.
Para se familiarizar com a terminologia básica do Gulp.js usada neste post, consulte “Uma Introdução à Automação do JavaScript com Gulp” que foi publicado anteriormente no blog por Antonios Minas, um dos nossos colaboradores da Toptal. Assumiremos a familiaridade básica com o ambiente npm, já que ele é usado extensivamente por todo este post para instalar pacotes.
Servindo arquivos Front-End
Antes de continuar, vamos dar alguns passos para obter uma visão geral do problema que o Gulp.js pode resolver para nós. Muitos projetos baseados na Web apresentam arquivos JavaScript front-end que são exibidos ao cliente para fornecer várias funcionalidades à página da web. Normalmente, há também um conjunto de folhas de estilo CSS que também são exibidas ao cliente. Às vezes, quando observamos o código-fonte de um site ou aplicativo da web, podemos ver um código como este:
<link href="css/main.css" rel="stylesheet">
<link href="css/custom.css" rel="stylesheet">
<script src="js/jquery.min.js"></script>
<script src="js/site.js"></script>
<script src="js/module1.js"></script>
<script src="js/module2.js"></script>
Existem alguns problemas com este código. Ele tem referências a duas folhas de estilo CSS separadas e quatro arquivos JavaScript separados. Isso significa que o servidor precisa fazer um total de seis solicitações para o servidor, e cada solicitação deve carregar separadamente um recurso antes que a página esteja pronta. Isso é um problema menor com o HTTP / 2, pois o HTTP / 2 introduz paralelismo e compactação de cabeçalho, mas ainda é um problema. Ele aumenta o volume total de tráfego necessário para carregar essa página e reduz a qualidade da experiência do usuário, pois leva mais tempo para carregar os arquivos. No caso do HTTP 1.1, ele também afeta a rede e reduz o número de canais de solicitação disponíveis. Teria sido muito melhor combinar os arquivos CSS e JavaScript em um único pacote para cada um. Dessa forma, haveria apenas um total de dois pedidos. Também seria bom servir versões “minificadas” desses arquivos, que geralmente são muito menores que os originais. Nosso aplicativo da Web também pode quebrar se qualquer um dos ativos estiver armazenado em cache e o cliente receber uma versão desatualizada.
Uma abordagem primitiva para resolver alguns desses problemas é combinar manualmente cada tipo de ativo em um pacote usando um editor de texto e, em seguida, executar o resultado por meio de um serviço minificador, como http://jscompress.com/. Isso prova ser muito tedioso de fazer continuamente durante o processo de desenvolvimento. Uma pequena, mas questionável, melhoria seria hospedar nosso próprio servidor minifier, usando um dos pacotes disponíveis no GitHub. Então poderíamos fazer coisas semelhantes às seguintes:
<script src="min/f=js/site.js,js/module1.js"></script>
Isso serviria arquivos minificados para o nosso cliente, mas não resolveria o problema de armazenamento em cache. Isso também causaria carga adicional no servidor, já que nosso servidor teria essencialmente que concatenar e minificar todos os arquivos de origem repetidamente em todas as solicitações.
Automatizando com o Gulp.js
Certamente podemos fazer melhor do que qualquer uma dessas duas abordagens. O que realmente queremos é automatizar o empacotamento e incluí-lo na fase de construção do nosso projeto. Queremos acabar com pacotes de recursos pré-criados que já estão reduzidos e prontos para veiculação. Também queremos forçar o cliente a receber as versões mais atualizadas de nossos recursos agrupados em todas as solicitações, mas ainda queremos aproveitar o armazenamento em cache, se possível. Felizmente para nós, o Gulp.js pode lidar com isso. No restante do artigo, estaremos desenvolvendo uma solução que aproveitará o poder do Gulp.js para concatenar e minimizar os arquivos. Nós também estaremos usando um plugin para quebrar o cache quando houver atualizações.
Nós estaremos criando o seguinte diretório e estrutura de arquivos em nosso exemplo:
public/
|- build/
|- js/
|- bundle-{hash}.js
|- css/
|- stylesheet-{hash}.css
assets/
|- js/
|- vendor/
|- jquery.js
|- site.js
|- module1.js
|- module2.js
|- css/
|- main.css
|- custom.css
gulpfile.js
package.json
O npm faz o gerenciamento de pacotes no Node.js projetar uma felicidade. O Gulp oferece enorme flexibilidade, aproveitando a abordagem simples de empacotamento do npm para fornecer plugins modulares e poderosos.
O arquivo gulpfile.js é onde definiremos as tarefas que o Gulp executará para nós. O package.json é usado pelo npm para definir o pacote do nosso aplicativo e rastrear as dependências que iremos instalar. O diretório público “public” é o que deve ser configurado para produção na web. O diretório de recursos “assets” é onde nós armazenaremos nossos arquivos de origem. Para usar o Gulp no projeto, precisaremos instalá-lo via npm e salvá-lo como uma dependência do desenvolvedor para o projeto. Nós também queremos começar com o plugin de concat para Gulp, que nos permitirá concatenar vários arquivos em um.
Para instalar esses dois itens, vamos executar o seguinte comando:
npm install --save-dev gulp gulp-concat
Em seguida, vamos querer começar a escrever o conteúdo do gulpfile.js.
var gulp = require('gulp');
var concat = require('gulp-concat');
gulp.task('pack-js', function () {
return gulp.src(['assets/js/vendor/*.js', 'assets/js/main.js', 'assets/js/module*.js'])
.pipe(concat('bundle.js'))
.pipe(gulp.dest('public/build/js'));
});
gulp.task('pack-css', function () {
return gulp.src(['assets/css/main.css', 'assets/css/custom.css'])
.pipe(concat('stylesheet.css'))
.pipe(gulp.dest('public/build/css'));
});
gulp.task('default', ['pack-js', 'pack-css']);
Aqui, estamos carregando a biblioteca gulp e seu plugin de concat. Nós então definimos três tarefas.
A primeira tarefa (pack-js) define um procedimento para compactar vários arquivos de origem JavaScript em um único pacote. Listamos os arquivos de origem, que serão globbed, read e concatenated na ordem especificada. Nós inserimos isso no plug-in de concat para obter um arquivo final chamado bundle.js. Finalmente, dizemos ao gulp para gravar o arquivo em public/build/js.
A segunda tarefa (pack-css) faz o mesmo que acima, mas para as folhas de estilo CSS. Ele diz ao Gulp para armazenar a saída concatenada como stylesheet.css em public/build/css.
A terceira tarefa padrão (default) é a que Gulp executa quando a invocamos sem argumentos. No segundo parâmetro, passamos a lista de outras tarefas para executar quando a tarefa padrão é executada.
Vamos colar esse código no gulpfile.js usando qualquer editor de código-fonte que normalmente usamos e, em seguida, salvar o arquivo na raiz do aplicativo.
Em seguida, vamos abrir a linha de comando e executar:
gulp
Se olharmos para os nossos arquivos depois de executar este comando, encontraremos dois novos arquivos: public/build/js/bundle.js e public/build/css/stylesheet.css. São concatenações de nossos arquivos de origem, o que resolve parte do problema original. No entanto, eles não são reduzidos e ainda não há cache bloqueado. Vamos adicionar a minimação automatizada.
Otimizando os Ativos Criados
Vamos precisar de dois novos plugins. Para adicioná-los, vamos executar o seguinte comando:
npm install --save-dev gulp-clean-css gulp-minify
O primeiro plugin é para diminuir o CSS, e o segundo é para diminuir o JavaScript. O primeiro usa o pacote clean-css e o segundo usa o pacote UglifyJS2. Vamos carregar estes dois pacotes no nosso gulpfile.js primeiro:
var minify = require('gulp-minify');
var cleanCss = require('gulp-clean-css');
Nós precisaremos usá-los em nossas tarefas antes de gravarmos a saída no disco:
.pipe(minify())
.pipe(cleanCss())
O gulpfile.js agora deve ficar assim:
var gulp = require('gulp');
var concat = require('gulp-concat');
var minify = require('gulp-minify');
var cleanCss = require('gulp-clean-css');
gulp.task('pack-js', function () {
return gulp.src(['assets/js/vendor/*.js', 'assets/js/main.js', 'assets/js/module*.js'])
.pipe(concat('bundle.js'))
.pipe(minify())
.pipe(gulp.dest('public/build/js'));
});
gulp.task('pack-css', function () {
return gulp.src(['assets/css/main.css', 'assets/css/custom.css'])
.pipe(concat('stylesheet.css'))
.pipe(cleanCss())
.pipe(gulp.dest('public/build/css'));
});
gulp.task('default', ['pack-js', 'pack-css']);
Vamos correr de novo. Veremos que o arquivo stylesheet.css é salvo no formato minificado e o arquivo bundle.js ainda é salvo como está. Notaremos que agora também temos o pacote bundle-min.js, que é reduzido. Queremos apenas o arquivo minificado e queremos salvá-lo como bundle.js, por isso modificaremos nosso código com parâmetros adicionais:
.pipe(minify({
ext:{
min:'.js'
},
noSource: true
}))
De acordo com a documentação do plug-in gulp-minify (https://www.npmjs.com/package/gulp-minify), isso definirá o nome desejado para a versão reduzida e informará ao plug-in para não criar a versão que contém a origem original. Se excluirmos o conteúdo do diretório de compilação e executarmos o gulp novamente na linha de comando, acabaremos com apenas dois arquivos compactados. Acabamos de implementar a fase de minificação do nosso processo de construção.
Aplicando de cache
Em seguida, queremos adicionar o bloqueio de cache, e precisaremos instalar um plugin para isso:
npm install --save-dev gulp-rev
E exija em nosso arquivo gulp:
var rev = require('gulp-rev');
Usando o plugin é um pouco complicado. Temos que canalizar a saída minificada através do plugin primeiro. Então, temos que chamar o plugin novamente depois de gravarmos os resultados no disco. O plug-in renomeia os arquivos para que eles sejam marcados com um hash exclusivo e também cria um arquivo de manifesto. O arquivo de manifesto é um mapa que pode ser usado pelo nosso aplicativo para determinar os nomes de arquivos mais recentes aos quais devemos nos referir em nosso código HTML. Depois de modificar o arquivo gulp, ele deve ficar assim:
var gulp = require('gulp');
var concat = require('gulp-concat');
var minify = require('gulp-minify');
var cleanCss = require('gulp-clean-css');
var rev = require('gulp-rev');
gulp.task('pack-js', function () {
return gulp.src(['assets/js/vendor/*.js', 'assets/js/main.js', 'assets/js/module*.js'])
.pipe(concat('bundle.js'))
.pipe(minify({
ext:{
min:'.js'
},
noSource: true
}))
.pipe(rev())
.pipe(gulp.dest('public/build/js'))
.pipe(rev.manifest())
.pipe(gulp.dest('public/build'));
});
gulp.task('pack-css', function () {
return gulp.src(['assets/css/main.css', 'assets/css/custom.css'])
.pipe(concat('stylesheet.css'))
.pipe(cleanCss())
.pipe(rev())
.pipe(gulp.dest('public/build/css'))
.pipe(rev.manifest())
.pipe(gulp.dest('public/build'));
});
gulp.task('default', ['pack-js', 'pack-css']);
Com o devido bloqueio de cache, você pode enlouquecer com um longo tempo de expiração para seus arquivos JS e CSS e substituí-los de forma confiável por versões mais novas sempre que necessário.
Vamos excluir o conteúdo do nosso diretório de criação e executar o gulp novamente. Descobriremos que agora temos dois arquivos com tags hash afixados em cada um dos nomes de arquivos e um manifest.json salvo em public / build. Se abrirmos o arquivo de manifesto, veremos que ele tem apenas uma referência a um de nossos arquivos compactados e marcados. O que está acontecendo é que cada tarefa grava um arquivo de manifesto separado, e um deles acaba substituindo o outro. Nós precisaremos modificar as tarefas com parâmetros adicionais que lhes dirão para procurar pelo arquivo de manifesto existente, e para mesclar os novos dados a ele, se existirem. A sintaxe disso é um pouco complicada, então vamos ver como o código deve ser e depois passar por cima:
var gulp = require('gulp');
var concat = require('gulp-concat');
var minify = require('gulp-minify');
var cleanCss = require('gulp-clean-css');
var rev = require('gulp-rev');
gulp.task('pack-js', function () {
return gulp.src(['assets/js/vendor/*.js', 'assets/js/main.js', 'assets/js/module*.js'])
.pipe(concat('bundle.js'))
.pipe(minify({
ext:{
min:'.js'
},
noSource: true
}))
.pipe(rev())
.pipe(gulp.dest('public/build/js'))
.pipe(rev.manifest('public/build/rev-manifest.json', {
merge: true
}))
.pipe(gulp.dest(''));
});
gulp.task('pack-css', function () {
return gulp.src(['assets/css/main.css', 'assets/css/custom.css'])
.pipe(concat('stylesheet.css'))
.pipe(cleanCss())
.pipe(rev())
.pipe(gulp.dest('public/build/css'))
.pipe(rev.manifest('public/build/rev-manifest.json', {
merge: true
}))
.pipe(gulp.dest(''));
});
gulp.task('default', ['pack-js', 'pack-css']);
Estamos direcionando a saída para rev.manifest () primeiro. Isso cria arquivos marcados em vez dos arquivos que tínhamos antes. Estamos fornecendo o caminho desejado de nosso rev-manifest.json, e dizendo ao rev.manifest () para mesclar o arquivo existente, se ele existir. Então estamos dizendo para escrever o manifesto no diretório atual, que nesse momento será public / build. O problema do caminho é devido a um bug que é discutido em mais detalhes no GitHub.
Agora, temos uma minuta automatizada, arquivos marcados e um arquivo de manifesto. Tudo isso nos permitirá entregar os arquivos mais rapidamente ao usuário, além de eliminar o cache sempre que fizermos nossas modificações. Há apenas dois problemas restantes embora.
O primeiro problema é que, se fizermos quaisquer modificações em nossos arquivos de origem, obteremos novos arquivos marcados, mas os antigos permanecerão lá também. Precisamos de alguma maneira para excluir automaticamente arquivos antigos e minificados. Vamos resolver esse problema usando um plug-in que nos permitirá excluir arquivos:
npm install --save-dev del
Função PHP para usar o rev-manifest.json
[code]
<?php
function asset_path($filename) {
$manifest_path = ‘public/build/rev-manifest.json’;
if (file_exists($manifest_path)) {
$manifest = json_decode(file_get_contents($manifest_path), TRUE);
} else {
$manifest = [];
}
if (array_key_exists($filename, $manifest)) {
return $manifest[$filename];
}
return $filename;
}
?>
[/code]
E para finalizar:
[code]
<script src=”public/build/js/<?php echo asset_path(‘bundle.js’) ?>”></script>
<link href=”public/build/css/<?php echo asset_path(‘stylesheet.css’) ?>” rel=”stylesheet” media=”all” />
[/code]
Vamos exigir isso em nosso código e definir duas novas tarefas, uma para cada tipo de arquivo de origem:
var del = require('del');
gulp.task('clean-js', function () {
return del([
'public/build/js/*.js'
]);
});
gulp.task('clean-css', function () {
return del([
'public/build/css/*.css'
]);
});
Nós, então, nos certificaremos de que a nova tarefa termine a execução antes de nossas duas tarefas principais:
gulp.task('pack-js', ['clean-js'], function () {
gulp.task('pack-css', ['clean-css'], function () {
Se executarmos novamente o gole após essa modificação, teremos apenas os arquivos mais recentes.
O segundo problema é que não queremos continuar a engolir toda vez que fazemos uma mudança. Para resolver isso, precisaremos definir uma tarefa de observador:
gulp.task('watch', function() {
gulp.watch('assets/js/**/*.js', ['pack-js']);
gulp.watch('assets/css/**/*.css', ['pack-css']);
});
Também vamos alterar a definição da nossa tarefa padrão:
gulp.task('default', ['watch']);
Se agora executarmos o gulp a partir da linha de comando, descobriremos que ele não cria mais nada após a invocação. Isso ocorre porque agora ele chama a tarefa de observador que assiste aos nossos arquivos de origem em busca de alterações e compila apenas quando detecta uma alteração. Se tentarmos alterar qualquer um dos nossos arquivos fonte e, em seguida, olharmos novamente para o nosso console, veremos que as tarefas pack-js e pack-css são executadas automaticamente junto com suas dependências.
Agora, tudo o que precisamos fazer é carregar o arquivo manifest.json em nosso aplicativo e obter os nomes de arquivos marcados a partir dele. A forma como fazemos isso depende da nossa linguagem de back-end e da pilha de tecnologia, e seria bastante trivial de implementar, de modo que não analisaremos isso em detalhes. No entanto, a idéia geral é que podemos carregar o manifesto em uma matriz ou objeto e, em seguida, definir uma função auxiliar que nos permitirá chamar recursos com versão de nossos modelos de maneira semelhante à seguinte:
gulp(‘bundle.js’)
Depois disso, não precisaremos mais nos preocupar com as tags alteradas em nossos nomes de arquivos, e poderemos nos concentrar em escrever códigos de alta qualidade.
O código-fonte final deste artigo, junto com alguns recursos de amostra, pode ser encontrado neste repositório do GitHub.
Conclusão
Neste artigo, explicamos como implementar a automação baseada em Gulp para nosso processo de criação. Espero que isso seja útil para você e permita que você desenvolva processos de criação mais sofisticados em seus próprios aplicativos.
Lembre-se de que o Gulp é apenas uma das ferramentas que podem ser usadas para essa finalidade, e há muitas outras, como o Grunt, o Browserify e o Webpack. Eles variam em seus propósitos e no escopo dos problemas que podem resolver. Alguns podem resolver problemas que o Gulp não pode, como agrupar módulos JavaScript com dependências que podem ser carregadas sob demanda. Isso é chamado de “divisão de código” e é uma melhoria em relação à idéia de servir um arquivo grande com todas as partes do programa em todas as páginas. Essas ferramentas são bastante sofisticadas, mas podem ser abordadas no futuro. Em um post a seguir, vamos ver como automatizar a implantação de nosso aplicativo.
Fonte: toptal.com (leia o artigo original )