Ce qui se passe
La quasi-totalité des convertisseurs WebP qui tournent dans le navigateur reposent sur une ligne :
canvas.toBlob(blob => { /* ... */ }, 'image/webp', quality)Sur Safari, cette ligne rend un PNG. Pas une erreur, pas null : un fichier
parfaitement valide, du mauvais format.
Ce n'est pas un bug. Safari décode le WebP depuis la version 14, mais n'a
jamais su l'encoder depuis un canvas. Or la spécification HTML prévoit que
si le type demandé n'est pas supporté, le navigateur retombe sur
image/png. Si vous avez nommé le téléchargement photo.webp — et pourquoi
ne l'auriez-vous pas fait, vous avez demandé un WebP — l'utilisateur repart avec
un PNG mal étiqueté, et ne l'apprendra que le jour où quelque chose en aval le
refusera.
La seule détection fiable
Il faut comparer ce qu'on a reçu à ce qu'on a demandé :
const blob = await canvas.convertToBlob({ type: mime, quality: quality / 100 })
if (blob.type !== mime) {
throw new Error(
`This browser cannot encode ${format} via canvas. ` +
`Safari does not support it — the WebAssembly encoder is required.`
)
}Une fois admis que le canvas n'est pas fiable pour encoder, la suite s'impose : embarquer le codec soi-même, libwebp compilé en WebAssembly.
Les pannes suivantes sont tout aussi muettes
L'auteur en liste six. Quatre méritent d'être retenues.
Vite a besoin de deux réglages opposés en même temps. Les codecs jSquash
doivent être exclus du pré-bundling — le pré-bundler réécrit leur glue
Emscripten, qui ne retrouve alors plus son fichier .wasm voisin. À l'inverse,
utif2 (TIFF) et libheif-js/wasm-bundle (HEIC) sont du CommonJS et doivent y
être inclus, faute de quoi on récolte un UTIF.decode is not a function à
l'exécution. Le critère qui tranche : le paquet charge-t-il un .wasm séparé à
l'exécution ? Piège supplémentaire, Vite compare le spécificateur exact :
déclarer @jsquash/webp ne couvre pas @jsquash/webp/encode.
Les imports dynamiques dans un worker échappent au scanner de dépendances.
En développement, le paquet reste inconnu jusqu'à la première conversion : Vite
découvre alors une dépendance, ré-optimise en pleine session et force un
rechargement complet. Vu de l'utilisateur, une file de 50 fichiers part à la
poubelle, et la console affiche 504 (Outdated Optimize Dep).
target_size est ignoré tant que pass vaut 1. libwebp atteint un budget
d'octets par analyses d'entropie répétées ; sans passes multiples, il n'y a rien
sur quoi converger. Le réglage est silencieusement sans effet.
Un PNG transparent converti en JPEG ressort noir, des deux côtés. mozjpeg
reçoit le tampon RGBA et ignore un octet sur quatre ; or le canvas donne
0,0,0 aux pixels transparents. Et la spec HTML impose de composer un contexte
avec alpha sur du noir lors de l'encodage vers un format qui n'en a pas.
Aucune des deux voies n'échoue : elles produisent une image abîmée. La parade est
d'aplatir soi-même sur du blanc, comme le font les éditeurs de bureau, avant que
l'encodeur ne voie le tampon — en se rappelant qu'ImageData n'est pas
prémultiplié, et qu'un Uint8ClampedArray arrondit déjà à l'affectation.
À retenir
Le fil rouge n'est pas la difficulté des codecs, c'est le silence. Safari rend
le mauvais format, target_size vous ignore, mozjpeg jette votre canal alpha,
Vite vide votre file d'attente. L'auteur en tire une consigne inverse du réflexe
habituel : un repli qui ment est pire que pas de repli du tout. Le sien
échoue franchement sur Safari plutôt que d'y renvoyer un PNG déguisé.
Source : DEV Community