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

Cet atelier de programmation explique comment la minimisation 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 de vous lancer dans l'ajout d'optimisations, il est toujours judicieux d'analyser d'abord l'état actuel de l'application.

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

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

Voyons 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 groupe d'origine dans le panneau "Réseau"

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 reste assez volumineux.

Réduction

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 son propre fichier, la taille du fichier est d'environ 112 B (octets).

Si tous les espaces blancs 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 maintenant d'environ 83 octets. S'il est encore tronqué en réduisant la longueur du nom de variable et en modifiant certaines expressions, le code final peut se présenter comme suit:

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

La taille du fichier atteint maintenant 62 octets.

À chaque étape, le code devient plus difficile à lire. Cependant, le moteur JavaScript du navigateur interprète chacun de ces éléments de la même manière. L'avantage d'obscurcir le code de cette manière peut aider à réduire la taille des fichiers. 112 milliards, ce n'était vraiment pas grand-chose au départ, mais il y avait encore une réduction de 50 % de la taille !

Dans cette application, la version 4 de webpack est utilisée en tant que bundler de modules. La version spécifique est visible dans package.json.

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

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

Pour vous faire une idée du code réduit, cliquez sur main.bundle.js dans le panneau Network (Réseau) des outils de développement. Cliquez maintenant sur l'onglet Response (Réponse).

Réponse réduite

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

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

Actualisez l'application et examinez à nouveau la taille du bundle via le panneau Network (Réseau) de DevTools.

Taille du fichier : 767 Ko

C’est une assez grande différence ! 😅

Veillez à annuler les modifications ici avant de continuer.

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

L'inclusion d'un processus permettant de réduire la taille du code dans votre application dépend des outils que vous utilisez:

  • Si vous utilisez webpack v4 ou une version ultérieure, aucune tâche supplémentaire n'est nécessaire, 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. Pour en savoir plus à ce sujet, consultez la documentation.
  • D'autres plug-ins de minimisation existent et peuvent être utilisés à la place, tels que BabelMinifyWebpackPlugin et ClosureCompilerPlugin.
  • Si un bundler de module n'est pas utilisé du tout, utilisez Terser en tant qu'outil CLI ou incluez-le directement en tant que dépendance.

Compression

Bien que le terme "compression" soit parfois utilisé de manière approximative pour expliquer comment réduire le code au cours du processus de minimisation, il n'est pas compressé au sens littéral.

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

Avec chaque requête et réponse HTTP, les navigateurs et les serveurs Web peuvent ajouter des headers pour inclure des informations supplémentaires sur l'élément récupéré ou reçu. Vous pouvez le constater dans l'onglet Headers du panneau DevTools Network, où trois types sont présentés:

  • Général représente des en-têtes généraux pertinents pour l'ensemble de l'interaction requête-réponse.
  • Le tableau de bord Response Headers affiche une liste d'en-têtes spécifiques à la réponse réelle du serveur.
  • Le tableau de bord Request Headers (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.

Accepter l&#39;en-tête d&#39;encodage

accept-encoding permet au navigateur de spécifier les formats d'encodage de contenu ou les algorithmes de compression acceptés. Il existe de nombreux algorithmes de compression de texte, mais seuls trois sont pris en charge ici pour la compression (et la décompression) des requêtes réseau HTTP:

  • Gzip (gzip): format de compression le plus utilisé pour les interactions avec le serveur et le client. Il s'appuie sur l'algorithme Deflate et est compatible avec tous les navigateurs actuels.
  • Décalage (deflate): peu utilisé.
  • Brotli (br): algorithme de compression plus récent visant à améliorer davantage les taux de compression, ce qui peut accélérer le chargement des pages. Elle est compatible avec les dernières versions de la plupart des navigateurs.

L'exemple d'application présenté dans ce tutoriel est identique à l'application réalisée dans l'atelier de programmation Supprimer le code inutilisé, à la différence près qu'Express est désormais utilisé comme framework de serveur. Les sections suivantes traitent de la compression statique et dynamique.

Compression dynamique

La compression dynamique implique la compression des éléments à la volée, à mesure qu'ils sont demandés par le navigateur.

Avantages

  • Il n'est pas nécessaire de créer et de mettre à jour des versions compressées et enregistrées des éléments.
  • La compression à la volée fonctionne particulièrement bien pour les pages Web générées dynamiquement.

Inconvénients

  • La compression des fichiers à des niveaux supérieurs pour obtenir de meilleurs taux de compression prend plus de temps. Cela peut entraîner une perte de performances, car l'utilisateur attend 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 responsable de la configuration du serveur de nœuds 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);
});

Actuellement, vous importez express et vous utilisez le middleware express.static pour charger tous les fichiers HTML statiques, JS et CSS du répertoire public/ (ces fichiers sont créés par webpack à chaque build).

Pour vous assurer que tous les éléments sont compressés à chaque requête, vous pouvez utiliser la bibliothèque de middleware de compression. Commencez par l'ajouter en tant que devDependency dans package.json:

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

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

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

Ajoutez-le en tant que middleware avant l'installation de express.static:

//...

const app = express();

app.use(compression());

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

//...

À présent, actualisez l'application et vérifiez la taille du bundle dans le panneau Network (Réseau).

Taille du bundle avec compression dynamique

De 225 Ko à 61,6 Ko ! Dans le fichier 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 des éléments et de gagner du temps.

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 build. La durée de compilation peut considérablement augmenter si des niveaux de compression élevés sont utilisés.

Compression statique avec Node/Express et Webpack

Étant donné que la compression statique implique la compression de fichiers à l'avance, les paramètres du pack Web peuvent être modifiés pour compresser les éléments dans le cadre de l'étape de compilation. Pour ce faire, vous pouvez utiliser CompressionPlugin.

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

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

Comme pour 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");

Et 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 afin d'utiliser un autre algorithme, ou d'inclure ou d'exclure certains fichiers.

Lorsque l'application est actualisée et recréée, une version compressée du bundle principal est maintenant créée. Ouvrez la console Glitch pour examiner le contenu du répertoire public/ final 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 finaux générés dans le répertoire public

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

L'étape suivante consiste à demander au serveur d'envoyer ces fichiers compressés avec gzip 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 permet d'indiquer au serveur comment répondre à une requête GET pour un point de terminaison spécifique. Une fonction de rappel permet ensuite de définir comment gérer cette requête. L'itinéraire fonctionne comme suit:

  • Si vous spécifiez '*.js' comme premier argument, cela fonctionne pour chaque point de terminaison déclenché pour extraire 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 peut être effectué ensuite.

Une fois l'application actualisée, consultez à nouveau le panneau Network.

Réduction de la taille des bundles avec compression statique

Comme auparavant, la taille du bundle est considérablement réduite.

Conclusion

Cet atelier de programmation a abordé le processus de réduction de la taille et de compression du code source. Ces deux techniques deviennent une valeur par défaut dans de nombreux outils disponibles actuellement. Il est donc important de savoir si votre chaîne d'outils les prend déjà en charge ou si vous devez commencer à appliquer les deux processus vous-même.