76 lines
3.3 KiB
Shell
Executable file
76 lines
3.3 KiB
Shell
Executable file
#!/bin/sh
|
|
# Make the wasm bundle's index.html work when it is served at a URL deeper than
|
|
# "/", and verify it stayed that way.
|
|
#
|
|
# usage: tools/fix-bundle-depth.sh <bundle-dir>
|
|
#
|
|
# WHY THIS EXISTS
|
|
#
|
|
# Crafter.Build generates an index.html whose boot scripts are relative
|
|
# (src="runtime.js?v=…"), and runtime.js in turn does fetch("variants.json"),
|
|
# fetch("files.json"), one fetch per VFS entry, and fetches the .wasm named by
|
|
# variants.json — all relative. Relative to the DOCUMENT, not to the module.
|
|
#
|
|
# That is correct when the document is "/". It is broken for every deeper path,
|
|
# and this site has several: /demos/raytracer, /shop/<slug>, /legal/<page>. A
|
|
# document at /demos/raytracer sends the browser to /demos/runtime.js, which does
|
|
# not exist, so Caddy's `try_files {path} /index.html` returns index.html — and
|
|
# the browser refuses to execute a module served as text/html. The visible result
|
|
# is four NS_ERROR_CORRUPTED_CONTENT errors and a dead page.
|
|
#
|
|
# The SSR path solves this itself (Views::RenderDocument emits <base href="/"> on
|
|
# any page that boots wasm). This script covers the OTHER path: the static shell
|
|
# Caddy serves directly when the backend is down, where there is no SSR to help.
|
|
#
|
|
# Two changes, both idempotent:
|
|
# 1. <base href="/"> in <head>, which fixes every relative fetch runtime.js
|
|
# makes, since a bare relative fetch() resolves against the document base.
|
|
# 2. Root the boot script srcs, which <base> already handles but which is worth
|
|
# doing anyway so the tags are correct even if the <base> is ever dropped.
|
|
#
|
|
# The durable fix belongs upstream: runtime.js should resolve its own assets
|
|
# against import.meta.url rather than the document. Then no bundle would care how
|
|
# deep the page is. Until then, this.
|
|
|
|
set -eu
|
|
|
|
DIR="${1:-}"
|
|
[ -n "$DIR" ] || { echo "usage: tools/fix-bundle-depth.sh <bundle-dir>" >&2; exit 1; }
|
|
IDX="$DIR/index.html"
|
|
[ -f "$IDX" ] || { echo "fix-bundle-depth: $IDX not found" >&2; exit 1; }
|
|
|
|
TMP="$(mktemp)"
|
|
trap 'rm -f "$TMP"' EXIT
|
|
|
|
# 1. Root every relative script src. Anchored on `src="` immediately followed by
|
|
# something that is not / : and : covers http:, https: and any other scheme,
|
|
# / covers both already-rooted and protocol-relative //host.
|
|
sed -E 's|(<script[^>]*[[:space:]]src=")([^"/:][^"]*")|\1/\2|g' "$IDX" > "$TMP"
|
|
|
|
# 2. Insert <base href="/"> as the first thing in <head>, unless one is present.
|
|
# First, so it applies to everything after it — a <base> only governs the
|
|
# references that follow it.
|
|
if ! grep -qi '<base[[:space:]]' "$TMP"; then
|
|
sed -E '0,/<head>/s|<head>|<head>\n<base href="/">|' "$TMP" > "$TMP.b" \
|
|
&& mv "$TMP.b" "$TMP"
|
|
fi
|
|
|
|
mv "$TMP" "$IDX"
|
|
trap - EXIT
|
|
|
|
# Verify rather than assume. A silent no-op here would ship the broken page.
|
|
fail=0
|
|
if ! grep -qi '<base href="/">' "$IDX"; then
|
|
echo "fix-bundle-depth: FAILED to insert <base> into $IDX" >&2
|
|
fail=1
|
|
fi
|
|
rel=$(grep -oE '<script[^>]*[[:space:]]src="[^"/:][^"]*"' "$IDX" | wc -l)
|
|
if [ "$rel" -ne 0 ]; then
|
|
echo "fix-bundle-depth: $rel script src(s) are still relative in $IDX:" >&2
|
|
grep -oE '<script[^>]*[[:space:]]src="[^"/:][^"]*"' "$IDX" >&2
|
|
fail=1
|
|
fi
|
|
[ "$fail" -eq 0 ] || exit 1
|
|
|
|
n=$(grep -coE '<script[^>]*[[:space:]]src="/' "$IDX" || true)
|
|
echo "fix-bundle-depth: $IDX has <base href=\"/\"> and $n rooted script src(s)"
|