Minimizza e comprimi i payload di rete con gzip

Questo codelab illustra come minimizzare e comprimere il bundle JavaScript per la seguente applicazione migliora le prestazioni della pagina riducendo le dimensioni della richiesta dell'app.

Screenshot dell'app

Misurazione

Prima di iniziare con l'aggiunta di ottimizzazioni, è sempre opportuno analizzare lo stato attuale dell'applicazione.

  • Per visualizzare l'anteprima del sito, premi Visualizza app, quindi Schermo intero schermo intero.

Questa app, inclusa anche nel codelab "Rimuovere il codice inutilizzato", ti consente di votare il tuo gattino preferito. 🐈

Ora dai un'occhiata alle dimensioni di questa applicazione:

  1. Premi "Ctrl+Maiusc+J" (o "Comando+Opzione+J" su Mac) per aprire DevTools.
  2. Fai clic sulla scheda Rete.
  3. Seleziona la casella di controllo Disabilita cache.
  4. Ricarica l'app.

Dimensioni del bundle originali nel riquadro Rete

Anche se sono stati fatti molti progressi nel codelab "Rimuovi il codice inutilizzato" per ridurre le dimensioni di questo bundle, i 225 kB sono ancora abbastanza grandi.

Minimizzazione

Considera il seguente blocco di codice.

function soNice() {
  let counter = 0;

  while (counter < 100) {
    console.log('nice');
    counter++;
  }
}

Se questa funzione viene salvata in un file autonomo, le dimensioni del file sono di circa 112 B (byte).

Se vengono rimossi tutti gli spazi vuoti, il codice risultante avrà il seguente aspetto:

function soNice(){let counter=0;while(counter<100){console.log("nice");counter++;}}

Le dimensioni del file ora sarebbero di circa 83 B. Se viene ulteriormente manipolato riducendo la lunghezza del nome della variabile e modificando alcune espressioni, il codice finale potrebbe avere un aspetto simile al seguente:

function soNice(){for(let i=0;i<100;)console.log("nice"),i++}

Le dimensioni del file raggiungono ora 62 B.

A ogni passaggio, la lettura del codice diventa più difficile. Tuttavia, il motore JavaScript del browser li interpreta nello stesso modo. Il vantaggio dell'offuscamento del codice in questo modo può aiutare a ridurre le dimensioni dei file. 112 B non era molto all'inizio, ma c'era comunque una riduzione del 50% delle dimensioni.

In questa applicazione, webpack versione 4 viene utilizzata come bundler del modulo. La versione specifica può essere visualizzata in package.json.

"devDependencies": {
  //...
  "webpack": "^4.16.4",
  //...
}

La versione 4 minimizza già il bundle per impostazione predefinita durante la modalità di produzione. Usa TerserWebpackPlugin un plug-in per Terser. Terser è un noto strumento utilizzato per comprimere il codice JavaScript.

Per avere un'idea dell'aspetto del codice minimizzato, fai clic su main.bundle.js mentre ti trovi ancora nel riquadro Rete di DevTools. Ora fai clic sulla scheda Risposta.

Risposta minimizzata

Il codice nella sua forma finale, minimizzato e manipolato, è mostrato nel corpo della risposta. Per scoprire le dimensioni del bundle se non fosse stato minimizzato, apri webpack.config.js e aggiorna la configurazione di mode.

module.exports = {
  mode: 'production',
  mode: 'none',
  //...

Ricarica l'applicazione e controlla di nuovo le dimensioni del bundle tramite il riquadro Network di DevTools

Dimensione del pacchetto da 767 kB

È una grossa differenza! 😅

Assicurati di annullare le modifiche qui prima di continuare.

module.exports = {
  mode: 'production',
  mode: 'none',
  //...

L'inclusione di un processo per minimizzare il codice nell'applicazione dipende dagli strumenti utilizzati:

  • Se viene utilizzato webpack v4 o superiore, non è necessario svolgere alcun lavoro aggiuntivo perché il codice viene minimizzato per impostazione predefinita in modalità di produzione. 👍
  • Se viene utilizzata una versione precedente di webpack, installa e includi TerserWebpackPlugin nel processo di compilazione di webpack. La documentazione spiega questo aspetto in dettaglio.
  • Esistono anche altri plug-in di minimizzazione che possono essere utilizzati al loro posto, ad esempio BabelMinifyWebpackPlugin e ClosureCompilerPlugin.
  • Se un bundler di moduli non viene utilizzato, usa Terser come strumento di interfaccia a riga di comando o includilo direttamente come dipendenza.

Compressione

Sebbene il termine "compressione" sia a volte usato a grandi linee per spiegare come il codice viene ridotto durante il processo di minimizzazione, in realtà non viene compresso nel senso letterale.

In genere, la compressione si riferisce a un codice modificato utilizzando un algoritmo di compressione dei dati. A differenza della minimizzazione, che fornisce un codice perfettamente valido, il codice compresso deve essere decompresso prima di essere utilizzato.

Con ogni richiesta e risposta HTTP, i browser e i server web possono aggiungere headers per includere ulteriori informazioni sull'asset recuperato o ricevuto. Puoi visualizzarlo nella scheda Headers all'interno del riquadro Network DevTools, dove sono mostrati tre tipi:

  • Generale rappresenta le intestazioni generali pertinenti all'intera interazione richiesta-risposta.
  • Intestazioni della risposta mostra un elenco di intestazioni specifiche per la risposta effettiva del server.
  • Intestazioni della richiesta mostra un elenco di intestazioni collegate alla richiesta dal client.

Dai un'occhiata all'intestazione accept-encoding in Request Headers.

Accetta intestazione di codifica

accept-encoding viene utilizzato dal browser per specificare i formati di codifica dei contenuti o gli algoritmi di compressione supportati. Esistono molti algoritmi di compressione del testo, ma solo tre sono supportati qui per la compressione (e la decompressione) delle richieste di rete HTTP:

  • Gzip (gzip): il formato di compressione più usato per le interazioni server e client. Si basa sull'algoritmo Deflate ed è supportato in tutti i browser attuali.
  • Deflate (deflate): non comunemente utilizzata.
  • Brotli (br): un algoritmo di compressione più recente che mira a migliorare ulteriormente i rapporti di compressione, determinando un caricamento delle pagine ancora più rapido. È supportato nelle ultime versioni della maggior parte dei browser.

L'applicazione di esempio in questo tutorial è identica all'app completata nel codelab "Rimuovi il codice inutilizzato", tranne per il fatto che ora Express viene utilizzato come framework del server. Nelle prossime sezioni, verrà trattata la compressione sia statica che dinamica.

Compressione dinamica

La compressione dinamica comporta la compressione immediata degli asset quando vengono richiesti dal browser.

Vantaggi

  • Non è necessario creare e aggiornare le versioni compresse salvate degli asset.
  • La compressione immediata funziona particolarmente bene per le pagine web generate dinamicamente.

Svantaggi

  • La compressione di file a livelli più elevati per ottenere rapporti di compressione migliori richiede più tempo. Ciò può causare un hit da rendimento in quanto l'utente attende che gli asset vengano compressi prima di essere inviati dal server.

Compressione dinamica con Node/Express

Il file server.js è responsabile della configurazione del server Node che ospita l'applicazione.

const express = require('express');

const app = express();

app.use(express.static('public'));

const listener = app.listen(process.env.PORT, function() {
  console.log('Your app is listening on port ' + listener.address().port);
});

Al momento è sufficiente importare express e utilizzare il middleware express.static per caricare tutti i file statici HTML, JS e CSS nella directory public/ (i file vengono creati tramite webpack con ogni build).

Per assicurarti che tutti gli asset vengano compressi ogni volta che vengono richiesti, è possibile utilizzare la libreria middleware di compressione. Inizia aggiungendolo come devDependency in package.json:

"devDependencies": {
  //...
  "compression": "^1.7.3"
},

E importalo nel file del server, server.js:

const express = require('express');
const compression = require('compression');

E aggiungilo come middleware prima del montaggio di express.static:

//...

const app = express();

app.use(compression());

app.use(express.static('public'));

//...

Ora ricarica l'app e controlla le dimensioni del bundle nel riquadro Rete.

Dimensioni del bundle con compressione dinamica

Da 225 KB a 61,6 KB! Ora in Response Headers, un'intestazione content-encoding indica che il server sta inviando questo file codificato con gzip.

Intestazione di codifica dei contenuti

Compressione statica

L'idea alla base della compressione statica è comprimere e risparmiare in anticipo gli asset.

Vantaggi

  • La latenza dovuta a livelli di compressione elevati non è più un problema. Non è necessario che avvenga nulla al volo per comprimere i file, poiché ora possono essere recuperati direttamente.

Svantaggi

  • Gli asset devono essere compressi a ogni build. I tempi di compilazione possono aumentare in modo significativo se vengono utilizzati livelli di compressione elevati.

Compressione statica con Node/Express e webpack

Poiché la compressione statica implica la compressione anticipata dei file, le impostazioni webpack possono essere modificate per comprimere gli asset come parte del passaggio di creazione. A questo scopo è possibile utilizzare CompressionPlugin.

Inizia aggiungendolo come devDependency in package.json:

"devDependencies": {
  //...
  "compression-webpack-plugin": "^1.1.11"
},

Come per qualsiasi altro plug-in Webpack, importalo nel file di configurazione webpack.config.js:

const path = require("path");

//...

const CompressionPlugin = require("compression-webpack-plugin");

e includilo nell'array plugins:

module.exports = {
  //...
  plugins: [
    //...
    new CompressionPlugin()
  ]
}

Per impostazione predefinita, il plug-in comprime i file di build utilizzando gzip. Consulta la documentazione per scoprire come aggiungere opzioni per utilizzare un algoritmo diverso o includere/escludere determinati file.

Quando l'app viene ricaricata e ricreata, viene creata una versione compressa del bundle principale. Apri la console Glitch per dare un'occhiata al contenuto della directory public/ finale gestita dal server Node.

  • Fai clic sul pulsante Strumenti.
  • Fai clic sul pulsante Console.
  • Nella console, esegui questi comandi per passare alla directory public e visualizzare tutti i relativi file:
cd public
ls

File finali di output nella directory pubblica

Qui viene salvata anche la versione compressa con gzip del bundle, main.bundle.js.gz. Per impostazione predefinita, CompressionPlugin comprime anche index.html.

La prossima cosa da fare è dire al server di inviare questi file compressi in formato gzip ogni volta che vengono richieste le versioni JS originali. Per farlo, puoi definire una nuova route in server.js prima che i file vengano pubblicati con express.static.

const express = require('express');
const app = express();

app.get('*.js', (req, res, next) => {
  req.url = req.url + '.gz';
  res.set('Content-Encoding', 'gzip');
  next();
});

app.use(express.static('public'));

//...

app.get viene utilizzato per indicare al server come rispondere a una richiesta GET per un endpoint specifico. Viene quindi utilizzata una funzione di callback per definire come gestire questa richiesta. Il percorso funziona nel seguente modo:

  • Specificare '*.js' come primo argomento significa che questa operazione funziona per ogni endpoint attivato per recuperare un file JS.
  • All'interno del callback, .gz è associato all'URL della richiesta e l'intestazione della risposta Content-Encoding è impostata su gzip.
  • Infine, next() garantisce che la sequenza continui con tutti i callback successivi.

Una volta ricaricata l'app, dai un'altra occhiata al riquadro Network.

Riduzione delle dimensioni del bundle con la compressione statica

Come in precedenza, si è verificata una riduzione significativa delle dimensioni dei bundle.

Conclusione

Questo codelab ha trattato il processo di minimizzazione e compressione del codice sorgente. Entrambe queste tecniche stanno diventando predefinite in molti degli strumenti disponibili oggi, quindi è importante scoprire se la tua toolchain le supporta già o se dovresti iniziare ad applicare entrambi i processi personalmente.