import { defineConfig, type Plugin } from 'vite'; import { svelte } from '@sveltejs/vite-plugin-svelte'; import { createHash } from 'node:crypto'; const version = process.env.TEFTER_VERSION || 'dev'; // Emits sw.js at build time with the precache list of all emitted assets, // versioned by a hash of the file names (which themselves carry content hashes). function serviceWorker(): Plugin { return { name: 'tefter-sw', apply: 'build', generateBundle(_opts, bundle) { const assets = Object.keys(bundle) .filter((f) => !f.endsWith('.map')) .map((f) => '/' + f); assets.push('/', '/manifest.webmanifest', '/icon-192.png', '/icon-512.png'); const hash = createHash('sha256').update(assets.join(',') + version).digest('hex').slice(0, 12); const sw = `// generated by vite.config.ts — do not edit const CACHE = 'tefter-${'${'}HASH}'; const ASSETS = ${'${'}ASSETS}; self.addEventListener('install', (e) => { e.waitUntil(caches.open(CACHE).then((c) => c.addAll(ASSETS)).then(() => self.skipWaiting())); }); self.addEventListener('activate', (e) => { e.waitUntil( caches.keys() .then((keys) => Promise.all(keys.filter((k) => k !== CACHE).map((k) => caches.delete(k)))) .then(() => self.clients.claim()) ); }); self.addEventListener('fetch', (e) => { const url = new URL(e.request.url); if (e.request.method !== 'GET' || url.origin !== location.origin) return; if (url.pathname.startsWith('/api/')) return; // network-only; sync engine handles offline e.respondWith( caches.match(url.pathname === '/' || url.pathname === '/index.html' ? '/' : e.request).then( (hit) => hit || fetch(e.request) ) ); }); ` .replace('${HASH}', hash) .replace('${ASSETS}', JSON.stringify(assets)); this.emitFile({ type: 'asset', fileName: 'sw.js', source: sw }); }, }; } export default defineConfig({ plugins: [svelte(), serviceWorker()], define: { __TEFTER_VERSION__: JSON.stringify(version), }, build: { target: 'es2020', sourcemap: false, }, server: { proxy: { '/api': 'http://127.0.0.1:8420', }, }, });