← retour aux snippets

précompresser assets gzip+brotli

Générer .gz et .br pour les assets statiques avec mtime stable et exclusions sûres.

objectif

Précompresser les fichiers statiques (HTML, CSS, JS, SVG, JSON) en .gz et .br pour servir plus vite, sans toucher aux binaires déjà compressés (PNG, JPG, MP4) et en conservant des métadonnées stables (mtime) pour de bons caches.

code minimal

# dossier des assets
ROOT="/srv/data.pm/current"

# 1) gzip optimal (rapide si zopfli indispo)
find "$ROOT" -type f -regextype posix-extended \
  -regex '.*\.(html|css|js|mjs|json|svg|txt|xml|map)$' \
  -not -name '*.gz' -not -name '*.br' -print0 \
  | xargs -0 -n1 -P"$(nproc 2>/dev/null || sysctl -n hw.ncpu)" bash -lc '
      f="$0"; g="$f.gz"
      if command -v zopfli >/dev/null 2>&1; then
        zopfli --i1000 --gzip -c -- "$f" > "$g"
      else
        gzip -k -n -9 -- "$f"
      fi
      touch -r "$f" "$g"
  '

# 2) brotli maximal (q=11)
find "$ROOT" -type f -regextype posix-extended \
  -regex '.*\.(html|css|js|mjs|json|svg|txt|xml|map)$' \
  -not -name '*.gz' -not -name '*.br' -print0 \
  | xargs -0 -n1 -P"$(nproc 2>/dev/null || sysctl -n hw.ncpu)" bash -lc '
      f="$0"; b="$f.br"
      brotli -f -q 11 --input "$f" --output "$b"
      touch -r "$f" "$b"
  '

utilisation

# 1) précompresser uniquement les fichiers modifiés depuis la dernière release
ROOT="/srv/data.pm/current"
find "$ROOT" -type f -newermt "2025-08-15T00:00:00Z" \
  -regextype posix-extended -regex '.*\.(html|css|js|mjs|json|svg|txt|xml|map)$' \
  -not -name '*.gz' -not -name '*.br' -print0 \
  | xargs -0 -n1 -P"$(nproc 2>/dev/null || sysctl -n hw.ncpu)" bash -lc '
      f="$0"
      gzip -k -n -9 -- "$f"; touch -r "$f" "$f.gz"
      brotli -f -q 11 --input "$f" --output "$f.br"; touch -r "$f" "$f.br"
  '

# 2) rapport: tailles avant/après (top 20 économies)
TMP="$(mktemp)"; trap 'rm -f "$TMP"' EXIT
find "$ROOT" -type f -regextype posix-extended -regex '.*\.(css|js|mjs|html|json|svg)$' -print0 \
  | xargs -0 -I{} bash -lc '
      f="{}"; s=$(stat -c %s "$f" 2>/dev/null || stat -f %z "$f")
      gz=$(stat -c %s "$f.gz" 2>/dev/null || stat -f %z "$f.gz" 2>/dev/null || echo 0)
      br=$(stat -c %s "$f.br" 2>/dev/null || stat -f %z "$f.br" 2>/dev/null || echo 0)
      printf "%d %d %d %s\n" "$s" "$gz" "$br" "$f"
  ' | awk '$2>0 || $3>0 {printf "%10d %10d %10d  %s\n",$1,$2,$3,$4}' \
  | sort -nr | head -n 20 | tee "$TMP"
sed -n '1,20p' "$TMP"

# 3) nettoyer les artefacts orphelins (.gz/.br sans source)
find "$ROOT" -type f \( -name '*.gz' -o -name '*.br' \) -print0 \
  | xargs -0 -I{} bash -lc '
      f="{}"
      case "$f" in
        *.gz) src="${f%.gz}" ;;
        *.br) src="${f%.br}" ;;
      esac
      [ -f "$src" ] || { echo "orphan: $f"; rm -f -- "$f"; }
  '

# 4) pipeline build: copier build -> target puis précompresser
rsync -a --delete ./build/data.pm/ "$ROOT"/
# puis exécuter le bloc "code minimal" ci-dessus

# 5) vérifier qu’aucun binaire compressé n’est traité (PNG, JPG, MP4, PDF ignorés)
find "$ROOT" -type f -regextype posix-extended \
  -regex '.*\.(png|jpe?g|gif|webp|avif|mp4|pdf|zip|gz|br)$' \
  -print | head

variante(s) utile(s)

# variante: ne garder que brotli (souvent plus petit sur texte)
find "$ROOT" -type f -regextype posix-extended \
  -regex '.*\.(html|css|js|mjs|json|svg|txt|xml|map)$' -print0 \
  | xargs -0 -n1 -P"$(nproc 2>/dev/null || sysctl -n hw.ncpu)" bash -lc '
      f="$0"; brotli -f -q 11 -- "$f" -o "$f.br"; touch -r "$f" "$f.br"
  '

# macOS: zopfli via Homebrew, fallback gzip
if command -v zopfli >/dev/null 2>&1; then
  COMPRESS='zopfli --i1000 --gzip -c'
else
  COMPRESS='gzip -k -n -9 -c'
fi
export COMPRESS
find "$ROOT" -type f -name "*.js" -print0 \
  | xargs -0 -I{} bash -lc '$COMPRESS "{}" > "{}.gz"; touch -r "{}" "{}.gz"'

# ranger les artefacts dans un sous-dossier (ex: ./.__precomp__) puis déployer
OUT="$ROOT/.__precomp__"; install -d -m 0755 "$OUT"
find "$ROOT" -type f -regextype posix-extended -regex ".*\.(css|js|html)$" -print0 \
  | xargs -0 -I{} bash -lc '
      f="{}"; d="$OUT${f#'"$ROOT"'}"; install -d -m 0755 "$(dirname "$d")"
      gzip -n -9 -c -- "$f" > "$d.gz"; touch -r "$f" "$d.gz"
      brotli -f -q 11 -- "$f" -o "$d.br"; touch -r "$f" "$d.br"
  '

# vérifier que mtime des artefacts == source (utile pour caches/ETag)
check_mtime() {
  local f="$1"; [ -f "$f.gz" ] || return 0
  test "$(stat -c %Y "$f" 2>/dev/null || stat -f %m "$f")" \
     -eq "$(stat -c %Y "$f.gz" 2>/dev/null || stat -f %m "$f.gz")" || echo "mtime mismatch: $f"
}
export -f check_mtime
find "$ROOT" -type f -name "*.js" -print0 | xargs -0 -n1 -P4 bash -lc 'check_mtime "$0"'

notes

  • ne compressez jamais les formats déjà compressés (images, vidéos, archives). Ciblez uniquement texte: html|css|js|json|svg|xml|txt|map.
  • fixez mtime des .gz/.br à celui de la source (touch -r) pour stabiliser caches et signatures (ETag/If-Modified-Since).
  • zopfli produit souvent des .gz plus petits que gzip -9 (plus lent). Gardez gzip -k -n -9 en fallback.
  • parallélisez avec -P $(nproc) ou sysctl -n hw.ncpu. Sur machines partagées, limitez la concurrence.
  • côté serveur, activez la livraison statique si dispo (ex: nginx gzip_static on; et brotli_static on;) pour servir *.br/*.gz précompilés.