Files
wasmeld/crates/wasmeld-console/web/vite.config.ts
T
Maofeng 70ee5e33da feat(console-web): replace TanStack UI with Solid host pages
- move the management frontend under the wasmeld-console crate
- use a persistent Host shell with disposable iframe page documents
- version Host/Page postMessage state and command contracts
- migrate runtime, service, WIT, invocation, and settings workflows
- add Tailwind CSS v4, responsive layouts, and Host-owned dialogs
- emit a dependency-free PWA worker with API cache exclusions
- remove the superseded root TanStack Start application
2026-07-30 09:22:55 +08:00

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)));
});
`;
}