Réduire et compresser les charges utiles du réseau avec gzip

Cet atelier de programmation explique comment la réduction et la compression du bundle JavaScript pour l'application suivante améliorent les performances des pages en réduisant la taille des requêtes de l'application.

Capture d'écran de l'application

Mesurer

Avant d'ajouter des optimisations, il est toujours judicieux d'analyser l'état actuel de l'application.

  • Pour prévisualiser le site, appuyez sur Afficher l'application, puis sur Plein écran plein écran.

Cette application, qui a également été abordée dans l'atelier de programmation Supprimer le code inutilisé, vous permet de voter pour votre chaton préféré. 🐈

Regardez maintenant la taille de cette application :

  1. Appuyez sur Ctrl+Maj+J (ou Cmd+Option+J sur Mac) pour ouvrir les outils de développement.
  2. Cliquez sur l'onglet Réseau.
  3. Cochez la case Désactiver le cache.
  4. Actualisez l'application.

Taille du bundle d'origine dans le panneau "Network"

Bien que de nombreux progrès aient été réalisés dans l'atelier de programmation Supprimer le code inutilisé pour réduire la taille de ce bundle, 225 Ko restent assez importants.

Minification

Prenons l'exemple du bloc de code suivant.

function soNice() {
  let counter = 0;

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

Si cette fonction est enregistrée dans un fichier distinct, la taille du fichier est d'environ 112 octets.

Si tous les espaces sont supprimés, le code obtenu se présente comme suit :

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

La taille du fichier est désormais d'environ 83 octets. Si le code est encore plus déformé en réduisant la longueur du nom de la variable et en modifiant certaines expressions, le code final peut ressembler à ceci :

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

La taille du fichier atteint désormais 62 octets.

À chaque étape, le code devient plus difficile à lire. Toutefois, le moteur JavaScript du navigateur interprète chacun d'eux exactement de la même manière. L'avantage de l'obscurcissement du code de cette manière est qu'il peut contribuer à réduire la taille des fichiers. 112 octets, ce n'était pas beaucoup au départ, mais la taille a quand même été réduite de 50 % !

Dans cette application, la version 4 de webpack est utilisée comme outil de regroupement de modules. La version spécifique est indiquée dans package.json.

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

La version 4 réduit déjà le bundle par défaut en mode production. Il utilise TerserWebpackPlugin, un plug-in pour Terser. Terser est un outil populaire utilisé pour compresser le code JavaScript.

Pour vous faire une idée de ce à quoi ressemble le code minifié, cliquez sur main.bundle.js dans le panneau Réseau des outils de développement. Cliquez ensuite sur l'onglet Réponse.

Réponse minimisée

Le code dans sa forme finale, minifié et compressé, est affiché dans le corps de la réponse. Pour connaître la taille du bundle s'il n'avait pas été réduit, ouvrez webpack.config.js et mettez à jour la configuration mode.

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

Rechargez l'application et examinez à nouveau la taille du bundle dans le panneau Réseau des outils de développement.

Taille du bundle de 767 Ko

C'est une différence assez importante ! 😅

Veillez à annuler les modifications ici avant de continuer.

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

L'inclusion d'un processus de minification du code dans votre application dépend des outils que vous utilisez :

  • Si webpack v4 ou une version ultérieure est utilisé, aucune autre action n'est requise, car le code est réduit par défaut en mode production. 👍
  • Si une ancienne version de webpack est utilisée, installez et incluez TerserWebpackPlugin dans le processus de compilation webpack. La documentation explique ce point en détail.
  • Il existe également d'autres plug-ins de minimisation qui peuvent être utilisés à la place, tels que BabelMinifyWebpackPlugin et ClosureCompilerPlugin.
  • Si aucun module bundler n'est utilisé, utilisez Terser comme outil CLI ou incluez-le directement comme dépendance.

Compression

Bien que le terme "compression" soit parfois utilisé de manière vague pour expliquer comment le code est réduit lors du processus de minification, il n'est pas réellement compressé au sens littéral du terme.

La compression fait généralement référence à un code qui a été modifié à l'aide d'un algorithme de compression de données. Contrairement à la minification, qui fournit un code parfaitement valide, le code compressé doit être décompressé avant d'être utilisé.

À chaque requête et réponse HTTP, les navigateurs et les serveurs Web peuvent ajouter des en-têtes pour inclure des informations supplémentaires sur le composant récupéré ou reçu. Vous pouvez le voir dans l'onglet Headers du panneau "Réseau" des outils pour les développeurs, où trois types sont affichés :

  • Général représente les en-têtes généraux liés à l'ensemble de l'interaction requête-réponse.
  • En-têtes de réponse affiche une liste d'en-têtes spécifiques à la réponse réelle du serveur.
  • En-têtes de requête affiche la liste des en-têtes associés à la requête par le client.

Examinez l'en-tête accept-encoding dans Request Headers.

En-tête Accept-Encoding

accept-encoding est utilisé par le navigateur pour spécifier les formats d'encodage de contenu ou les algorithmes de compression qu'il prend en charge. Il existe de nombreux algorithmes de compression de texte, mais seuls trois sont compatibles avec la compression (et la décompression) des requêtes réseau HTTP :

  • Gzip (gzip) : format de compression le plus utilisé pour les interactions entre le serveur et le client. Il s'appuie sur l'algorithme Deflate et est compatible avec tous les navigateurs actuels.
  • Deflate (deflate) : pas souvent utilisé.
  • Brotli (br) : algorithme de compression plus récent qui vise à améliorer encore les taux de compression, ce qui peut entraîner des chargements de page encore plus rapides. Il est compatible avec les dernières versions de la plupart des navigateurs.

L'application exemple de ce tutoriel est identique à celle que vous avez créée dans l'atelier de programmation Supprimer le code inutilisé, à la différence près qu'elle utilise désormais Express comme framework de serveur. Dans les sections suivantes, nous allons explorer la compression statique et dynamique.

Compression dynamique

La compression dynamique consiste à compresser les éléments à la volée lorsqu'ils sont demandés par le navigateur.

Avantages

  • Il n'est pas nécessaire de créer ni de mettre à jour les versions compressées enregistrées des composants.
  • La compression à la volée est particulièrement efficace pour les pages Web générées de manière dynamique.

Inconvénients

  • La compression des fichiers à des niveaux plus élevés pour obtenir de meilleurs taux de compression prend plus de temps. Cela peut entraîner une baisse des performances, car l'utilisateur doit attendre que les éléments soient compressés avant d'être envoyés par le serveur.

Compression dynamique avec Node/Express

Le fichier server.js est chargé de configurer le serveur Node qui héberge l'application.

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

Pour l'instant, tout ce que cela fait, c'est importer express et utiliser le middleware express.static pour charger tous les fichiers HTML, JS et CSS statiques dans le répertoire public/ (et ces fichiers sont créés par webpack à chaque compilation).

Pour vous assurer que tous les composants sont compressés chaque fois qu'ils sont demandés, vous pouvez utiliser la bibliothèque de middleware compression. Commencez par l'ajouter en tant que devDependency dans package.json :

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

Importez-le dans le fichier du serveur, server.js :

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

Ajoutez-le en tant qu'intergiciel avant le montage de express.static :

//...

const app = express();

app.use(compression());

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

//...

Actualisez l'application et examinez la taille du bundle dans le panneau Network (Réseau).

Taille du bundle avec compression dynamique

De 225 Ko à 61,6 Ko ! Dans le Response Headers, un en-tête content-encoding indique que le serveur envoie ce fichier encodé avec gzip.

En-tête d&#39;encodage du contenu

Compression statique

L'idée derrière la compression statique est de compresser et d'enregistrer les éléments à l'avance.

Avantages

  • La latence due à des niveaux de compression élevés n'est plus un problème. Il n'est plus nécessaire de compresser les fichiers à la volée, car ils peuvent désormais être récupérés directement.

Inconvénients

  • Les éléments doivent être compressés à chaque compilation. Les temps de compilation peuvent augmenter considérablement si des niveaux de compression élevés sont utilisés.

Compression statique avec Node/Express et webpack

Étant donné que la compression statique implique de compresser les fichiers à l'avance, les paramètres webpack peuvent être modifiés pour compresser les éléments lors de l'étape de compilation. Vous pouvez utiliser CompressionPlugin à cette fin.

Commencez par l'ajouter en tant que devDependency dans package.json :

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

Comme tout autre plug-in Webpack, importez-le dans le fichier de configuration, webpack.config.js:.

const path = require("path");

//...

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

Incluez-le dans le tableau plugins :

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

Par défaut, le plug-in compresse les fichiers de compilation à l'aide de gzip. Consultez la documentation pour découvrir comment ajouter des options permettant d'utiliser un autre algorithme ou d'inclure/exclure certains fichiers.

Lorsque l'application se recharge et se reconstruit, une version compressée du bundle principal est désormais créée. Ouvrez la console Glitch pour voir ce qui se trouve dans le répertoire public/ final qui est diffusé par le serveur Node.

  • Cliquez sur le bouton Outils.
  • Cliquez sur le bouton Console.
  • Dans la console, exécutez les commandes suivantes pour accéder au répertoire public et afficher tous ses fichiers :
cd public
ls

Fichiers de sortie finaux dans le répertoire public

La version compressée du bundle, main.bundle.js.gz, est également enregistrée ici. CompressionPlugin compresse également index.html par défaut.

L'étape suivante consiste à indiquer au serveur d'envoyer ces fichiers compressés chaque fois que leurs versions JS d'origine sont demandées. Pour ce faire, définissez une nouvelle route dans server.js avant que les fichiers ne soient diffusés avec 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 est utilisé pour indiquer au serveur comment répondre à une requête GET pour un point de terminaison spécifique. Une fonction de rappel est ensuite utilisée pour définir comment gérer cette requête. Voici comment fonctionne le parcours :

  • Si vous spécifiez '*.js' comme premier argument, cela signifie que cela fonctionne pour tous les points de terminaison déclenchés pour récupérer un fichier JS.
  • Dans le rappel, .gz est associé à l'URL de la requête et l'en-tête de réponse Content-Encoding est défini sur gzip.
  • Enfin, next() garantit que la séquence se poursuit avec tout rappel qui pourrait suivre.

Une fois l'application rechargée, examinez à nouveau le panneau Network.

Réduire la taille du bundle avec la compression statique

Comme avant, la taille du bundle a été considérablement réduite.

Conclusion

Cet atelier de programmation a abordé le processus de réduction et de compression du code source. Ces deux techniques sont en train de devenir la norme dans de nombreux outils disponibles aujourd'hui. Il est donc important de déterminer si votre chaîne d'outils les prend déjà en charge ou si vous devez commencer à appliquer vous-même les deux processus.