Minimizza e comprimi i payload di rete con gzip

Questo codelab illustra in che modo minimizzare e comprimere il bundle JavaScript per la seguente applicazione migliora le prestazioni delle pagine riducendo le dimensioni della richiesta dell'app.

Screenshot dell'app

Misura

Prima di addentrarci nelle 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, trattata anche nel codelab "Rimuovi codice inutilizzato", ti consente di votare il tuo gattino preferito. 🐈

Ora vediamo le 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 Disabilita cache.
  4. Ricarica l'app.

Dimensione del bundle originale nel riquadro Rete

Sebbene siano stati compiuti molti progressi nel codelab "Rimuovi il codice inutilizzato" per ridurre le dimensioni di questo bundle, 225 kB sono ancora molto 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 proprietario, le dimensioni del file saranno di circa 112 B (byte).

Se vengono rimossi tutti gli spazi vuoti, il codice risultante sarà simile a questo:

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

Le dimensioni del file saranno ora 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 62 byte.

A ogni passaggio, il codice diventa più difficile da leggere. Tuttavia, il motore JavaScript del browser li interpreta esattamente nello stesso modo. Il vantaggio di offuscare il codice in questo modo può aiutare a ottenere file di dimensioni inferiori. Il formato 112 B non era molto all'inizio, ma c'era comunque una riduzione delle dimensioni del 50%.

In questa applicazione, webpack versione 4 viene utilizzato come bundler di moduli. La versione specifica è visibile in package.json.

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

Per impostazione predefinita, la versione 4 minimizza il bundle in modalità di produzione. Utilizza 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 sei ancora nel riquadro Rete di DevTools. Ora fai clic sulla scheda Risposta.

Risposta minimizzata

Il codice nella sua forma finale, minimizzato e strappato, 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 ricontrolla le dimensioni del bundle tramite il riquadro Rete di DevTools

Dimensione bundle 767 kB

Si tratta di una differenza piuttosto importante! 😅

Assicurati di ripristinare 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 che utilizzi:

  • Se viene utilizzato Webpack v4 o versioni successive, non è necessario eseguire ulteriori operazioni 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 del webpack. La documentazione spiega questo aspetto in dettaglio.
  • Esistono anche altri plug-in di minimizzazione che possono essere utilizzati al loro posto, come 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 utilizzato in modo generico per spiegare in che modo il codice viene ridotto durante il processo di minimizzazione, in realtà non è compresso in senso letterale.

La compressione si riferisce in genere a un codice che è stato modificato utilizzando un algoritmo di compressione dei dati. A differenza della minimizzazione, che finisce per fornire codice perfettamente valido, il codice compresso deve essere decompresso prima di essere utilizzato.

Per 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 di DevTools, dove vengono mostrati tre tipi:

  • Generale rappresenta le intestazioni generali pertinenti all'intera interazione richiesta-risposta.
  • Intestazioni delle risposte mostra un elenco di intestazioni specifiche della risposta effettiva del server.
  • Intestazioni delle richieste mostra un elenco di intestazioni allegate 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 che supporta. Esistono molti algoritmi di compressione del testo, ma qui ne sono supportati solo tre per la compressione (e la decompressione) delle richieste di rete HTTP:

  • Gzip (gzip): il formato di compressione più utilizzato per le interazioni con server e client. Si basa sull'algoritmo Deflate ed è supportato in tutti i browser attuali.
  • Deflate (deflate): opzione non comune.
  • Brotli (br): un algoritmo di compressione più recente che mira a migliorare ulteriormente i rapporti di compressione, determinando un caricamento delle pagine ancora più veloce. È 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 ora Express viene utilizzato come framework del server. Nelle sezioni successive vedremo la compressione sia statica che dinamica.

Compressione dinamica

La compressione dinamica comporta la compressione all'istante degli asset quando vengono richiesti dal browser.

Vantaggi

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

Svantaggi

  • La compressione dei file a livelli più elevati per ottenere rapporti di compressione migliori richiede più tempo. Di conseguenza, l'utente attende che gli asset vengano compressi prima che vengano inviati dal server.

Compressione dinamica con Node/Express

Il file server.js è responsabile della configurazione del server nodo 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 HTML, JS e CSS statici nella directory public/ (questi file vengono creati dal 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');

Infine, aggiungilo come middleware prima che venga montato express.static:

//...

const app = express();

app.use(compression());

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

//...

Ora ricarica l'app e dai un'occhiata alle dimensioni del gruppo nel riquadro Rete.

Dimensioni bundle con compressione dinamica

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

Intestazione per la codifica dei contenuti

Compressione statica

L'idea alla base della compressione statica è fare in modo che gli asset vengano compressi e salvati in anticipo.

Vantaggi

  • La latenza dovuta a livelli di compressione elevati non è più un problema. Non è necessario che ciò avvenga sul momento per comprimere i file, dato che ora possono essere recuperati direttamente.

Svantaggi

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

Compressione statica con Node/Express e webpack

Poiché la compressione statica prevede la compressione anticipata dei file, le impostazioni del webpack possono essere modificate per comprimere gli asset nell'ambito del passaggio di creazione. Per farlo, puoi usare CompressionPlugin.

Inizia aggiungendolo 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 build utilizzando gzip. Consulta la documentazione per scoprire come aggiungere opzioni per utilizzare un algoritmo diverso o includere/escludere determinati file.

Quando l'app si ricarica e ricrea, viene creata una versione compressa del bundle principale. Apri la console Glitch per dare un'occhiata ai contenuti della directory public/ finale gestita dal server nodo.

  • 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 di output finali nella directory pubblica

La versione compressa del bundle, main.bundle.js.gz, viene ora salvata qui. Per impostazione predefinita, CompressionPlugin comprime anche index.html.

Successivamente, comunica al server di inviare questi file 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:

  • Se specifichi '*.js' come primo argomento, questo 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() assicura che la sequenza continui a ogni callback successivo.

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

Riduzione delle dimensioni del bundle con la compressione statica

Come in passato, abbiamo ridotto notevolmente le dimensioni del bundle.

Conclusione

Questo codelab ha trattato il processo di minimizzazione e compressione del codice sorgente. Entrambe queste tecniche stanno diventando un valore predefinito in molti degli strumenti attualmente disponibili, quindi è importante scoprire se la toolchain le supporta già o se è opportuno iniziare ad applicare entrambi i processi autonomamente.