import path from "node:path"; import vue from "@vitejs/plugin-vue"; import { defineConfig, type Plugin } from "vite"; import { VitePWA } from "vite-plugin-pwa"; /** * Recover browsers that visited this hostname while Docker still served Vite's * development mode. Its development worker can retain HTML that imports * `/@vite/client` and `/src/main.ts`; a normal static fallback would return * HTML for those module URLs and leave the visitor on a blank screen. * * This preview-only compatibility endpoint is requested only by that obsolete * shell. It clears the old origin-local worker/cache state and writes the * current production HTML into the document. The production application then * registers the real `/sw.js` worker normally. */ function legacyDevelopmentWorkerMigration(): Plugin { const migrationSource = ` void (async () => { if ("serviceWorker" in navigator) { const registrations = await navigator.serviceWorker.getRegistrations(); await Promise.all(registrations.map((registration) => registration.unregister())); } if ("caches" in window) { const names = await caches.keys(); await Promise.all(names.map((name) => caches.delete(name))); } const productionDocument = await fetch( "/index.html?origin-pwa-migration=" + Date.now(), { cache: "no-store" }, ); const html = await productionDocument.text(); document.open(); document.write(html); document.close(); })(); `; return { name: "origins-legacy-development-worker-migration", configurePreviewServer(server) { server.middlewares.use((request, response, next) => { const pathname = new URL(request.url ?? "/", "http://origins.local") .pathname; if (pathname === "/@vite/client") { response.statusCode = 200; response.setHeader("Content-Type", "text/javascript; charset=utf-8"); response.setHeader("Cache-Control", "no-store"); response.end( "// Compatibility stub for the retired Vite dev client.", ); return; } if (pathname === "/src/main.ts") { response.statusCode = 200; response.setHeader("Content-Type", "text/javascript; charset=utf-8"); response.setHeader("Cache-Control", "no-store"); response.end(migrationSource); return; } next(); }); }, }; } export default defineConfig({ root: __dirname, base: "./", // Reuse the workspace's existing install icons rather than maintaining a // second set that can silently drift from the main demo. publicDir: path.resolve(__dirname, "../../public"), plugins: [ vue(), legacyDevelopmentWorkerMigration(), VitePWA({ registerType: "autoUpdate", devOptions: { // The hosted Docker service runs Vite's development server, so its // public HTTPS URL also needs a real manifest and service worker. enabled: true, navigateFallback: "index.html", suppressWarnings: true, }, includeAssets: [ "favicon.svg", "app-icon.svg", "apple-touch-icon.png", "pwa-192.png", "pwa-512.png", ], manifest: { id: "/", name: "Routeless Origins Lab", short_name: "Origins Lab", description: "Seven interactive labs for a routeless Vue animation and gesture engine.", theme_color: "#080b12", background_color: "#080b12", display: "standalone", scope: "/", start_url: "/", orientation: "any", icons: [ { src: "/pwa-192.png", sizes: "192x192", type: "image/png", purpose: "any", }, { src: "/pwa-512.png", sizes: "512x512", type: "image/png", purpose: "any", }, { src: "/pwa-512.png", sizes: "512x512", type: "image/png", purpose: "maskable", }, { src: "/app-icon.svg", sizes: "any", type: "image/svg+xml", purpose: "any", }, ], }, workbox: { cleanupOutdatedCaches: true, clientsClaim: true, skipWaiting: true, navigateFallback: "/index.html", globPatterns: ["**/*.{js,css,html,svg,png,woff2}"], }, }), ], server: { // The development container is reached through this Traefik hostname. // Keeping the allow-list explicit preserves Vite's DNS-rebinding defense. allowedHosts: ["v2.demo.native-router.harvmaster.com"], }, preview: { allowedHosts: ["v2.demo.native-router.harvmaster.com"], }, resolve: { alias: [ { find: /^@native-vue-router\/core-v2\/style\.css$/, replacement: path.resolve( __dirname, "../../packages/core-v2/src/style.css", ), }, { find: /^@native-vue-router\/core-v2$/, replacement: path.resolve( __dirname, "../../packages/core-v2/src/index.ts", ), }, ], }, build: { outDir: path.resolve(__dirname, "dist"), emptyOutDir: true, }, });