import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' import { VitePWA } from 'vite-plugin-pwa' export default defineConfig({ plugins: [ react(), VitePWA({ registerType: 'autoUpdate', includeAssets: ['favicon-32.png', 'apple-touch-icon.png'], manifest: { name: 'Muzick', short_name: 'Muzick', description: 'Self-hosted music player and recommendation engine', start_url: '/', scope: '/', display: 'standalone', orientation: 'portrait', background_color: '#14110D', theme_color: '#14110D', icons: [ { src: '/icon-192.png', sizes: '192x192', type: 'image/png' }, { src: '/icon-512.png', sizes: '512x512', type: 'image/png' }, { src: '/icon-maskable-512.png', sizes: '512x512', type: 'image/png', purpose: 'maskable', }, ], }, workbox: { globPatterns: ['**/*.{js,css,html,woff2}'], // The SPA fallback must never swallow the API: /api/playback/stream is a // long-lived SSE channel and the track endpoints stream audio. Both look // like navigations to Workbox unless they are denied here. navigateFallback: '/index.html', navigateFallbackDenylist: [/^\/api\//], // A waiting service worker would otherwise activate and reload the page // mid-song. Letting it wait means updates land on the next cold start, // which is the right trade for a player that runs in the background. skipWaiting: false, clientsClaim: false, runtimeCaching: [ { // Artwork only. Audio is deliberately absent: range requests and // multi-megabyte bodies do not belong in the shell cache. urlPattern: ({ request }) => request.destination === 'image', handler: 'CacheFirst', options: { cacheName: 'muzick-artwork', expiration: { maxEntries: 300, maxAgeSeconds: 60 * 60 * 24 * 30 }, cacheableResponse: { statuses: [0, 200] }, }, }, ], }, }), ], test: { environment: 'jsdom', setupFiles: './src/test/setup.ts', clearMocks: true, }, server: { proxy: { // ponytail: the deployed nginx injects the Authorization header, so dev can // point at it (DEV_API_TARGET=http://localhost:5174) instead of a bare backend. '/api': process.env.DEV_API_TARGET || 'http://localhost:3000', } }, build: { outDir: 'dist', emptyOutDir: true, rollupOptions: { output: { // The framework changes on an npm upgrade, the app changes every // deploy. Splitting them means a redeploy re-downloads app code only, // which matters here because the service worker precaches the lot. manualChunks: { vendor: ['react', 'react-dom', '@tanstack/react-router', '@tanstack/react-query'], }, }, }, } })