Minimizza e comprimi i payload di rete con gzip

Questo codelab illustra in che modo la minimizzazione e la compressione del bundle JavaScript per la seguente applicazione migliorano le prestazioni della pagina riducendo le dimensioni della richiesta dell'app.

Screenshot dell'app

Misura

Prima di iniziare ad aggiungere ottimizzazioni, è sempre buona prassi analizzare innanzitutto lo stato attuale dell'applicazione.

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

Questa app, che è stata trattata anche nel codelab "Rimuovi il codice inutilizzato", ti consente di votare per il tuo gattino preferito. 🐈

Ora dai un'occhiata alle dimensioni di questa applicazione:

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

Dimensioni del bundle originale nel riquadro Rete

Sebbene siano stati fatti molti progressi nel codelab "Rimuovi il codice inutilizzato" per ridurre le dimensioni del bundle, 225 KB sono ancora piuttosto 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 a parte, le dimensioni del file sono di circa 112 B (byte).

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

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

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

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

Le dimensioni del file ora raggiungono i 62 B.

A ogni passaggio, il codice diventa più difficile da leggere. Tuttavia, il motore JavaScript del browser interpreta ciascuno di questi nello stesso modo. L'oscuramento del codice in questo modo può contribuire a ridurre le dimensioni dei file. 112 miliardi non era molto per cominciare, ma c'è stata comunque una riduzione delle dimensioni del 50%.

In questa applicazione, la versione 4 di webpack viene utilizzata come aggregazione di moduli. La versione specifica è disponibile in package.json.

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

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

Per farti un'idea di come appare il codice compresso, fai clic su main.bundle.js mentre sei ancora nel riquadro Rete di DevTools. Ora fai clic sulla scheda Risposta.

Risposta minimizzata

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

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

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

Dimensioni del bundle di 767 KB

Una differenza sostanziale. 😅

Assicurati di annullare le modifiche qui prima di continuare.

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

L'inclusione di un processo per ridurre il codice nella tua applicazione dipende dagli strumenti che utilizzi:

  • Se viene utilizzata webpack 4 o versioni successive, non è necessario alcun intervento aggiuntivo poiché 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 lo spiega in dettaglio.
  • Esistono anche altri plug-in di minimizzazione che possono essere utilizzati al loro posto, come BabelMinifyWebpackPlugin e ClosureCompilerPlugin.
  • Se non viene utilizzato alcun aggregatore di moduli, utilizza Terser come strumento CLI o includilo direttamente come dipendenza.

Compressione

Sebbene il termine "compressione" a volte venga utilizzato in modo approssimativo per spiegare come viene ridotto il codice durante il processo di minificazione, in realtà non viene compresso nel senso letterale del termine.

La compressione in genere si riferisce al codice che è stato 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 intestazioni per includere informazioni aggiuntive sull'asset recuperato o ricevuto. Questo può essere visualizzato nella scheda Headers del riquadro Rete di DevTools, dove vengono mostrati tre tipi:

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

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

Intestazione di codifica accettata

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 per la compressione (e la decompressione) delle richieste di rete HTTP:

  • Gzip (gzip): il formato di compressione più utilizzato per le interazioni tra server e client. Si basa sull'algoritmo Deflate e è supportato in tutti i browser attuali.
  • Deflate (deflate): non è molto utilizzato.
  • Brotli (br): un algoritmo di compressione più recente che mira a migliorare ulteriormente i rapporti di compressione, il che può comportare caricamenti di pagine ancora più rapidi. È supportato nelle versioni più recenti 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 Express ora viene utilizzato come framework del server. Nelle sezioni successive vengono esplorate sia la compressione statica sia quella dinamica.

Compressione dinamica

La compressione dinamica prevede la compressione degli asset in tempo reale quando vengono richiesti dal browser.

Pro

  • Non è necessario creare e aggiornare le versioni compresse salvate delle risorse.
  • La compressione dinamica è particolarmente efficace per le pagine web generate dinamicamente.

Contro

  • La compressione dei file a livelli superiori per ottenere rapporti di compressione migliori richiede più tempo. Ciò può causare un calo delle prestazioni mentre l'utente attende la compressione degli asset prima che vengano 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, tutto ciò che fa è importare express e utilizzare il middleware express.static per caricare tutti i file HTML, JS e CSS statici nella directory public/ (e questi file vengono creati da webpack con ogni compilazione).

Per assicurarti che tutte le risorse vengano compresse ogni volta che vengono richieste, puoi utilizzare la libreria middleware di compressione. Per iniziare, aggiungilo 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');

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 salvare gli asset in anticipo.

Pro

  • La latenza dovuta a livelli di compressione elevati non è più un problema. Non è necessario alcun intervento istantaneo per comprimere i file, in quanto ora possono essere recuperati direttamente.

Contro

  • 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 prevede la compressione dei file in anticipo, le impostazioni di webpack possono essere modificate per comprimere gli asset nell'ambito del passaggio di compilazione. CompressionPlugin puoi essere utilizzato per questo.

Per iniziare, aggiungilo come devDependency in package.json:

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

Come 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 compilazione 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 ricostruita, viene creata una versione compressa del bundle principale. Apri la console Glitch per dare un'occhiata al contenuto della directory public/ finale servita dal server Node.

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

File di output finale nella directory pubblica

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

La prossima cosa da fare è dire al server di inviare questi file GZ ogni volta che vengono richieste le versioni JS originali. Per farlo, basta definire un nuovo percorso 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 la modalità di gestione di questa richiesta. Il percorso funziona nel seguente modo:

  • Se specifichi '*.js' come primo argomento, la funzionalità funziona per ogni endpoint attivato per recuperare un file JS.
  • All'interno del callback, .gz viene allegato all'URL della richiesta e l'intestazione di risposta Content-Encoding è impostata su gzip.
  • Infine, next() garantisce che la sequenza continui a qualsiasi callback che potrebbe essere successivo.

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

Riduzione delle dimensioni del bundle con la compressione statica

Come prima, una riduzione significativa delle dimensioni del bundle.

Conclusione

Questo codelab ha illustrato 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 entrambe le procedure autonomamente.