levineuwirth.org/tools/compress-assets.sh

81 lines
2.6 KiB
Bash
Executable File

#!/usr/bin/env bash
# compress-assets.sh — Generate .gz (and .br, if brotli is installed) sidecars
# for compressible text assets in _site/.
#
# Pairs with nginx `gzip_static on` / `brotli_static on`: nginx serves the
# pre-compressed file when the client advertises a matching Accept-Encoding,
# so each build pays the compression cost once (at brotli -q 11) instead of
# the server paying it on every request.
#
# Only files >= MIN_SIZE bytes are compressed — below that, the compression
# framing overhead can exceed the savings. Sidecars are regenerated only
# when the source is newer than the existing sidecar, so re-runs are cheap.
#
# Usage:
# ./tools/compress-assets.sh # compress _site/
# ./tools/compress-assets.sh path/to/dir # compress a specific directory
set -euo pipefail
SITE_DIR="${1:-_site}"
MIN_SIZE="${MIN_SIZE:-1024}" # bytes
if [ ! -d "$SITE_DIR" ]; then
echo "compress-assets: directory '$SITE_DIR' not found" >&2
exit 1
fi
have_brotli=0
if command -v brotli >/dev/null 2>&1; then
have_brotli=1
else
echo "compress-assets: brotli not found — generating gzip only" >&2
echo " (install: pacman -S brotli / apt install brotli)" >&2
fi
# Export for subshells invoked by xargs.
export MIN_SIZE
export have_brotli
compress_one() {
local src="$1"
local size
size=$(stat -c '%s' "$src" 2>/dev/null || stat -f '%z' "$src")
if [ "$size" -lt "$MIN_SIZE" ]; then
return
fi
# gzip sidecar — -9 max ratio, -n strips filename/mtime for reproducible output.
if [ ! -f "$src.gz" ] || [ "$src" -nt "$src.gz" ]; then
gzip -9 -n -c "$src" > "$src.gz.tmp" && mv "$src.gz.tmp" "$src.gz"
fi
# brotli sidecar — -Z is the max quality (level 11); slow but cached.
if [ "$have_brotli" = "1" ]; then
if [ ! -f "$src.br" ] || [ "$src" -nt "$src.br" ]; then
brotli -Z -f -o "$src.br.tmp" "$src" && mv "$src.br.tmp" "$src.br"
fi
fi
}
export -f compress_one
# Extensions worth compressing. Images (png/jpg/webp) and PDFs are already
# compressed; fonts (woff2) are zstd/brotli internally — don't re-wrap.
find "$SITE_DIR" -type f \( \
-name '*.html' -o \
-name '*.css' -o \
-name '*.js' -o \
-name '*.mjs' -o \
-name '*.json' -o \
-name '*.svg' -o \
-name '*.xml' -o \
-name '*.txt' -o \
-name '*.wasm' \
\) \
-not -name '*.gz' \
-not -name '*.br' \
-print0 \
| xargs -0 -P "$(nproc 2>/dev/null || echo 4)" -I {} bash -c 'compress_one "$@"' _ {}
echo "compress-assets: sidecars written under $SITE_DIR/"