diff --git a/public/sw.js b/public/sw.js index fef849f..c8ae52b 100644 --- a/public/sw.js +++ b/public/sw.js @@ -1,9 +1,16 @@ -// Bump this on every release that changes the app shell or the vendored -// engine build — it's the only thing that invalidates old caches, since +// Bump manually (via `pnpm sw:bump`) whenever SHELL_ASSETS or the shell's +// caching behavior changes — nothing else invalidates the shell cache, since // none of the cached URLs below are content-hashed by us. -const CACHE_VERSION = "v2"; -const SHELL_CACHE = `shell-${CACHE_VERSION}`; -const ENGINE_CACHE = `engine-${CACHE_VERSION}`; +const SHELL_VERSION = "v2"; +// Written automatically by scripts/copy-engine-assets.mjs from the installed +// @valknar/vpinball-wasm version — do not edit by hand. Ties the engine +// cache key to the actual engine build, so a version bump always busts +// stale caches instead of relying on a human to remember a separate step +// (which is exactly how returning visitors ended up on a stale, and +// possibly internally inconsistent, engine bundle before this existed). +const ENGINE_VERSION = "0.3.4"; +const SHELL_CACHE = `shell-${SHELL_VERSION}`; +const ENGINE_CACHE = `engine-${ENGINE_VERSION}`; const SHELL_ASSETS = [ "/", diff --git a/scripts/bump-sw-version.mjs b/scripts/bump-sw-version.mjs index 9ecb1fa..bb461ec 100644 --- a/scripts/bump-sw-version.mjs +++ b/scripts/bump-sw-version.mjs @@ -1,7 +1,8 @@ #!/usr/bin/env node -// Bumps public/sw.js's CACHE_VERSION so a deploy invalidates old caches — -// run this before cutting a release that changes the app shell or updates -// the vendored vpinball-wasm engine build. +// Bumps public/sw.js's SHELL_VERSION so a deploy invalidates old shell +// caches — run this before cutting a release that changes SHELL_ASSETS or +// the shell's caching behavior. (The engine cache busts itself automatically +// from the installed vpinball-wasm version — see copy-engine-assets.mjs.) import { readFile, writeFile } from "node:fs/promises"; import path from "node:path"; import { fileURLToPath } from "node:url"; @@ -10,12 +11,12 @@ const rootDir = path.dirname(path.dirname(fileURLToPath(import.meta.url))); const swPath = path.join(rootDir, "public", "sw.js"); const contents = await readFile(swPath, "utf8"); -const match = contents.match(/CACHE_VERSION = "v(\d+)"/); +const match = contents.match(/SHELL_VERSION = "v(\d+)"/); if (!match) { - console.error(`Couldn't find CACHE_VERSION in ${swPath}`); + console.error(`Couldn't find SHELL_VERSION in ${swPath}`); process.exit(1); } const next = Number(match[1]) + 1; -const updated = contents.replace(/CACHE_VERSION = "v\d+"/, `CACHE_VERSION = "v${next}"`); +const updated = contents.replace(/SHELL_VERSION = "v\d+"/, `SHELL_VERSION = "v${next}"`); await writeFile(swPath, updated); -console.log(`Bumped service worker CACHE_VERSION to v${next}`); +console.log(`Bumped service worker SHELL_VERSION to v${next}`); diff --git a/scripts/copy-engine-assets.mjs b/scripts/copy-engine-assets.mjs index 8c6e371..8715bca 100644 --- a/scripts/copy-engine-assets.mjs +++ b/scripts/copy-engine-assets.mjs @@ -3,13 +3,14 @@ // so it's served as plain static files (see lib/pinball/usePinballInstance.ts for why: // the package's own loadPinball() does a dynamic import() of its glue script that must // never pass through webpack's module graph). -import { cp, mkdir, rm } from "node:fs/promises"; +import { cp, mkdir, readFile, rm, writeFile } from "node:fs/promises"; import { existsSync } from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; const rootDir = path.dirname(path.dirname(fileURLToPath(import.meta.url))); -const pkgDistDir = path.join(rootDir, "node_modules", "@valknar", "vpinball-wasm", "dist"); +const pkgDir = path.join(rootDir, "node_modules", "@valknar", "vpinball-wasm"); +const pkgDistDir = path.join(pkgDir, "dist"); const targetDir = path.join(rootDir, "public", "vendor", "vpinball-wasm"); if (!existsSync(pkgDistDir)) { @@ -24,3 +25,16 @@ await mkdir(targetDir, { recursive: true }); await cp(pkgDistDir, targetDir, { recursive: true }); console.log(`Copied vpinball-wasm engine assets to ${path.relative(rootDir, targetDir)}/`); + +// Keep the service worker's engine cache key in lockstep with the installed +// engine version, so a version bump always busts stale caches for returning +// visitors — see sw.js's ENGINE_VERSION comment for why this must not be a +// manual step. +const { version: engineVersion } = JSON.parse(await readFile(path.join(pkgDir, "package.json"), "utf8")); +const swPath = path.join(rootDir, "public", "sw.js"); +const swContents = await readFile(swPath, "utf8"); +const updatedSw = swContents.replace(/ENGINE_VERSION = "[^"]*"/, `ENGINE_VERSION = "${engineVersion}"`); +if (updatedSw !== swContents) { + await writeFile(swPath, updatedSw); + console.log(`Set sw.js ENGINE_VERSION to ${engineVersion}`); +}