Optimización JPEG automatizada con jpegoptim en tus proyectos
Ejecuta jpegoptim sobre copias de tus imágenes en un directorio de compilación. Así obtienes JPEG optimizados para publicar sin modificar los originales ni el área de preparación de Git. Esta guía crea un directorio de JPEG optimizados sin pérdida en Linux y se detiene con un error si falla alguna imagen.
Instala jpegoptim en Ubuntu
Necesitas Bash, GNU coreutils y findutils, y un directorio existente de archivos JPEG. El siguiente
script usa opciones de GNU realpath; es un ejemplo para Linux, no un script
nativo de macOS ni de Windows. Se probó con el paquete jpegoptim 1.4.7 de Ubuntu 24.04 y con
jpegoptim 1.5.6 en Linux.
En Ubuntu 24.04, instala el paquete jpegoptim:
sudo apt-get update &&
sudo apt-get install -y jpegoptim &&
jpegoptim --version
El gestor de paquetes instala las bibliotecas necesarias para la ejecución. No necesitas
libjpeg-dev para ejecutar el programa incluido en el paquete.
Previsualiza una imagen
Reemplaza photo.jpg por un archivo de tu directorio de imágenes:
jpegoptim --noaction --strip-none --nofix -- assets/images/photo.jpg
--noaction informa de un tamaño candidato sin escribir el archivo. Sin
--max ni --size, jpegoptim optimiza la codificación
del JPEG sin pérdida. Su manual distingue esta operación de la
reducción de calidad de la imagen. Los bytes comprimidos pueden cambiar mientras los píxeles
decodificados permanecen iguales. Un resultado skipped puede significar
simplemente que el tamaño candidato no era menor.
Crea un directorio separado de JPEG optimizados
Guarda este script como optimize-jpegs.sh en la raíz de tu proyecto. Copia archivos
regulares .jpg y .jpeg, incluidos aquellos con
extensiones en mayúsculas y dentro de directorios anidados, y luego optimiza las copias. Se excluyen
los demás archivos y las entradas de enlaces simbólicos. Mantén el árbol de origen sin cambios
mientras se ejecuta el 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"
Ejecútalo desde la raíz del proyecto, con un directorio de salida que aún no exista. Su directorio padre debe existir previamente. Este ejemplo escribe junto al directorio de tu proyecto:
bash optimize-jpegs.sh assets/images ../optimized-images
Por ejemplo, assets/images/products/front.JPG se convierte en
../optimized-images/products/front.JPG. Copiar primero también conserva los archivos cuyo tamaño ya no
puede reducirse: la opción --dest de jpegoptim por sí sola puede omitirlos.
El script nunca sobrescribe un directorio de salida existente, por lo que una segunda ejecución
necesita un destino nuevo.
La lista de archivos delimitada por caracteres nulos y las rutas entre comillas permiten manejar
espacios, saltos de línea, guiones iniciales y caracteres % literales
en los nombres de archivo. Tanto la búsqueda como la copia usan la misma ruta de origen resuelta,
incluso cuando el argumento del directorio contiene un enlace simbólico seguido de
...
--nofix rechaza las imágenes que generan advertencias de decodificación en
lugar de intentar repararlas. Un fallo de copia u optimización detiene el bucle, y
pipefail de Bash también propaga un fallo de find.
Un fallo posterior a la creación de la salida puede dejar un directorio incompleto; inspecciona o
elimina esa compilación fallida antes de volver a intentarlo. Publica solo después de obtener un
código de salida cero y el mensaje final de éxito. Un directorio de origen vacío produce un
directorio de salida vacío y la ejecución finaliza correctamente.
Mantén intactos los cambios preparados para el commit
Evita un hook de pre-commit que optimice los archivos de trabajo y luego ejecute
git add assets/images.
Git añade el estado actual de todo ese directorio, incluidas las
ediciones no relacionadas, los archivos nuevos y las eliminaciones. También puede reemplazar una
versión de una imagen preparada deliberadamente por la versión diferente de tu árbol de trabajo.
Usa el script de compilación como una tarea independiente. No ejecuta comandos de Git ni reescribe las imágenes de origen, por lo que los cambios preparados, parcialmente preparados y sin preparar, así como los archivos sin seguimiento, quedan tal como los dejaste. En local, lee el árbol de trabajo, incluidos los JPEG sin seguimiento; su salida no es una instantánea del commit que has preparado. Usa una copia de trabajo limpia en CI cuando la salida deba corresponder a un commit.
Ejecuta el mismo script en CI
En una tarea de CI con Ubuntu, obtén una copia de trabajo del proyecto, ejecuta el comando de
instalación anterior y luego invoca el script desde la raíz del proyecto. Para un paso de shell en
GitHub Actions, usa un directorio nuevo dentro de
RUNNER_TEMP
como segundo argumento. Configura la subida de artefactos o el despliegue para usar ese directorio
solo después de que el script finalice correctamente. El script produce archivos JPEG, no un sitio
web completo; tu compilación aún debe aportar los demás archivos y referenciar las imágenes
optimizadas.
Elige conscientemente las políticas de metadatos y calidad
El script usa --strip-none para conservar los marcadores de metadatos, incluidos
la orientación EXIF, los perfiles de color ICC y los comentarios. La biblioteca JPEG aún puede
regenerar los marcadores JFIF y Adobe. Esto no es una copia de archivo idéntica byte por byte ni una
limpieza de datos privados: la información de ubicación y de la cámara puede permanecer.
No lo sustituyas por --strip-all sin considerar cómo usan tus imágenes esos
metadatos. Eliminar la información de orientación o color puede afectar su visualización incluso
cuando las muestras de píxeles decodificadas no cambian. Las
opciones de metadatos describen qué marcadores conserva o elimina
cada opción.
Para obtener archivos más pequeños a costa de la calidad de imagen,
--max=80 habilita la optimización con pérdida cuando corresponde. Es un
límite máximo de calidad, no un objetivo de tamaño del 80 %. --size también
habilita la optimización con pérdida y busca alcanzar un tamaño en lugar de garantizarlo. Evalúa
estos cambios sobre copias nuevas de tus originales; el script de compilación mantiene
intencionalmente la optimización sin pérdida.
Mide tus propias imágenes
Compara la cantidad de bytes del original y de la salida para una imagen que hayas procesado:
wc -c -- assets/images/photo.jpg ../optimized-images/photo.jpg
No hay un porcentaje de ahorro fijo. Una imagen ya optimizada puede conservar el mismo tamaño, y unos archivos más pequeños por sí solos no demuestran una mejora en la carga de la página. Revisa la salida real de tu compilación y mide la página una vez que sirva esos archivos. Conserva los originales para poder reconsiderar las decisiones sobre calidad o metadatos sin partir de una imagen previamente recomprimida.
