prerender-static.sh now uses the wasm-bindgen CLI version the crate pins (installing a private copy when the machine's differs — the global 0.2.127 could not build the pinned 0.2.114 crate), content-hashes pkg/ including the hydration loader's arguments, and writes sw.js from a template with a versioned precache list. The old worker used a fixed cache name, cached responses without checking them, and left the wasm-bindgen snippets out of its precache, so an installed app could not open offline; deploy.sh's renaming also missed the hydration loader, downloading the wasm twice. Deploy with no_cache_bust. The manifest no longer claims every language or every access method. Co-Authored-By: Claude Opus 5.5 <[email protected]>
123 lines
5.9 KiB
Shell
Executable file
123 lines
5.9 KiB
Shell
Executable file
#!/usr/bin/env bash
|
|
# ============================================================================
|
|
# prerender-static.sh — build aac.transaction.science as a self-contained
|
|
# static site.
|
|
#
|
|
# AAC ships as a static CSR-hydrate SPA, but it's a Leptos SSR app: the
|
|
# index.html the browser loads is the SSR server's *rendered* output (it carries
|
|
# the hydration island markers and the pre-rendered board). A plain
|
|
# `cargo leptos build` never emits that index.html, and it doesn't copy the
|
|
# PWA assets that live in `static/`. This script closes both gaps and makes the
|
|
# result safe to cache forever:
|
|
#
|
|
# build -> target/site/{pkg/, icons/} (cargo-leptos)
|
|
# render -> target/site/index.html (capture SSR `/`)
|
|
# hash -> pkg/aac-web.<sha256:12>.{js,wasm,css} (and rewrite references)
|
|
# worker -> target/site/sw.js (static/sw.js + precache list)
|
|
#
|
|
# Output: a self-contained `target/site/` ready to rsync to prod. Deploy it with
|
|
# no_cache_bust — the names are already content-hashed, and the service worker
|
|
# lists them. Idempotent.
|
|
#
|
|
# Hosting under a sub-path, or under another name, is a build-time choice; see
|
|
# AAC_BASE_PATH, AAC_SW_URL, AAC_APP_TITLE and AAC_HIDE_ENERGY in
|
|
# crates/aac-web/src/app.rs. This script builds the root-mounted default.
|
|
# ============================================================================
|
|
set -euo pipefail
|
|
cd "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
|
PORT="${AAC_PRERENDER_PORT:-39207}"
|
|
SITE="target/site"
|
|
CACHE="${XDG_CACHE_HOME:-$HOME/.cache}/aac-rs"
|
|
|
|
# wasm-bindgen's CLI must match the crate version exactly (Cargo.toml pins it).
|
|
# Use a private copy of the right version rather than the machine's global one.
|
|
WBG_VER="$(awk '/^name = "wasm-bindgen"$/{getline; gsub(/version = |"/, ""); print; exit}' Cargo.lock)"
|
|
if ! wasm-bindgen --version 2>/dev/null | grep -qx "wasm-bindgen $WBG_VER"; then
|
|
WBG_ROOT="$CACHE/wasm-bindgen-$WBG_VER"
|
|
if [ ! -x "$WBG_ROOT/bin/wasm-bindgen" ]; then
|
|
echo "▸ installing wasm-bindgen-cli $WBG_VER into $WBG_ROOT"
|
|
cargo install --quiet --locked --root "$WBG_ROOT" wasm-bindgen-cli --version "$WBG_VER"
|
|
fi
|
|
export PATH="$WBG_ROOT/bin:$PATH"
|
|
fi
|
|
|
|
echo "▸ cargo leptos build --release"
|
|
rm -rf "$SITE/pkg"
|
|
cargo leptos build --release
|
|
find "$SITE/pkg" -name '*.d.ts' -delete
|
|
|
|
echo "▸ starting SSR server on 127.0.0.1:$PORT to prerender /"
|
|
LOG="$(mktemp)"
|
|
LEPTOS_OUTPUT_NAME=aac-web \
|
|
LEPTOS_SITE_ROOT="$SITE" \
|
|
LEPTOS_SITE_PKG_DIR=pkg \
|
|
LEPTOS_SITE_ADDR="127.0.0.1:$PORT" \
|
|
LEPTOS_ENV=PROD \
|
|
target/release/aac-web > "$LOG" 2>&1 &
|
|
SRV=$!
|
|
trap 'kill "$SRV" 2>/dev/null || true' EXIT
|
|
|
|
# Wait for the listener (up to ~30s).
|
|
ready=false
|
|
for _ in $(seq 1 120); do
|
|
if curl -sf "http://127.0.0.1:$PORT/" -o /dev/null 2>/dev/null; then ready=true; break; fi
|
|
sleep 0.25
|
|
done
|
|
if ! $ready; then
|
|
echo "✗ SSR server did not come up; log:" >&2; tail -5 "$LOG" >&2; exit 1
|
|
fi
|
|
|
|
echo "▸ capturing rendered / -> $SITE/index.html"
|
|
curl -sf "http://127.0.0.1:$PORT/" -o "$SITE/index.html"
|
|
|
|
kill "$SRV" 2>/dev/null || true
|
|
trap - EXIT
|
|
|
|
# Sanity-check the capture is the hydrating SSR shell, not an error page.
|
|
test -s "$SITE/index.html"
|
|
grep -q 'mod.hydrate' "$SITE/index.html" || { echo "✗ captured index.html is not the hydrating shell" >&2; exit 1; }
|
|
grep -q '<title>AAC' "$SITE/index.html" || { echo "✗ captured index.html missing expected title" >&2; exit 1; }
|
|
|
|
echo "▸ content-hashing pkg/"
|
|
hash12() { shasum -a 256 "$1" | cut -c1-12; }
|
|
# Portable in-place sed (BSD and GNU disagree about -i): sed_i <args…> <file>
|
|
sed_i() { local f="${!#}"; sed "${@:1:$#-1}" "$f" > "$f.tmp" && mv "$f.tmp" "$f"; }
|
|
P="$SITE/pkg"
|
|
[ -f "$P/aac-web_bg.wasm" ] || mv "$P/aac-web.wasm" "$P/aac-web_bg.wasm"
|
|
W="$(hash12 "$P/aac-web_bg.wasm")"; mv "$P/aac-web_bg.wasm" "$P/aac-web.$W.wasm"
|
|
C="$(hash12 "$P/aac-web.css")"; mv "$P/aac-web.css" "$P/aac-web.$C.css"
|
|
# The glue may name its wasm; point it at the hashed file before hashing the glue.
|
|
sed_i -e "s#aac-web_bg\\.wasm#aac-web.$W.wasm#g" -e "s#aac-web\\.wasm#aac-web.$W.wasm#g" "$P/aac-web.js"
|
|
J="$(hash12 "$P/aac-web.js")"; mv "$P/aac-web.js" "$P/aac-web.$J.js"
|
|
sed_i \
|
|
-e "s#/pkg/aac-web_bg\\.wasm#/pkg/aac-web.$W.wasm#g" \
|
|
-e "s#/pkg/aac-web\\.wasm#/pkg/aac-web.$W.wasm#g" \
|
|
-e "s#/pkg/aac-web\\.js#/pkg/aac-web.$J.js#g" \
|
|
-e "s#/pkg/aac-web\\.css#/pkg/aac-web.$C.css#g" \
|
|
"$SITE/index.html"
|
|
# Leptos's hydration loader builds its URLs from arguments:
|
|
# (root, pkg_path, output_name, wasm_output_name)
|
|
LOADER='("", "pkg", "aac-web", "aac-web")'
|
|
grep -qF "$LOADER" "$SITE/index.html" || { echo "✗ hydration loader call not found — has Leptos changed its loader?" >&2; exit 1; }
|
|
sed_i "s#(\"\", \"pkg\", \"aac-web\", \"aac-web\")#(\"\", \"pkg\", \"aac-web.$J\", \"aac-web.$W\")#" "$SITE/index.html"
|
|
for f in "aac-web.$W.wasm" "aac-web.$J.js" "aac-web.$C.css" "\"aac-web.$J\", \"aac-web.$W\""; do
|
|
grep -q "$f" "$SITE/index.html" || { echo "✗ index.html does not reference $f" >&2; exit 1; }
|
|
done
|
|
|
|
echo "▸ bundling PWA assets and the service worker"
|
|
cp crates/aac-web/static/manifest.json "$SITE/manifest.json"
|
|
# Precache: the shell, the manifest, every file in pkg/ (including wasm-bindgen
|
|
# snippets such as the eye-tracking loader) and the icons.
|
|
PRECACHE=("/" "/manifest.json")
|
|
while IFS= read -r f; do PRECACHE+=("/${f#"$SITE"/}"); done < <(find "$SITE/pkg" "$SITE/icons" -type f | LC_ALL=C sort)
|
|
VERSION="$(for u in "${PRECACHE[@]}"; do
|
|
if [ "$u" = "/" ]; then f="$SITE/index.html"; else f="$SITE$u"; fi
|
|
printf '%s %s\n' "$u" "$(shasum -a 256 "$f" | cut -c1-64)"
|
|
done | shasum -a 256 | cut -c1-12)"
|
|
LIST="[$(printf '"%s",' "${PRECACHE[@]}" | sed 's/,$//')]"
|
|
sed -e "s#__VERSION__#$VERSION#" -e "s#__PRECACHE_URLS__#$LIST#" crates/aac-web/static/sw.js > "$SITE/sw.js"
|
|
grep -q "__" "$SITE/sw.js" && grep -nE "__[A-Z_]+__" "$SITE/sw.js" && { echo "✗ sw.js has an unfilled placeholder" >&2; exit 1; }
|
|
|
|
echo "✓ self-contained dist ready at $SITE/ (service worker $VERSION, ${#PRECACHE[@]} files precached)"
|
|
ls -1 "$SITE" "$SITE/pkg"
|