Gulp: arma secretă a unui dezvoltator web pentru maximizarea vitezei site-ului
Publicat: 2022-03-11Mulți dintre noi trebuie să se ocupe de proiecte bazate pe web care sunt utilizate în producție, care oferă diverse servicii publicului. Când aveți de-a face cu astfel de proiecte, este important să putem construi și implementa codul nostru rapid. A face ceva rapid duce adesea la erori, mai ales dacă un proces este repetitiv, de aceea este o practică bună să automatizezi un astfel de proces cât mai mult posibil.
În această postare, ne vom uita la un instrument care poate face parte din ceea ce ne va permite să realizăm o astfel de automatizare. Acest instrument este un pachet npm numit Gulp.js. Pentru a vă familiariza cu terminologia de bază Gulp.js folosită în această postare, vă rugăm să consultați „O introducere în automatizarea JavaScript cu Gulp”, care a fost publicat anterior pe blog de Antonios Minas, unul dintre colegii noștri dezvoltatori Toptal. Vom presupune familiaritatea de bază cu mediul npm, deoarece este folosit pe scară largă în această postare pentru a instala pachete.
Servirea activelor front-end
Înainte de a continua, să facem câțiva pași înapoi pentru a obține o imagine de ansamblu asupra problemei pe care Gulp.js o poate rezolva pentru noi. Multe proiecte bazate pe web prezintă fișiere JavaScript front-end care sunt oferite clientului pentru a oferi diferite funcționalități paginii web. De obicei, există și un set de foi de stil CSS care sunt oferite și clientului. Uneori, când ne uităm la codul sursă al unui site web sau al unei aplicații web, putem vedea codul astfel:
<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>
Există câteva probleme cu acest cod. Are referințe la două foi de stil CSS separate și patru fișiere JavaScript separate. Aceasta înseamnă că serverul trebuie să facă un total de șase solicitări către server și fiecare cerere trebuie să încarce separat o resursă înainte ca pagina să fie gata. Aceasta este mai puțin o problemă cu HTTP/2, deoarece HTTP/2 introduce paralelismul și comprimarea antetului, dar este totuși o problemă. Mărește volumul total de trafic necesar pentru a încărca această pagină și reduce calitatea experienței utilizatorului, deoarece durează mai mult încărcarea fișierelor. În cazul HTTP 1.1, de asemenea, blochează rețeaua și reduce numărul de canale de solicitare disponibile. Ar fi fost mult mai bine să combinați fișierele CSS și JavaScript într-un singur pachet pentru fiecare. Astfel, ar exista doar două cereri în total. De asemenea, ar fi fost frumos să difuzăm versiuni reduse ale acestor fișiere, care sunt de obicei mult mai mici decât cele originale. Aplicația noastră web s-ar putea rupe, de asemenea, dacă oricare dintre active este stocat în cache, iar clientul ar primi o versiune învechită.
O abordare primitivă pentru rezolvarea unora dintre aceste probleme este de a combina manual fiecare tip de activ într-un pachet utilizând un editor de text și apoi de a rula rezultatul printr-un serviciu de reducere, cum ar fi http://jscompress.com/. Acest lucru se dovedește a fi foarte obositor de făcut continuu în timpul procesului de dezvoltare. O îmbunătățire ușoară, dar discutabilă, ar fi găzduirea propriului server de minificator, folosind unul dintre pachetele disponibile pe GitHub. Apoi am putea face lucruri care ar arăta oarecum similare cu următoarele:
<script src="min/f=js/site.js,js/module1.js"></script>
Acest lucru ar servi fișiere minificate clientului nostru, dar nu ar rezolva problema stocării în cache. De asemenea, ar provoca încărcare suplimentară pe server, deoarece serverul nostru ar trebui, în esență, să concateneze și să minimizeze toate fișierele sursă în mod repetitiv la fiecare solicitare.
Automatizarea cu Gulp.js
Cu siguranță putem face mai bine decât oricare dintre aceste două abordări. Ceea ce ne dorim cu adevărat este să automatizăm gruparea și să o includem în faza de construire a proiectului nostru. Dorim să ajungem cu pachete de active pre-construite care sunt deja reduse și sunt gata de difuzare. De asemenea, dorim să forțăm clientul să primească cele mai actualizate versiuni ale activelor noastre bundle la fiecare solicitare, dar dorim totuși să folosim stocarea în cache, dacă este posibil. Din fericire pentru noi, Gulp.js se poate descurca cu asta. În restul articolului, vom construi o soluție care va valorifica puterea Gulp.js pentru a concatena și a minimiza fișierele. Vom folosi, de asemenea, un plugin pentru a sparge memoria cache atunci când există actualizări.
Vom crea următorul director și structură de fișiere în exemplul nostru:
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
Fișierul gulpfile.js este locul în care vom defini sarcinile pe care le va îndeplini Gulp pentru noi. package.json
este folosit de npm pentru a defini pachetul aplicației noastre și pentru a urmări dependențele pe care le vom instala. Directorul public este ceea ce ar trebui configurat pentru a se confrunta cu web. Directorul assets este locul în care vom stoca fișierele sursă. Pentru a folosi Gulp în proiect, va trebui să-l instalăm prin npm și să-l salvăm ca dependență de dezvoltator pentru proiect. De asemenea, vom dori să începem cu pluginul concat
pentru Gulp, care ne va permite să concatenăm mai multe fișiere într-unul singur.
Pentru a instala aceste două elemente, vom rula următoarea comandă:
npm install --save-dev gulp gulp-concat
În continuare, vom dori să începem să scriem conținutul 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']);
Aici încărcăm biblioteca gulp și pluginul său concat. Definim apoi trei sarcini.
Prima sarcină ( pack-js
) definește o procedură de comprimare a mai multor fișiere sursă JavaScript într-un singur pachet. Enumerăm fișierele sursă, care vor fi globate, citite și concatenate în ordinea specificată. O introducem în pluginul concat pentru a obține un fișier final numit bundle.js
. În cele din urmă, îi spunem lui gulp să scrie fișierul în public/build/js
.
A doua sarcină ( pack-css
) face același lucru ca mai sus, dar pentru foile de stil CSS. Îi spune lui Gulp să stocheze ieșirea concatenată ca stylesheet.css
în public/build/css
.
A treia sarcină ( default
) este cea pe care o rulează Gulp atunci când o invocăm fără argumente. În cel de-al doilea parametru, trecem lista cu alte sarcini de executat atunci când sarcina implicită este executată.
Să lipim acest cod în gulpfile.js folosind orice editor de cod sursă pe care îl folosim în mod normal și apoi să salvăm fișierul în rădăcina aplicației.
În continuare, vom deschide linia de comandă și vom rula:
gulp
Dacă ne uităm la fișierele noastre după rularea acestei comenzi, vom găsi două fișiere noi: public/build/js/bundle.js
și public/build/css/stylesheet.css
. Sunt concatenări ale fișierelor noastre sursă, care rezolvă o parte a problemei inițiale. Cu toate acestea, ele nu sunt minimizate și nu există încă nicio distrugere a memoriei cache. Să adăugăm minimizarea automată.
Optimizarea activelor construite
Vom avea nevoie de două plugin-uri noi. Pentru a le adăuga, vom rula următoarea comandă:
npm install --save-dev gulp-clean-css gulp-minify
Primul plugin este pentru minimizarea CSS, iar al doilea este pentru minimizarea JavaScript. Primul folosește pachetul clean-css, iar al doilea folosește pachetul UglifyJS2. Vom încărca mai întâi aceste două pachete în gulpfile.js:
var minify = require('gulp-minify'); var cleanCss = require('gulp-clean-css');
Va trebui apoi să le folosim în sarcinile noastre chiar înainte de a scrie rezultatul pe disc:
.pipe(minify()) .pipe(cleanCss())
gulpfile.js ar trebui să arate acum astfel:
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']);
Hai să alergăm din nou înghițit. Vom vedea că fișierul stylesheet.css
este salvat în format redus, iar fișierul bundle.js
este încă salvat așa cum este. Vom observa că acum avem și bundle-min.js, care este miniat. Vrem doar fișierul minimizat și vrem să fie salvat ca bundle.js
, așa că ne vom modifica codul cu parametri suplimentari:

.pipe(minify({ ext:{ min:'.js' }, noSource: true }))
Conform documentației pluginului gulp-minify (https://www.npmjs.com/package/gulp-minify), aceasta va seta numele dorit pentru versiunea minimizată și va spune pluginului să nu creeze versiunea care conține sursa originală. Dacă ștergem conținutul directorului de compilare și rulăm din nou gulp din linia de comandă, vom ajunge cu doar două fișiere minificate. Tocmai am terminat de implementat faza de minimizare a procesului nostru de construire.
Cache Busting
În continuare, vom dori să adăugăm cache busting și va trebui să instalăm un plugin pentru asta:
npm install --save-dev gulp-rev
Și solicitați-l în fișierul nostru gulp:
var rev = require('gulp-rev');
Utilizarea pluginului este puțin dificilă. Mai întâi trebuie să transmitem rezultatul minimizat prin plugin. Apoi, trebuie să apelăm din nou pluginul după ce scriem rezultatele pe disc. Pluginul redenumește fișierele astfel încât să fie etichetate cu un hash unic și creează, de asemenea, un fișier manifest. Fișierul manifest este o hartă care poate fi folosită de aplicația noastră pentru a determina cele mai recente nume de fișiere la care ar trebui să ne referim în codul nostru HTML. După ce modificăm fișierul gulp, ar trebui să arate astfel:
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']);
Să ștergem conținutul directorului nostru de compilare și să rulăm din nou gulp. Vom descoperi că acum avem două fișiere cu hashtag-uri aplicate la fiecare nume de fișier și un manifest.json salvat în public/build
. Dacă deschidem fișierul manifest, vom vedea că are doar o referință la unul dintre fișierele noastre minimizate și etichetate. Ceea ce se întâmplă este că fiecare sarcină scrie un fișier manifest separat, iar unul dintre ele ajunge să îl suprascrie pe celălalt. Va trebui să modificăm sarcinile cu parametri suplimentari care le vor spune să caute fișierul manifest existent și să îmbine noile date în el dacă există. Sintaxa pentru aceasta este puțin complicată, așa că să ne uităm la cum ar trebui să arate codul și apoi să trecem peste el:
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']);
Mai întâi trimitem ieșirea la rev.manifest()
. Acest lucru creează fișiere etichetate în locul fișierelor pe care le aveam înainte. Oferim calea dorită a rev-manifest.json
și îi spunem rev.manifest()
să fuzioneze în fișierul existent, dacă acesta există. Apoi îi spunem lui gulp să scrie manifestul în directorul curent, care în acel moment va fi public/build. Problema căii se datorează unei erori care este discutată mai detaliat pe GitHub.
Avem acum minificare automată, fișiere etichetate și un fișier manifest. Toate acestea ne vor permite să livrăm fișierele mai rapid utilizatorului și să le distrugem memoria cache de fiecare dată când facem modificări. Totuși, au rămas doar două probleme.
Prima problemă este că, dacă facem modificări la fișierele noastre sursă, vom primi fișiere nou etichetate, dar și cele vechi vor rămâne acolo. Avem nevoie de o modalitate de a șterge automat fișierele vechi minificate. Să rezolvăm această problemă folosind un plugin care ne va permite să ștergem fișiere:
npm install --save-dev del
O vom solicita în codul nostru și vom defini două sarcini noi, câte una pentru fiecare tip de fișier sursă:
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' ]); });
Ne vom asigura apoi că noua sarcină se termină înaintea celor două sarcini principale:
gulp.task('pack-js', ['clean-js'], function () { gulp.task('pack-css', ['clean-css'], function () {
Dacă rulăm din nou gulp
după această modificare, vom avea doar cele mai recente fișiere minificate.
A doua problemă este că nu vrem să continuăm să înghițim de fiecare dată când facem o schimbare. Pentru a rezolva acest lucru, va trebui să definim o sarcină de observator:
gulp.task('watch', function() { gulp.watch('assets/js/**/*.js', ['pack-js']); gulp.watch('assets/css/**/*.css', ['pack-css']); });
Vom schimba, de asemenea, definiția sarcinii noastre implicite:
gulp.task('default', ['watch']);
Dacă rulăm acum gulp din linia de comandă, vom descoperi că nu mai construiește nimic la invocare. Acest lucru se datorează faptului că acum apelează sarcina de supraveghere care va urmări fișierele noastre sursă pentru orice modificări și va construi numai atunci când detectează o modificare. Dacă încercăm să schimbăm oricare dintre fișierele noastre sursă și apoi ne uităm din nou la consola noastră, vom vedea că sarcinile pack-js
și pack-css
rulează automat împreună cu dependențele lor.
Acum, tot ce trebuie să facem este să încărcăm fișierul manifest.json în aplicația noastră și să obținem numele fișierelor etichetate din acesta. Modul în care facem asta depinde de limbajul și tehnologia noastră de back-end și ar fi destul de banal de implementat, așa că nu o vom analiza în detaliu. Cu toate acestea, ideea generală este că putem încărca manifestul într-o matrice sau într-un obiect și apoi să definim o funcție de ajutor care ne va permite să apelăm active versiuni din șabloanele noastre într-un mod similar cu următorul:
gulp('bundle.js')
Odată ce facem asta, nu va mai trebui să ne facem griji cu privire la etichetele modificate în numele fișierelor noastre și ne vom putea concentra pe scrierea codului de înaltă calitate.
Codul sursă final pentru acest articol, împreună cu câteva exemple de active, poate fi găsit în acest depozit GitHub.
Concluzie
În acest articol, am analizat cum să implementăm automatizarea bazată pe Gulp pentru procesul nostru de construire. Sper că acest lucru vă va fi de ajutor și vă permite să dezvoltați procese de compilare mai sofisticate în propriile aplicații.
Vă rugăm să rețineți că Gulp este doar unul dintre instrumentele care pot fi utilizate în acest scop și există multe altele, cum ar fi Grunt, Browserify și Webpack. Ele variază în ceea ce privește scopurile și sfera problemelor pe care le pot rezolva. Unele pot rezolva probleme pe care Gulp nu le poate, cum ar fi gruparea modulelor JavaScript cu dependențe care pot fi încărcate la cerere. Aceasta se numește „divizare de cod” și este o îmbunătățire față de ideea de a servi un fișier mare cu toate părțile programului nostru pe fiecare pagină. Aceste instrumente sunt destul de sofisticate, dar ar putea fi acoperite în viitor. Într-o postare următoare, vom analiza cum să automatizăm implementarea aplicației noastre.