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
mtimedes.gz/.brà celui de la source (touch -r) pour stabiliser caches et signatures (ETag/If-Modified-Since). zopfliproduit souvent des.gzplus petits quegzip -9(plus lent). Gardezgzip -k -n -9en fallback.- parallélisez avec
-P $(nproc)ousysctl -n hw.ncpu. Sur machines partagées, limitez la concurrence. - côté serveur, activez la livraison statique si dispo (ex: nginx
gzip_static on;etbrotli_static on;) pour servir*.br/*.gzprécompilés.