Utiliser WebP pour gagner en performances

WebP est un format d’image développé par Google spécialement pour le web et sorti en 2010. Comme il propose à la fois des compressions avec et sans perte de qualité, il est tout indiqué pour se substituer aussi bien au png qu’au jpeg.

Jusqu’alors, seul Google Chrome supportait WebP. Malgré l’importante part de marché du navigateur, cela restait limité. Cependant, à partir de 2019, Firefox et Edge vont à leur tour ajouter le support des images WebP. On pourra donc réellement parler d’un standard.

Dès lors, comme il autorise des fichiers de 20 à 80% plus légers, voyons comment convertir et servir nos images afin de proposer une expérience plus rapide à nos visiteurs – tout en leur faisant économiser de la bande passante.

Convertir nos images

Certains éditeurs d’images permettent déjà l’export en WebP. Nous ne nous attarderons pas la dessus car nul besoin d’explication pour cliquer sur un bouton.

cwebp, la lib officielle

Le plus basique est d’utiliser la bibliothèque officielle en ligne de commande. Elle s’installe aussi bien sur Linux que Mac ou PC.

Une fois cette dernière installée, depuis un shell bash, une petite boucle permettra de convertir toutes les images d’un coup.

# depuis le dossier images
for f in *.jpg; do cwebp $f -o "$f".webp; done

# ou bien via find
find . \( -name '*.jpg' -o -name '*.jpeg' -o -name '*.png' \)` -exec cwebp '{}' -o '{}'.webp' \;

Les réglages par défaut conviennent dans la plupart des cas. Si vous voulez faire du lossless, changer de profil de couleur etc, lisez la doc.

imagemin, the node way

Évidemment, comme nous traitons d’un sujet assez lié au front, il serait malvenu de ne pas aborder le flux de travail du développeur frontend.

En général, ce dernier repose sur npm. On peut trouver différents niveaux d’abstraction, gulp, webpack etc, mais on a toujours npm comme base.

Dès lors, on peut tirer parti de imagemin. Cette bibliothèque propose d’optimiser les images : jpeg, png, svg, webp, tout est là. De plus, il est possible d’interagir en JavaScript – parfait dans une config webpack – ou directement en CLI – parfait pour des scripts npm.

Si l’on utilise imagemin directement en CLI, le principal avantage face à la commande cwebp réside dans le fait qu’il n’est pas requis d’avoir les outils webp installés sur le système. Il suffit d’avoir imagemin et les plugins requis en dépendances du projet.

Comme je viens de le mentionner, imagemin possède un système de plugins. Cela permet de n’ajouter que le nécessaire. Vous voulez compresser vos jpeg en webp tout en optimisant les originaux avec mozjpeg ? Vous installez ces deux là.

npm install --save-dev imagemin-cli imagemin-webp

Ensuite, vous n’avez qu’à ajouter une tâche dans vos scripts du package.json.

{
  …
  "scripts": {
    "compress:jpeg": "for f in img/*.jpg; do imagemin -p=webp $f > $f.webp; done;",
    …
}

Cette méthode est la plus simple et la plus concise. Toutefois, il ne récupère les images que dans un seul dossier et toutes doivent avoir jpg en extension et non jpeg.

Il est possible d’utiliser find pour plus de souplesse.

"find . \\( -name '*.jpg' -o -name '*.jpeg' -o -name '*.png' \\) -exec bash -c 'imagemin -p=webp $1 > $1.webp' _ {} \\;",

On utilise bash -c car sans cela, après la redirection >, il nous est impossible d’accéder au contenu de {}. Nous obtiendrions la valeur {} littéralement. Si le pourquoi du comment vous intéresse, j’avais justement posé la question sur StackExchange [en].

Servir les fichiers WebP

Ici encore, il existe plusieurs manières de faire. Le plus simple est de donner le choix au navigateur en utilisant l’élément picture de HTML5.

L’autre approche se fait côté serveur avec de la réécriture. Nous allons voir comment faire cela avec Apache et Nginx.

Client side

Tel qu’expliqué dans mon article sur les images responsives, voici comment le HTML5 permet de nativement proposer plusieurs formats.

<picture>
  <source
    srcset="buzut.webp"
    type="image/webp">

    <img src="buzut.jpg" alt="Buzut AFK IRL">
</picture>

Si on part sur cette approche, il ne nous est nativement pas possible d’utiliser le WebP en CSS. Il faudra pour cela utiliser Modernizr.

Server side

Nous l’avons dit, la seconde approche consiste à servir différents types de fichiers aux navigateurs. La plupart des navigateurs envoient un en-tête Accept indiquant les formats qu’ils acceptent dans la réponse. Ainsi, selon le contenu de cet en-tête, il sera possible de faire varier le format de la réponse.

C’est déjà ce que l’on fait avec la compression et l’en-tête Accept-Encoding. De plus, sur le web, les extensions de fichiers n’ont aucune signification. On peut donc très bien avoir background.jpg et envoyer du WebP à la place, cela ne posera aucun problème. L’important est d’avoir le bon content-type dans l’en-tête de réponse !

Vous avez peut-être noté que dans notre processus d’encodage, nos fichiers images WebP prennent la forme de image-source.jpg.web. Pourquoi ne pas directement mettre webp comme extension en enlevant complètement jpeg ?

Simplement car de cette manière, la substitution via la réécriture depuis le serveur web est très facile. Exemple avec Apache, directement depuis le VHost ou depuis le .htaccess.

En plus de servir le WebP, nous allons ajouter l’en-tête vary: Accept-Encoding afin de signifier aux clients, mais surtout aux serveurs mandataires et proxys cache, qu’il existe des variantes de ce fichier selon ce le support du client tel qu’annoncé dans Accept-Encoding

# on précise ici que les fichiers .webp ont pour type MIME image/webp
<Files *.webp>
    Header set Vary "Accept-Encoding";
    AddType "image/webp" .webp
    AddEncoding webp .webp
</Files>

# on active la réécriture si ce n'est pas déjà fait
RewriteEngine on
RewriteBase /

# pour les navigateurs qui précisent supporter webp
# si l'équivalent du fichier existe en webp, on le sert à la place
RewriteCond %{HTTP:Accept} image/webp
RewriteCond %{REQUEST_FILENAME}.webp -f
RewriteRule ^(.*)$ $1.webp [L]

Faisons maintenant la même chose pour Nginx.

# si le navigateur accepte webp
map $http_accept $webp_suffix {
    webp_suffix "";
    "~*webp" ".webp";
}

# on envoie le webp
location ~ /*.(gif|jpg|jpeg|png).webp {
    add_header Vary "Accept-Encoding";
    try_files $uri$webp_suffix $uri =404;
}

Et voilà, quelle que soit votre pile technique, vous êtes maintenant parés pour servir des images au format WebP pour ceux qui sont capables de les lire. Sachant la part du poids que représentent les images dans une page, vos visiteurs ne manqueront pas d’apprécier le gain en performance !

Déjà 4 réponses, rejoignez la discussion !

    • Buzut

      dit :

      Comme souvent, Apple fait de la résistance et pour le moment, aucune annonce n’a été faite à ce sujet. Donc les utilisateurs de Safari devront se contenter de jpeg ou png.

  • Thomas

    dit :

    Bonjour,

    pour la réécriture conditionnelle avec Nginx, il est préférable d’utiliser la directives map pour associer la variable $http_accept et try_files pour tester l’existence du fichier .webp.

    Ce qui donne :

    map $http_accept $webp_suffix {
        default "";
        "~*webp" ".webp";
    }
    

    Et dans le bloc server {} :

    location ~ \.(png|jpe?g)$ {
        add_header Vary "Accept-Encoding";
        add_header "Access-Control-Allow-Origin" "*";
        try_files $uri$webp_suffix $uri =404;
    }
    

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *