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

Cet atelier de programmation explique comment la minification et la compression du bundle JavaScript de l'application suivante améliorent les performances des pages en réduisant la taille de la requête 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, 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é. 🐈

Voyons maintenant la taille de cette application:

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

Taille du groupe 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 reste une taille assez importante.

Minimisation

Prenons le 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 propre, sa taille 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 maintenant d'environ 83 octets. Si le code est encore tronqué en réduisant la longueur du nom de la 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 désormais 62 octets.

À chaque étape, le code devient plus difficile à lire. Cependant, le moteur JavaScript du navigateur interprète chacun d'eux exactement 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 B n'était pas vraiment beaucoup au départ, mais la taille a tout de 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 visible dans package.json.

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

La version 4 minimise 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 l'apparence du code minifié, cliquez sur main.bundle.js dans le panneau Network (Réseau) des outils de développement. Cliquez ensuite sur l'onglet Réponse.

Réponse minifiée

Le code sous sa forme finale, minifié et tronqué, est affiché dans le corps de la réponse. Pour connaître la taille du bundle s'il n'était pas minifié, 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 dans le panneau Network (Réseau) des outils de développement.

Taille du bundle de 767 Ko

C'est une différence assez importante ! 😅

Assurez-vous d'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 vous utilisez webpack v4 ou une version ultérieure, aucune tâche supplémentaire n'est requise, car le code est minifié par défaut en mode production. 👍
  • Si vous utilisez une ancienne version de webpack, installez et incluez TerserWebpackPlugin dans le processus de compilation webpack. La documentation explique cela en détail.
  • D'autres plug-ins de minification existent et peuvent être utilisés à la place, tels que BabelMinifyWebpackPlugin et ClosureCompilerPlugin.
  • Si aucun outil de regroupement de modules n'est utilisé, utilisez Terser comme outil de ligne de commande ou incluez-le directement en tant que 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.

La compression désigne généralement 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 l'élément extrait ou reçu. Vous pouvez le voir dans l'onglet Headers du panneau "Réseau" de DevTools, où trois types sont affichés:

  • General (Général) représente les en-têtes généraux pertinents pour 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 une liste d'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.
  • Déflation (deflate): n'est pas couramment utilisé.
  • Brotli (br): algorithme de compression plus récent qui vise à améliorer davantage les ratios de compression, ce qui peut entraîner des chargements de pages 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 réalisée dans l'atelier de programmation Supprimer le code inutilisé, à l'exception du fait que Express est désormais utilisé 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 et de mettre à jour des versions compressées enregistrées des composants.
  • La compression instantanée fonctionne particulièrement bien pour les pages Web générées dynamiquement.

Inconvénients

  • La compression des fichiers à des niveaux plus élevés pour obtenir de meilleurs ratios de compression prend plus de temps. Cela peut entraîner une baisse des 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 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);
});

Tout cela n'importe actuellement que express et utilise le middleware express.static pour charger tous les fichiers HTML, JS et CSS statiques du répertoire public/ (ces fichiers sont créés par webpack à chaque compilation).

Pour vous assurer que tous les éléments sont compressés chaque fois qu'ils sont demandés, 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 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 maintenant 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 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 de 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. Rien ne doit se produire à la volée pour compresser les fichiers, 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 de manière significative 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");

et l'inclure 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 pour utiliser un algorithme différent ou inclure/exclure certains fichiers.

Lorsque l'application se recharge et se recompile, une version compressée du bundle principal est désormais créée. Ouvrez la console Glitch pour examiner le contenu du répertoire public/ final servi 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 lot, main.bundle.js.gz, est désormais également enregistrée ici. CompressionPlugin compresse également index.html par défaut.

La prochaine étape consiste à demander au serveur d'envoyer ces fichiers compressés par gzip chaque fois que leurs versions JavaScript d'origine sont demandées. Pour ce faire, définissez un nouveau routage 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 est ensuite utilisée pour définir comment gérer cette requête. Le parcours fonctionne comme suit:

  • Spécifier '*.js' comme premier argument signifie que 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() s'assure que la séquence continue vers tout rappel qui peut être suivant.

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

Réduction de la taille du bundle avec la compression statique

Comme précédemment, la taille du bundle a été 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 la norme dans de nombreux outils disponibles aujourd'hui. 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.