89 lines
3.4 KiB
TypeScript
89 lines
3.4 KiB
TypeScript
|
|
import tailwindcss from "@tailwindcss/vite";
|
||
|
|
import { createHash } from "node:crypto";
|
||
|
|
import { readFileSync } from "node:fs";
|
||
|
|
import { resolve } from "node:path";
|
||
|
|
import { defineConfig, type Plugin } from "vite";
|
||
|
|
import solid from "vite-plugin-solid";
|
||
|
|
|
||
|
|
const outputDirectory = process.env.WASMELD_WEB_OUT_DIR ?? "dist";
|
||
|
|
|
||
|
|
export default defineConfig({
|
||
|
|
server: {
|
||
|
|
port: 3000,
|
||
|
|
},
|
||
|
|
plugins: [solid(), tailwindcss(), pwaWorker()],
|
||
|
|
build: {
|
||
|
|
outDir: outputDirectory,
|
||
|
|
emptyOutDir: true,
|
||
|
|
rollupOptions: {
|
||
|
|
// Host and Page remain separate HTML documents. Dynamic page imports
|
||
|
|
// then create one lazy chunk per primary management view.
|
||
|
|
input: {
|
||
|
|
host: resolve(import.meta.dirname, "index.html"),
|
||
|
|
page: resolve(import.meta.dirname, "page.html"),
|
||
|
|
},
|
||
|
|
},
|
||
|
|
},
|
||
|
|
});
|
||
|
|
|
||
|
|
function pwaWorker(): Plugin {
|
||
|
|
return {
|
||
|
|
name: "wasmeld-pwa-worker",
|
||
|
|
apply: "build",
|
||
|
|
generateBundle(_, bundle) {
|
||
|
|
// Derive the cache name from emitted filenames and unhashed public
|
||
|
|
// assets. A changed build activates a new cache without another PWA
|
||
|
|
// dependency or a generated source file in the repository.
|
||
|
|
const publicFiles = ["icon.svg", "manifest.webmanifest", "og.png"];
|
||
|
|
const generatedFiles = Object.keys(bundle).filter((file) =>
|
||
|
|
/\.(?:html|js|css|svg|png|webmanifest)$/.test(file),
|
||
|
|
);
|
||
|
|
const files = [...new Set([...generatedFiles, ...publicFiles])].sort();
|
||
|
|
const digest = createHash("sha256");
|
||
|
|
for (const file of files) digest.update(file);
|
||
|
|
for (const file of publicFiles) {
|
||
|
|
digest.update(readFileSync(resolve(import.meta.dirname, "public", file)));
|
||
|
|
}
|
||
|
|
const cacheName = `wasmeld-${digest.digest("hex").slice(0, 16)}`;
|
||
|
|
const urls = files.map((file) => `/${file}`);
|
||
|
|
this.emitFile({
|
||
|
|
type: "asset",
|
||
|
|
fileName: "sw.js",
|
||
|
|
source: serviceWorkerSource(cacheName, urls),
|
||
|
|
});
|
||
|
|
},
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
function serviceWorkerSource(cacheName: string, urls: string[]): string {
|
||
|
|
// Runtime state must never be satisfied from an old cache. Only documents
|
||
|
|
// and build assets participate in offline behavior.
|
||
|
|
return `const CACHE = ${JSON.stringify(cacheName)};
|
||
|
|
const PRECACHE = ${JSON.stringify(urls)};
|
||
|
|
self.addEventListener("install", (event) => {
|
||
|
|
event.waitUntil(caches.open(CACHE).then((cache) => cache.addAll(PRECACHE)).then(() => self.skipWaiting()));
|
||
|
|
});
|
||
|
|
self.addEventListener("activate", (event) => {
|
||
|
|
event.waitUntil(caches.keys().then((keys) => Promise.all(keys.filter((key) => key.startsWith("wasmeld-") && key !== CACHE).map((key) => caches.delete(key)))).then(() => self.clients.claim()));
|
||
|
|
});
|
||
|
|
self.addEventListener("fetch", (event) => {
|
||
|
|
if (event.request.method !== "GET") return;
|
||
|
|
const url = new URL(event.request.url);
|
||
|
|
if (url.origin !== self.location.origin || url.pathname === "/healthz" || url.pathname.startsWith("/api/")) return;
|
||
|
|
if (event.request.mode === "navigate") {
|
||
|
|
event.respondWith(fetch(event.request).then((response) => {
|
||
|
|
const copy = response.clone();
|
||
|
|
caches.open(CACHE).then((cache) => cache.put(event.request, copy));
|
||
|
|
return response;
|
||
|
|
}).catch(async () => {
|
||
|
|
const cached = await caches.match(event.request);
|
||
|
|
if (cached) return cached;
|
||
|
|
return caches.match(url.pathname === "/page.html" ? "/page.html" : "/index.html");
|
||
|
|
}));
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
event.respondWith(caches.match(event.request).then((cached) => cached || fetch(event.request)));
|
||
|
|
});
|
||
|
|
`;
|
||
|
|
}
|