- Comme GitHub Pages ne prend pas en charge Brotli, il s’agit d’une expérimentation consistant à encoder du HTML en image WebP sans perte, puis à le restaurer via le décodeur d’images du navigateur et JavaScript afin de réduire le volume transféré
- Les décodeurs Brotli en WASM ajoutent un surcoût de
71 KiB à 200 KB, et la Compression Streams API ne prend en charge quegzip,deflateetdeflate-raw, ce qui en fait difficilement une voie de contournement pour le décodage Brotli - Le WebP sans perte VP8L peut produire, même sur des données textuelles, de meilleurs résultats que gzip grâce à sa transformation prédictive, à la réutilisation d’arbres Huffman par blocs de 16x16, et à son color cache
- Un HTML de test de
439,478octets a été réduit à94,683octets avec gzip et jusqu’à43,182octets avec WebP ; c’est plus gros que les37 KiBde Brotli, mais environ 2,2 fois plus petit que gzip - À cause du bruit anti-fingerprinting de Canvas 2D, des changements sur le
readPixelsde WebGL, de l’écran vide initial et des problèmes de restauration du scroll, cela ressemble davantage à un hack révélant les contraintes des navigateurs qu’à une solution réellement applicable
Le problème : impossible d’utiliser Brotli sur GitHub Pages
- Pour réduire le temps de chargement d’une page, la compression HTTP a plus d’effet qu’un simple minify du HTML
- HTTP prend en charge gzip et Brotli via l’en-tête
Content-Encoding- gzip est peu coûteux et souvent activé par défaut
- Brotli compresse généralement mieux que gzip, mais il est nettement plus lent
- GitHub Pages ne prend pas en charge Brotli ; l’article le plus long du site,
Recovering garbled Bitcoin addresses, ferait37 KiBen Brotli, mais monte à92 KiBen gzip - Cet écart allonge inutilement le temps de chargement d’un facteur 2,5
Les premières alternatives envisagées, et leurs limites
- Le problème serait évité si GitHub permettait d’uploader et de servir des fichiers Brotli précompressés, mais cette fonctionnalité n’existe pas
- Décompresser côté client directement en JavaScript réduit fortement l’intérêt à cause de la taille du décodeur WASM
- brotli-dec-wasm fait environ
200 KB - tiny-brotli-dec-wasm fait
71 KiB - La comparaison devient donc gzip
92 KiBcontre contenu Brotli37 KiB + 71 KiB, ce qui annule pratiquement le gain
- brotli-dec-wasm fait environ
- La pile HTTP du navigateur contient bien un décodeur Brotli, mais DecompressionStream de la Compression Streams API n’accepte que
gzip,deflateetdeflate-raw - Même un
gzipprécompressé avec Zopfli reste à86 KiB, donc plus gros que Brotli
Utiliser un format d’image comme conteneur de compression
- Les navigateurs savent déjà décoder les images ; il suffit donc d’insérer les données dans des pixels, puis de les relire via l’API Canvas, sans avoir à ajouter une nouvelle logique de décompression
- GIF déroule les données en ordre row-major puis applique LZW, mais DEFLATE de gzip a justement été conçu pour remplacer LZW, donc il est difficile d’en attendre un bénéfice
- PNG utilise DEFLATE, mais applique d’abord une transformation prédictive qui compresse non pas les pixels bruts, mais leurs différences avec les pixels voisins
- Exemple : au lieu de compresser
[a, b, c, d], on compresse[a, b-a, c-b, d-c] - Plus l’écart entre valeur prédite et valeur réelle est faible, plus la compression Huffman est efficace
- Exemple : au lieu de compresser
- Le cœur de l’expérimentation consiste à détourner le WebP sans perte, VP8L, pour compresser des données binaires générales
Ce qui différencie VP8L de gzip
- WebP existe en variantes avec perte et sans perte ; ici, seul le format sans perte VP8L est concerné
- Comme PNG, VP8L utilise une transformation prédictive, mais remplace DEFLATE par un algorithme proche conçu par Google
- DEFLATE découpe un fichier en plusieurs blocs, chacun pouvant avoir son propre arbre Huffman
- Dans un HTML qui mêle JavaScript, SVG et balisage, différents arbres peuvent être avantageux
- VP8L permet de définir une table d’arbres Huffman arbitrairement grande et d’utiliser un arbre différent pour chaque bloc de 16x16 pixels
- Si du JavaScript est suivi de CSS puis à nouveau de JavaScript, DEFLATE peut devoir réencoder plusieurs fois des arbres similaires
- VP8L permet de réutiliser ces arbres, et donc de les changer plus souvent, à moindre coût
- Le color cache de VP8L permet aussi de représenter brièvement une valeur en disant, en substance, de recopier un pixel récent ayant certaines propriétés
Première expérimentation de compression en WebP
- Le fichier de test est le HTML de
Recovering garbled Bitcoin addresses- Taille d’origine :
439,478octets gzip --best:94,683octets
- Taille d’origine :
- Les octets ont été transformés en image RGB en niveaux de gris puis compressés en WebP sans perte à l’aide de la crate Rust webp
- Le niveau de gris a été choisi à cause de la transformation WebP
subtract green- En niveaux de gris, si l’on soustrait le canal G aux canaux R/B, R/B deviennent pratiquement nuls
- WebP encode les trois canaux avec des arbres Huffman séparés, donc les canaux constants occupent un espace proche de
O(1)
- L’idée initiale était de créer une image
1xN, mais WebP ne prend en charge que jusqu’à16383x16383, ce qui a provoqué l’erreurVP8_ENC_ERROR_BAD_DIMENSION - En passant à une forme
16383xN, le résultat est descendu à45,604octets, soit deux fois plus petit que gzip et même plus petit que bzip2 à49,764octets
Ajustements spécifiques à WebP
- Sur une image large en ordre row-major, des octets éloignés dans l’entrée se retrouvent mélangés au sein d’un même bloc 16x16
- En changeant la forme de l’image pour une version verticale
27x16383, le résultat est tombé à43,232octets - Le paramètre de compression method de
cwebpa été comparé de0à6- method 0 :
48,902 - method 1 :
43,546 - method 2 :
43,442 - method 3 :
43,292 - method 4 :
43,232 - method 5 :
43,182 - method 6 :
43,182
- method 0 :
- La méthode
5a été retenue, car elle produit la même taille que la méthode6tout en étant plus rapide - À ce stade, WebP est 2,2 fois plus petit que gzip, mais 1,2 fois plus gros que Brotli
Benchmarks sur plusieurs fichiers
- Les jeux de comparaison comprenaient les snappy testdata, le Canterbury Corpus et Large Corpus, ainsi que deux fichiers SVG
- Les formats comparés étaient
gzip --best,brotli --best,bzip2 --bestet le script de compression WebP - À part quelques très petits fichiers comme
grammar.lsp,xargs.1et quelques exceptions, WebP faisait presque toujours mieux que gzip - Les exceptions étaient
kennedy.xlsetpaper-100k.pdfpaper-100k.pdfcontient19 KBde XML suivis de données déjà compressées ; la mesure revient donc essentiellement à tester un petit volume de donnéeskennedy.xlsprésente aussi un comportement inhabituel vis-à-vis de Brotli et bzip2 ; il peut s’agir d’un fichier difficile à compresser parce qu’il mélange, à courte distance, des données très hétérogènes
- WebP est généralement un peu moins bon que bzip2, mais le dépasse dans certains cas
- WebP est toujours moins bon que Brotli, sauf sur quelques cas extrêmes comme
fireworks.jpeg, proche d’un blob aléatoire presque uniforme - Sur de gros jeux de données en texte brut, l’amélioration par rapport à gzip reste mesurable
- Il y avait aussi un gain sur les fichiers SVG
- Sur
html_x_4, WebP atteignait3.3%, moins bon que les2.8%de Brotli, mais bien meilleur que les13%de gzip
Restaurer le contenu avec JavaScript
- Le décodage WebP lui-même peut être mis en œuvre avec
fetch,createImageBitmap,OffscreenCanvas,getImageDataetTextDecoder - Le principe consiste à prendre le canal R des pixels comme octets HTML d’origine, à les décoder en UTF-8, puis à les injecter dans
document.documentElement.innerHTML - L’API Canvas est souvent utilisée pour le fingerprinting, donc certains navigateurs ajoutent du bruit au résultat de
getImageData- Avec la protection stricte contre le pistage de Firefox, moins de 1 % des pixels peuvent être touchés
- Dans du HTML, ce bruit apparaît comme des fautes de frappe
- En utilisant
readPixelsde WebGL, cela fonctionnait alors sans bruit- WebGL ne garantit toutefois la prise en charge stable des textures que jusqu’à
2048x2048, ce qui impose à nouveau des contraintes de taille - Une fois minifié, ce code de restauration faisait environ
550octets
- WebGL ne garantit toutefois la prise en charge stable des textures que jusqu’à
- WebP et le code réunis totalisaient
44 KiB, à comparer aux92 KiBde gzip et aux37 KiBde Brotli - Après le 15 avril 2026, Firefox a ajouté l’anti-fingerprinting à
readPixelsaussi, de sorte que cette méthode ne fonctionne plus telle quelle
Clignotement de l’écran et problème de scroll
awaitrepose sur des promesses ; avant la fin du téléchargement du WebP, le navigateur considère donc que l’exécution du script est terminée- Comme le DOM est encore vide, l’utilisateur voit brièvement un écran blanc vide
- Pour atténuer cela, on peut conserver dans le HTML gzip les styles et environ
8 KiBdu haut de page, et ne compresser en WebP que le contenu situé sous le viewport - La restauration de la position de scroll au rechargement pose aussi problème
- Par exemple, si l’on recharge à la position
Y = 5000pxalors que la page mesure0pxde hauteur, cette position est réinitialisée - Ajouter un très grand
divtemporaire peut aider
- Par exemple, si l’on recharge à la position
- Il faut affecter
document.documentElement.innerHTMLplutôt que d’utiliserdocument.write, afin de mettre à jour le document courant sans le remplacer par un nouveau
Intégrer directement le WebP dans le JavaScript
- Pour réduire encore un peu la latence, le WebP peut être embarqué directement dans le JavaScript
- La méthode la plus simple est une data URL en base64
- La base64 augmente la taille d’origine d’un facteur
1.33, mais gzip compense presque entièrement cette hausse- Converti en base64,
compressed.webpfait57,576octets - Compressé ensuite avec
gzip --best, cela donne43,519octets
- Converti en base64,
- Un blob compressé comme un WebP ressemble presque à des données aléatoires uniformes, et la transformation 8 bits vers 6 bits de la base64 permet à l’arbre Huffman de gzip d’agir pratiquement comme une transformation inverse
- On pourrait aussi utiliser Unicode et UTF-16, mais la base64 reste une première solution suffisante
Mise en pratique et état actuel
- Au moment de la rédaction, cette page elle-même était compressée en WebP à partir de la section « Fool me twice », sauf sur les anciens navigateurs ou lorsque JavaScript était désactivé
- Dans le code réel, l’image WebP de la page était longue et étroite, mais un exemple plus visuel sous forme de WebP carré était aussi fourni
- Dans cette image, les zones claires en haut et en bas correspondaient au texte et au code, la zone hachurée vers un cinquième de la hauteur à un diagramme, et la plupart des zones sombres au texte présent dans ce diagramme
- Le gain réel restait limité
- Page gzip d’origine :
88 KiB - Page gzip après application de WebP :
83 KiB - Estimation avec Brotli :
69 KiB
- Page gzip d’origine :
- Après le 15 avril 2026, la page est revenue à une version non compressée afin d’éviter que les visiteurs sur Firefox ne voient du contenu corrompu
- Le code Rust, le corpus et les autres fichiers sont publiés sur GitHub
1 commentaires
Avis de Hacker News
Si l’on ignore la latence, sans doute, mais en pratique le temps de chargement semble n’augmenter que d’environ 0,001 %
Le surcroît de taille est négligeable par rapport à la latence aller-retour, et le temps passé à décompresser pourrait même être supérieur au temps économisé en transférant 55 Kio de moins
C’est une expérience intéressante, mais dans ce cas l’expérience utilisateur risque plutôt de se dégrader, avec une vitesse presque identique et seulement une compatibilité moindre
Le problème, c’est qu’il y a des gens comme l’auteur de l’article qui s’efforcent de faire passer 100 Ko à 50 Ko, mais aussi des sites qui m’envoient sans sourciller des dizaines de Mo d’images alors que je veux juste consulter les horaires d’ouverture d’un restaurant en roaming
La conscience des ressources existe, mais elle est malheureusement très inégalement répartie
Si la connexion se coupe, on perd tout, et il devient même impossible de lire ne serait-ce que la partie téléchargée
Sur une connexion normale, la différence n’a pas d’importance ; sur une connexion très lente ou instable, au point que 50 Ko comptent, cette méthode est clairement pire. Expérience amusante, mais j’aimerais qu’on ne l’applique pas à un site
Symbols-2048-em%20Nerd%20Font%20Complete.woff2de 850 Kio, ce qui efface presque la différenceCe ne sera pas un facteur 2,5, mais ce n’est pas 0,001 % non plus
Sur un réseau avec pertes, cela pourrait faire une différence, mais je n’en suis pas certain
Je ne comprends pas pourquoi
readPixelsn’est pas visé par les protections anti-empreinte. Ça me va, puisqu’on ne dissémine pas sur toute la page des fautes de frappe presque invisiblesLe passage expliquant que le HTML gzipé ne contient que les styles et environ 8 Kio du haut de page, et que seul le contenu sous le viewport est compressé en WebP, explique pourquoi l’article se coupait soudainement après une phrase quelconque avant d’afficher une page blanche
J’utilise LibreWolf, donc WebGL est désactivé, et j’utilise Chromium pour les jeux web aléatoires qui nécessitent WebGL. En activant WebGL, l’article fonctionnait bien et, franchement, c’est une technique assez élégante
Le WWW devrait rester universellement accessible en partant de HTML ordinaire et en s’améliorant progressivement
Il est certes possible d’utiliser Brotli directement dans un navigateur web, mais avec des contraintes évidentes
Je pense que la soumission JS1024 de 2022 [1] a été la première démonstration de ce concept, et il existe aussi un code de preuve de concept pour de la compression arbitraire. Malheureusement, cela ne convenait pas au codage de taille qui était l’objectif initial
La contrainte principale est qu’on est de fait limité aux caractères ASCII et, pour des raisons évidentes, que c’est très sensible à la pile de rendu. Pour l’instant, cela ne semble pas fonctionner dans Firefox
[1] https://js1024.fun/demos/2022/18/readme
[2] https://gist.github.com/lifthrasiir/1c7f9c5a421ad39c1af19a9c...
On ne peut utiliser que le format de fichier de polices WOFF2, pour lequel Brotli a été conçu à l’origine, mais pour en tirer parti il faut créer un fichier de police complet
Les navigateurs récents nettoient généralement les polices avec OpenType Sanitizer (OTS), car placer directement dans le système des fichiers de polices non fiables est très dangereux ; il faut donc créer un fichier WOFF2 suffisamment valide pour être accepté par OTS, tout en contenant à l’intérieur la suite d’octets voulue et en permettant de l’extraire
Après de nombreux échecs, l’idée de se rabattre sur les largeurs de glyphes, c’est-à-dire les advances, encodées comme une suite d’entiers signés sur 2 octets presque sans contraintes, est fantastique
Chromium l’a longtemps bloqué, mais zstd arrive désormais aussi sur le Web. Maintenant qu’il est enfin dans Chrome, il ne reste plus qu’à Safari de suivre
Je développe Batch Compress (https://batchcompress.com/en) et, peu après avoir récemment ajouté la prise en charge de WebP, je l’ai défini comme format par défaut
À ma connaissance, l’outil produisait déjà les JPEG les plus petits parmi les outils de compression web, mais WebP ne faisait qu’environ 50 % de la taille du JPEG. Le passer par défaut peu après l’ajout de la prise en charge a donc été une décision facile
Comme le site compte pas mal d’utilisateurs, je m’attendais à quelques plaintes après avoir fait de WebP le format par défaut, mais un mois plus tard environ, il n’y a eu qu’une seule question ou plainte liée à WebP
Désormais, presque tous les outils et navigateurs semblent prendre en charge WebP. Je n’ai vu récemment qu’un seul site web où l’étape suivante était bloquée parce qu’il ne gérait pas correctement l’upload d’images WebP ; aujourd’hui, presque tout le monde le prend bien en charge
Avec un JPEG bien compressé et optimisé, on ne devrait pas être très loin de WebP
On peut toujours créer un WebP qui paraît presque identique à un JPEG et réduire la taille du fichier, mais on peut faire la même chose en recompressant vers un JPEG qui paraît presque identique
C’est une propriété de tous les codecs de compression avec perte et, comme la taille des fichiers augmente de façon exponentielle à mesure que la qualité monte, les gens sont toujours surpris de voir qu’une minuscule baisse de qualité presque invisible peut changer énormément la taille du fichier
WebP avait mauvaise réputation à cause de paramètres par défaut catastrophiques qui massacraient les détails dans les zones sombres
En regardant le source, j’ai remarqué qu’il manquait un espace dans la déclaration doctype. La forme actuelle est incorrecte et devrait contenir un espace
Omettre l’espace dans
DOCTYPEpermet de raccourcir encoreÀ strictement parler, ce n’est pas du HTML valide, mais cela déclenche quand même correctement le mode standard
Référence : https://GitHub.com/kangax/html-minifier/pull/970 / https://HTML.spec.WHATWG.org/multipage/parsing.html#parse-er...
J’utilise aussi cette astuce sur https://FreeSolitaire.win
On dirait le résultat d’un minifier qui supprime le maximum possible [0]
0 : https://github.com/KTibow/KTibow/issues/3#issuecomment-23367...
J’ai déjà utilisé cette astuce par le passé. Curieusement, je ne me souviens plus à quoi elle m’a servi, mais je crois que c’était probablement pour voir si c’était possible, et j’avais aussi laissé un commentaire ici : https://gist.github.com/gasman/2560551?permalink_comment_id=...
J’ai aussi retrouvé un vieux prototype, qui semble n’avoir été qu’un test : https://retr0.id/stuff/bee_movie.webp.html
Aujourd’hui, ce genre de choses qui injectent des scripts s’appellent des extensions plutôt que des add-ons, mais en tout cas l’approche consistant à transmettre d’abord des « déchets », puis à ajouter du JS derrière pour le renvoyer vers la page, est intéressante
Le passionné de sécurité en moi se demande si cela pourrait devenir exploitable avec des données fournies par l’utilisateur, comme un formulaire de commentaires
Peut-être que quelqu’un pourrait trouver une séquence d’octets à mettre dans un commentaire qui, après compression, se transformerait en balise
scriptplacée avant mon script et exécutéeLa majeure partie de ce que le WebP sans perte apporte par rapport au PNG relève de la modélisation plutôt que de l’encodage, et ce type de compression de texte n’utilise que la partie encodage de WebP
Retirer Google Fonts améliorerait aussi un peu le temps de chargement de la page. Elles sont chargées depuis un serveur distant et nécessitent une poignée de main supplémentaire
Cette page est cassée au moins dans le navigateur de Sailfish OS. Il y a un long espace vide après le paragraphe suivant
“Alright, so we’re dealing with 92 KiB for gzip vs 37 + 71 KiB for Brotli. Umm…”
Cela dit, le surcoût de la compression HTML avec gzip et Brotli n’est rien comparé à la quantité de JS, d’images et de vidéos utilisée par les sites web modernes
Personnellement, je n’aime pas beaucoup ce format. Quand j’enregistre une image et qu’elle est enregistrée en WebP, rien ne le prend en charge à part les navigateurs web, donc je dois la convertir avant de pouvoir la modifier ou l’utiliser de façon utile
J’ai juste l’impression qu’on m’impose une étape supplémentaire
Cela dit, si la prise en charge continue de s’étendre, ça ira. Je peux supporter qu’un nouveau format apparaisse tous les 20 ans
.webmpeut disparaître, en revanche