Automatiser l’optimisation JPEG avec jpegoptim dans vos projets
Exécutez jpegoptim sur des copies de vos images dans un répertoire de build. Vous obtenez ainsi des JPEG optimisés à publier, tout en conservant intacts vos originaux et l’index de Git (la zone de staging). Ce tutoriel construit sous Linux un répertoire de JPEG optimisés sans perte et s’arrête avec une erreur si le traitement d’une image échoue.
Installer jpegoptim sur Ubuntu
Il vous faut Bash, GNU coreutils et findutils, ainsi qu’un répertoire existant de ressources JPEG. Le
script ci-dessous utilise des options de realpath propres à GNU ; c’est un exemple pour Linux, pas un script
natif pour macOS ou Windows. Il a été testé avec le paquet jpegoptim 1.4.7 d’Ubuntu 24.04 et avec
jpegoptim 1.5.6 sous Linux.
Sous Ubuntu 24.04, installez le paquet jpegoptim :
sudo apt-get update &&
sudo apt-get install -y jpegoptim &&
jpegoptim --version
Le gestionnaire de paquets installe les bibliothèques d’exécution. Vous n’avez pas besoin de
libjpeg-dev pour exécuter le programme empaqueté.
Prévisualiser une image
Remplacez photo.jpg par un fichier de votre répertoire de ressources :
jpegoptim --noaction --strip-none --nofix -- assets/images/photo.jpg
--noaction indique une taille candidate sans écrire le fichier. Sans --max ni --size,
jpegoptim optimise sans perte le codage du JPEG. Son
manuel distingue cette opération
d’une baisse de la qualité de l’image. Les octets compressés peuvent changer tandis que les pixels
décodés restent identiques. Un résultat skipped peut simplement signifier que le candidat n’était pas plus
petit.
Construire un répertoire séparé de JPEG optimisés
Enregistrez ceci sous optimize-jpegs.sh à la racine de votre projet. Le script copie les fichiers réguliers .jpg et
.jpeg, y compris ceux dont l’extension est en majuscules et ceux des répertoires imbriqués, puis optimise
les copies. Les autres fichiers et les entrées de lien symbolique sont exclus. Ne modifiez pas
l’arborescence source pendant l’exécution du script.
#!/usr/bin/env bash
set -euo pipefail
if (( $# != 2 )); then
printf 'Usage: bash optimize-jpegs.sh SOURCE NEW_OUTPUT\n' >&2
exit 1
fi
command -v jpegoptim >/dev/null || {
printf 'Install jpegoptim first.\n' >&2
exit 1
}
IFS= read -r -d '' source_dir < <(realpath -e -z -- "$1")
[[ -d "$source_dir" ]] || { printf 'Source must be a directory.\n' >&2; exit 1; }
IFS= read -r -d '' output_dir < <(realpath -m -z -- "$2")
if [[ "$source_dir" == / || "$output_dir/" == "$source_dir/"* ]]; then
printf 'Output must be outside the source tree.\n' >&2
exit 1
fi
if [[ -e "$2" || -L "$2" ]]; then
printf 'Output already exists; choose a new directory.\n' >&2
exit 1
fi
mkdir -- "$output_dir"
find "$source_dir" -type f \( -iname '*.jpg' -o -iname '*.jpeg' \) -print0 |
while IFS= read -r -d '' file; do
relative=${file#"$source_dir"/}
target="$output_dir/$relative"
mkdir -p -- "${target%/*}"
cp -- "$file" "$target"
jpegoptim --strip-none --nofix -- "$target"
done
printf 'JPEG build ready: %s\n' "$output_dir"
Exécutez-le depuis la racine du projet, avec un répertoire de sortie qui n’existe pas encore. Son répertoire parent doit déjà exister. Cet exemple écrit à côté de votre projet :
bash optimize-jpegs.sh assets/images ../optimized-images
Par exemple, assets/images/products/front.JPG devient
../optimized-images/products/front.JPG. Copier d’abord permet aussi de conserver les fichiers qui ne peuvent
pas devenir plus petits : utilisée seule, l’option --dest de jpegoptim peut omettre ces fichiers. Le script
n’écrase jamais un répertoire de sortie existant ; une deuxième exécution nécessite donc une
nouvelle destination.
La liste de fichiers délimitée par des caractères nuls et les chemins entre guillemets gèrent les
espaces, les retours à la ligne, les tirets initiaux et les caractères % littéraux dans les noms de
fichiers. La découverte et la copie utilisent le même chemin source résolu, y compris lorsque
l’argument de répertoire contient un lien symbolique suivi de ...
--nofix rejette les images qui produisent des avertissements de décodage au lieu de tenter de les réparer.
Un échec de copie ou d’optimisation arrête la boucle, et le réglage pipefail de Bash propage aussi l’échec
de find. Un échec survenant après la création de la sortie peut laisser un répertoire incomplet ;
inspectez ou supprimez ce build échoué avant de réessayer. Ne publiez qu’après un code de sortie
nul et le message de réussite final. Un répertoire source vide aboutit à un succès avec un
répertoire de sortie vide.
Préserver les modifications indexées du commit
Évitez un hook pre-commit qui optimise les fichiers de travail puis exécute git add assets/images.
Git ajoute l’état actuel de tout ce répertoire, y compris les
modifications sans rapport, les nouveaux fichiers et les suppressions. Il peut aussi remplacer une
version d’une image délibérément indexée par la version différente présente dans votre arbre de
travail.
Utilisez le script de build comme une tâche distincte. Il n’exécute aucune commande Git et ne réécrit pas les images sources ; le travail indexé, partiellement indexé, non indexé et non suivi reste donc tel que vous l’avez laissé. En local, il lit l’arbre de travail, y compris les JPEG non suivis ; sa sortie n’est pas un instantané de ce que vous avez indexé pour le commit. Utilisez un checkout propre en CI lorsque la sortie doit correspondre à un commit.
Exécuter le même script en CI
Dans une tâche CI sous Ubuntu, récupérez le projet, exécutez la commande d’installation ci-dessus,
puis lancez le script depuis la racine du projet. Pour une étape shell GitHub Actions, utilisez un
nouveau répertoire sous
RUNNER_TEMP
comme second argument. Configurez l’envoi de l’artefact ou le déploiement pour qu’il n’utilise ce
répertoire qu’après la réussite du script. Le script produit des ressources JPEG, pas un site web
complet ; votre build doit encore fournir ses autres fichiers et référencer les ressources
optimisées.
Choisir délibérément vos règles de métadonnées et de qualité
Le script utilise --strip-none pour conserver les marqueurs de métadonnées, notamment l’orientation EXIF, les
profils de couleur ICC et les commentaires. Les marqueurs JFIF et Adobe peuvent tout de même être
régénérés par la bibliothèque JPEG. Il ne s’agit ni d’une archive identique octet par octet ni d’un
nettoyage des données personnelles : les informations de localisation et d’appareil photo peuvent
subsister.
N’utilisez pas --strip-all à la place sans examiner la façon dont vos images utilisent ces métadonnées.
Supprimer les informations d’orientation ou de couleur peut modifier leur affichage, même lorsque
les échantillons de pixels décodés sont inchangés. Les
options de métadonnées décrivent les marqueurs que chaque option
conserve ou supprime.
Pour obtenir des fichiers plus petits au prix de la qualité d’image, --max=80 active l’optimisation avec
perte lorsque c’est applicable. Il s’agit d’un plafond de qualité, pas d’un objectif de taille de
80 %. --size active aussi l’optimisation avec perte et vise une taille sans la garantir. Évaluez ces
changements sur de nouvelles copies de vos originaux ; le script de build reste volontairement sans
perte.
Mesurer vos propres images
Comparez le nombre d’octets de l’original et celui de la sortie pour une image que vous avez construite :
wc -c -- assets/images/photo.jpg ../optimized-images/photo.jpg
Il n’existe pas de pourcentage d’économie fixe. Une image déjà optimisée peut garder la même taille, et des ressources plus petites ne suffisent pas à elles seules à établir une amélioration du chargement de la page. Vérifiez la sortie réelle de votre build et mesurez la page une fois qu’elle sert ces fichiers. Conservez les originaux afin de pouvoir revoir vos choix de qualité ou de métadonnées sans repartir d’une image déjà recompressée.
