Merge branch 'dev'

This commit is contained in:
Jeff Emmett 2026-03-04 19:12:15 -08:00
commit 798a5edb65
2 changed files with 112 additions and 15 deletions

View File

@ -831,6 +831,60 @@ export default defineConfig({
// Demo script not yet created — skip
}
}
// ── Generate precache manifest ──
// Scans dist/ for all cacheable assets and writes precache-manifest.json
const { readdirSync, writeFileSync, statSync: statSync2 } = await import("node:fs");
const distDir = resolve(__dirname, "dist");
function walkDir(dir: string, prefix = ""): string[] {
const entries: string[] = [];
for (const entry of readdirSync(dir, { withFileTypes: true })) {
const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
if (entry.isDirectory()) {
entries.push(...walkDir(resolve(dir, entry.name), rel));
} else {
entries.push(`/${rel}`);
}
}
return entries;
}
const allFiles = walkDir(distDir);
// Core: shell assets + HTML pages (precached at install, ~300KB)
const core = allFiles.filter((f) =>
f === "/" ||
f === "/index.html" ||
f === "/canvas.html" ||
f === "/create-space.html" ||
f === "/admin.html" ||
f === "/shell.js" ||
f === "/shell.css" ||
f === "/theme.css" ||
f === "/favicon.png"
);
// Ensure root URL is present
if (!core.includes("/")) core.unshift("/");
// Modules: all module JS + CSS (lazy-cached after activation)
const modules = allFiles.filter((f) =>
f.startsWith("/modules/") &&
(f.endsWith(".js") || f.endsWith(".css")) &&
!f.includes("-demo.js") // skip demo scripts
);
const manifest = {
version: new Date().toISOString(),
core,
modules,
};
writeFileSync(
resolve(distDir, "precache-manifest.json"),
JSON.stringify(manifest, null, "\t"),
);
console.log(`[precache] Generated manifest: ${core.length} core + ${modules.length} module assets`);
},
},
},

View File

@ -1,7 +1,7 @@
/// <reference lib="webworker" />
declare const self: ServiceWorkerGlobalScope;
const CACHE_VERSION = "rspace-v1";
const CACHE_VERSION = "rspace-v2";
const STATIC_CACHE = `${CACHE_VERSION}-static`;
const HTML_CACHE = `${CACHE_VERSION}-html`;
const API_CACHE = `${CACHE_VERSION}-api`;
@ -9,32 +9,75 @@ const API_CACHE = `${CACHE_VERSION}-api`;
// Vite-hashed assets are immutable (content hash in filename)
const IMMUTABLE_PATTERN = /\/assets\/.*\.[a-f0-9]{8}\.(js|css|wasm)$/;
// App shell to precache on install
const PRECACHE_URLS = ["/", "/canvas.html"];
// Minimal fallback if manifest fetch fails
const FALLBACK_PRECACHE = ["/", "/canvas.html"];
// Max age for cached API GET responses (5 minutes)
const API_CACHE_MAX_AGE_MS = 5 * 60 * 1000;
interface PrecacheManifest {
version: string;
core: string[]; // shell assets + HTML pages — cached at install
modules: string[]; // module JS/CSS — lazy-cached after activation
}
self.addEventListener("install", (event) => {
event.waitUntil(
caches.open(HTML_CACHE).then((cache) => cache.addAll(PRECACHE_URLS))
(async () => {
let manifest: PrecacheManifest | null = null;
try {
const res = await fetch(`/precache-manifest.json?v=${CACHE_VERSION}`);
if (res.ok) manifest = await res.json();
} catch { /* manifest unavailable — use fallback */ }
const coreUrls = manifest?.core ?? FALLBACK_PRECACHE;
// Precache core shell assets (blocking — required for cold offline start)
const htmlCache = await caches.open(HTML_CACHE);
const staticCache = await caches.open(STATIC_CACHE);
const htmlUrls = coreUrls.filter((u) => u.endsWith(".html") || u === "/");
const staticUrls = coreUrls.filter((u) => !u.endsWith(".html") && u !== "/");
await Promise.all([
htmlCache.addAll(htmlUrls),
staticCache.addAll(staticUrls),
]);
})()
);
self.skipWaiting();
});
self.addEventListener("activate", (event) => {
// Clean up old versioned caches
event.waitUntil(
caches
.keys()
.then((keys) =>
Promise.all(
keys
.filter((key) => !key.startsWith(CACHE_VERSION))
.map((key) => caches.delete(key))
)
)
.then(() => self.clients.claim())
(async () => {
// Clean up old versioned caches
const keys = await caches.keys();
await Promise.all(
keys
.filter((key) => !key.startsWith(CACHE_VERSION))
.map((key) => caches.delete(key))
);
await self.clients.claim();
// Lazy-cache module bundles in background (non-blocking)
try {
const res = await fetch(`/precache-manifest.json?v=${CACHE_VERSION}`);
if (res.ok) {
const manifest: PrecacheManifest = await res.json();
if (manifest.modules?.length) {
const cache = await caches.open(STATIC_CACHE);
// Cache modules one-by-one to avoid overwhelming bandwidth
for (const url of manifest.modules) {
try {
const existing = await cache.match(url);
if (!existing) await cache.add(url);
} catch { /* skip individual failures */ }
}
}
}
} catch { /* manifest unavailable */ }
})()
);
});