Gulp: l'arma segreta di uno sviluppatore Web per massimizzare la velocità del sito

Pubblicato: 2022-03-11

Molti di noi devono gestire progetti basati sul Web utilizzati nella produzione, che forniscono vari servizi al pubblico. Quando si tratta di progetti di questo tipo, è importante essere in grado di creare e distribuire rapidamente il nostro codice. Fare qualcosa velocemente spesso porta a errori, soprattutto se un processo è ripetitivo, quindi è buona norma automatizzare il più possibile tale processo.

Gulp: l'arma segreta di uno sviluppatore Web per massimizzare la velocità del sito

I miei colleghi sviluppatori: non ci sono scuse per servire spazzatura sul tuo browser.
Twitta

In questo post, esamineremo uno strumento che può essere una parte di ciò che ci consentirà di ottenere tale automazione. Questo strumento è un pacchetto npm chiamato Gulp.js. Per acquisire familiarità con la terminologia di base di Gulp.js utilizzata in questo post, fare riferimento a "An Introduction to JavaScript Automation with Gulp" precedentemente pubblicato sul blog da Antonios Minas, uno dei nostri colleghi sviluppatori Toptal. Assumeremo una familiarità di base con l'ambiente npm, poiché è ampiamente utilizzato in questo post per installare i pacchetti.

Servire gli asset di front-end

Prima di continuare, facciamo qualche passo indietro per avere una panoramica del problema che Gulp.js può risolvere per noi. Molti progetti basati sul Web presentano file JavaScript front-end che vengono serviti al client per fornire varie funzionalità alla pagina Web. Di solito c'è anche una serie di fogli di stile CSS che vengono serviti anche al cliente. A volte, quando guardiamo il codice sorgente di un sito Web o di un'applicazione Web, possiamo vedere un codice come questo:

 <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>

Ci sono alcuni problemi con questo codice. Ha riferimenti a due fogli di stile CSS separati e quattro file JavaScript separati. Ciò significa che il server deve effettuare un totale di sei richieste al server e ogni richiesta deve caricare separatamente una risorsa prima che la pagina sia pronta. Questo è un problema minore con HTTP/2 perché HTTP/2 introduce il parallelismo e la compressione dell'intestazione, ma è ancora un problema. Aumenta il volume totale di traffico necessario per caricare questa pagina e riduce la qualità dell'esperienza utente perché richiede più tempo per caricare i file. In caso di HTTP 1.1, monopolizza anche la rete e riduce il numero di canali di richiesta disponibili. Sarebbe stato molto meglio combinare i file CSS e JavaScript in un unico pacchetto per ciascuno. In questo modo, ci sarebbero solo un totale di due richieste. Sarebbe stato anche bello fornire versioni ridotte di questi file, che di solito sono molto più piccoli degli originali. La nostra applicazione Web potrebbe anche interrompersi se una delle risorse viene memorizzata nella cache e il client riceverà una versione obsoleta.

Sovraccarico

Un approccio primitivo per risolvere alcuni di questi problemi consiste nel combinare manualmente ogni tipo di risorsa in un pacchetto utilizzando un editor di testo, quindi eseguire il risultato tramite un servizio di minimizzazione, come http://jscompress.com/. Questo si rivela molto noioso da fare continuamente durante il processo di sviluppo. Un leggero ma discutibile miglioramento sarebbe quello di ospitare il nostro server minifier, utilizzando uno dei pacchetti disponibili su GitHub. Quindi potremmo fare cose che sembrerebbero in qualche modo simili alle seguenti:

 <script src="min/f=js/site.js,js/module1.js"></script>

Ciò servirebbe file ridotti al nostro client, ma non risolverebbe il problema della memorizzazione nella cache. Causerebbe anche un carico aggiuntivo sul server poiché il nostro server dovrebbe essenzialmente concatenare e minimizzare tutti i file di origine ripetutamente su ogni richiesta.

Automatizzare con Gulp.js

Sicuramente possiamo fare meglio di uno di questi due approcci. Quello che vogliamo veramente è automatizzare il bundling e includerlo nella fase di costruzione del nostro progetto. Vogliamo ottenere pacchetti di risorse precostruiti che sono già ridotti a icona e sono pronti per essere pubblicati. Vogliamo anche costringere il cliente a ricevere le versioni più aggiornate delle nostre risorse in bundle su ogni richiesta, ma vogliamo comunque sfruttare la memorizzazione nella cache, se possibile. Fortunatamente per noi, Gulp.js può gestirlo. Nel resto dell'articolo, creeremo una soluzione che sfrutterà la potenza di Gulp.js per concatenare e ridurre al minimo i file. Utilizzeremo anche un plug-in per bussare la cache quando ci sono aggiornamenti.

Creeremo la seguente struttura di directory e file nel nostro esempio:

 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
npm rende la gestione dei pacchetti nei progetti Node.js una gioia. Gulp offre un'incredibile estensibilità sfruttando il semplice approccio di packaging di npm per fornire plug-in modulari e potenti.

Il file gulpfile.js è dove definiremo le attività che Gulp eseguirà per noi. Il package.json viene utilizzato da npm per definire il pacchetto della nostra applicazione e tenere traccia delle dipendenze che installeremo. La directory pubblica è ciò che dovrebbe essere configurato per affrontare il web. La directory degli asset è dove memorizzeremo i nostri file di origine. Per utilizzare Gulp nel progetto, dovremo installarlo tramite npm e salvarlo come dipendenza dello sviluppatore per il progetto. Vorremo anche iniziare con il plug-in concat per Gulp, che ci consentirà di concatenare più file in uno solo.

Per installare questi due elementi, eseguiremo il seguente comando:

 npm install --save-dev gulp gulp-concat

Successivamente, vorremo iniziare a scrivere il contenuto di 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']);

Qui stiamo caricando la libreria gulp e il suo plugin concat. Definiamo quindi tre compiti.

Caricamento della libreria gulp e del suo plugin concat

La prima attività ( pack-js ) definisce una procedura per comprimere più file di origine JavaScript in un unico bundle. Elenchiamo i file di origine, che verranno globati, letti e concatenati nell'ordine specificato. Lo inseriamo nel plug-in concat per ottenere un file finale chiamato bundle.js . Infine, diciamo a Gulp di scrivere il file in public/build/js .

La seconda attività ( pack-css ) fa la stessa cosa di cui sopra, ma per i fogli di stile CSS. Dice a Gulp di memorizzare l'output concatenato come stylesheet.css in public/build/css .

La terza attività ( default ) è quella che Gulp esegue quando la invochiamo senza argomenti. Nel secondo parametro, passiamo all'elenco di altre attività da eseguire quando viene eseguita l'attività predefinita.

Incolliamo questo codice in gulpfile.js utilizzando qualsiasi editor di codice sorgente che utilizziamo normalmente, quindi salviamo il file nella radice dell'applicazione.

Successivamente, apriremo la riga di comando ed eseguiremo:

 gulp

Se esaminiamo i nostri file dopo aver eseguito questo comando, troveremo due nuovi file: public/build/js/bundle.js e public/build/css/stylesheet.css . Sono concatenazioni dei nostri file sorgente, che risolvono parte del problema originale. Tuttavia, non sono minimizzati e non c'è ancora busting della cache. Aggiungiamo la minimizzazione automatizzata.

Ottimizzazione delle risorse costruite

Avremo bisogno di due nuovi plugin. Per aggiungerli, eseguiremo il seguente comando:

 npm install --save-dev gulp-clean-css gulp-minify

Il primo plugin serve per minimizzare CSS e il secondo serve per minimizzare JavaScript. Il primo usa il pacchetto clean-css e il secondo usa il pacchetto UglifyJS2. Caricheremo prima questi due pacchetti nel nostro gulpfile.js:

 var minify = require('gulp-minify'); var cleanCss = require('gulp-clean-css');

Dovremo quindi usarli nelle nostre attività appena prima di scrivere l'output su disco:

 .pipe(minify()) .pipe(cleanCss())

Il gulpfile.js ora dovrebbe assomigliare a questo:

 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']);

Facciamo un sorso di nuovo. Vedremo che il file stylesheet.css è salvato in formato ridotto e il file bundle.js è ancora salvato così com'è. Noteremo che ora abbiamo anche bundle-min.js, che è minimizzato. Vogliamo solo il file minimizzato e vogliamo che venga salvato come bundle.js , quindi modificheremo il nostro codice con parametri aggiuntivi:

 .pipe(minify({ ext:{ min:'.js' }, noSource: true }))

Come da documentazione del plug-in gulp-minify (https://www.npmjs.com/package/gulp-minify), questo imposterà il nome desiderato per la versione ridotta e dirà al plug-in di non creare la versione contenente il sorgente originale. Se cancelliamo il contenuto della directory build ed eseguiamo di nuovo gulp dalla riga di comando, ci ritroveremo con solo due file minimizzati. Abbiamo appena finito di implementare la fase di minimizzazione del nostro processo di costruzione.

Cache Busting

Successivamente, vorremo aggiungere il busting della cache e dovremo installare un plug-in per questo:

 npm install --save-dev gulp-rev

E richiedilo nel nostro file gulp:

 var rev = require('gulp-rev');

Usare il plugin è un po' complicato. Dobbiamo prima reindirizzare l'output minimizzato attraverso il plug-in. Quindi, dobbiamo chiamare di nuovo il plug-in dopo aver scritto i risultati su disco. Il plug-in rinomina i file in modo che vengano contrassegnati con un hash univoco e crea anche un file manifest. Il file manifest è una mappa che può essere utilizzata dalla nostra applicazione per determinare gli ultimi nomi di file a cui dovremmo fare riferimento nel nostro codice HTML. Dopo aver modificato il file gulp, dovrebbe apparire così:

 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']);
Con un corretto busting della cache in atto, puoi impazzire con lunghi tempi di scadenza per i tuoi file JS e CSS e sostituirli in modo affidabile ancora con versioni più recenti ogni volta che è necessario.

Eliminiamo il contenuto della nostra directory build ed eseguiamo di nuovo gulp. Scopriremo che ora abbiamo due file con hashtag apposti a ciascuno dei nomi di file e un manifest.json salvato in public/build . Se apriamo il file manifest, vedremo che ha solo un riferimento a uno dei nostri file minimizzati e contrassegnati. Quello che sta succedendo è che ogni attività scrive un file manifest separato e uno di essi finisce per sovrascrivere l'altro. Avremo bisogno di modificare le attività con parametri aggiuntivi che diranno loro di cercare il file manifest esistente e di unire i nuovi dati in esso se esiste. La sintassi per questo è un po 'complicata, quindi diamo un'occhiata a come dovrebbe essere il codice e poi esaminiamolo:

 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']);

Per prima cosa stiamo inviando l'output a rev.manifest() . Questo crea file con tag invece dei file che avevamo prima. Stiamo fornendo il percorso desiderato del nostro rev-manifest.json e stiamo dicendo a rev.manifest() di fondersi nel file esistente, se esiste. Quindi stiamo dicendo a gulp di scrivere il manifest nella directory corrente, che a quel punto sarà public/build. Il problema del percorso è dovuto a un bug discusso in modo più dettagliato su GitHub.

Ora abbiamo la minimizzazione automatizzata, i file con tag e un file manifest. Tutto ciò ci consentirà di consegnare i file più rapidamente all'utente e di rompere la cache ogni volta che apportiamo le nostre modifiche. Ci sono solo due problemi rimanenti però.

Il primo problema è che se apportiamo modifiche ai nostri file di origine, otterremo file appena taggati, ma anche quelli vecchi rimarranno lì. Abbiamo bisogno di un modo per eliminare automaticamente i vecchi file minimizzati. Risolviamo questo problema utilizzando un plugin che ci permetterà di eliminare i file:

 npm install --save-dev del

Lo richiederemo nel nostro codice e definiremo due nuove attività, una per ogni tipo di file sorgente:

 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' ]); });

Ci assicureremo quindi che la nuova attività termini l'esecuzione prima delle nostre due attività principali:

 gulp.task('pack-js', ['clean-js'], function () { gulp.task('pack-css', ['clean-css'], function () {

Se eseguiamo di nuovo gulp dopo questa modifica, avremo solo gli ultimi file minimizzati.

Il secondo problema è che non vogliamo continuare a bere ogni volta che facciamo un cambiamento. Per risolvere questo problema, dovremo definire un task watcher:

 gulp.task('watch', function() { gulp.watch('assets/js/**/*.js', ['pack-js']); gulp.watch('assets/css/**/*.css', ['pack-css']); });

Cambieremo anche la definizione della nostra attività predefinita:

 gulp.task('default', ['watch']);

Se ora eseguiamo gulp dalla riga di comando, scopriremo che non crea più nulla sull'invocazione. Questo perché ora chiama l'attività di controllo che controllerà i nostri file di origine per eventuali modifiche e costruirà solo quando rileva una modifica. Se proviamo a modificare uno qualsiasi dei nostri file di origine e poi esaminiamo di nuovo la nostra console, vedremo che le attività pack-js e pack-css vengono eseguite automaticamente insieme alle loro dipendenze.

Ora, tutto ciò che dobbiamo fare è caricare il file manifest.json nella nostra applicazione e ottenere i nomi dei file contrassegnati da quello. Il modo in cui lo facciamo dipende dal nostro particolare linguaggio di back-end e dallo stack tecnologico e sarebbe abbastanza banale da implementare, quindi non lo esamineremo in dettaglio. Tuttavia, l'idea generale è che possiamo caricare il manifest in un array o in un oggetto e quindi definire una funzione di supporto che ci consentirà di chiamare asset con versione dai nostri modelli in un modo simile al seguente:

 gulp('bundle.js')

Una volta fatto, non dovremo più preoccuparci dei tag modificati nei nostri nomi di file e saremo in grado di concentrarci sulla scrittura di codice di alta qualità.

Il codice sorgente finale per questo articolo, insieme ad alcune risorse di esempio, è disponibile in questo repository GitHub.

Conclusione

In questo articolo, abbiamo esaminato come implementare l'automazione basata su Gulp per il nostro processo di compilazione. Spero che questo ti sia utile e ti permetta di sviluppare processi di compilazione più sofisticati nelle tue applicazioni.

Tieni presente che Gulp è solo uno degli strumenti che possono essere utilizzati per questo scopo e ce ne sono molti altri come Grunt, Browserify e Webpack. Variano nei loro scopi e nella portata dei problemi che possono risolvere. Alcuni possono risolvere problemi che Gulp non può, come il raggruppamento di moduli JavaScript con dipendenze che possono essere caricate su richiesta. Questo è indicato come "divisione del codice" ed è un miglioramento rispetto all'idea di servire un file di grandi dimensioni con tutte le parti del nostro programma su ogni pagina. Questi strumenti sono piuttosto sofisticati ma potrebbero essere trattati in futuro. In un post successivo, esamineremo come automatizzare la distribuzione della nostra applicazione.

Correlati: Gulp Under the Hood: creazione di uno strumento di automazione delle attività basato sul flusso