import { defineConfig, type Plugin, type UserConfig } from 'vite' import react from '@vitejs/plugin-react' import tsconfigPaths from 'vite-tsconfig-paths' import { VitePWA } from 'vite-plugin-pwa'; import * as path from 'path' // ✅ Import path module import { readFileSync, renameSync, existsSync, unlinkSync } from 'node:fs' import { fileURLToPath } from 'node:url' import { execSync } from 'node:child_process' const rootDir = path.dirname(fileURLToPath(import.meta.url)) const LOCALNET_MODE = 'localnet' /** Dev server: serve the minimal LAN shell (indexLocal.html) instead of the full index with third-party bootstraps. */ function useLocalnetShellHtml (mode: string, command: string): Plugin { if (mode !== LOCALNET_MODE || command !== 'serve') { return { name: 'localnet-shell-html-noop' } } return { name: 'localnet-shell-html', apply: 'serve', transformIndexHtml: { order: 'pre', handler () { return readFileSync(path.join(rootDir, 'indexLocal.html'), 'utf-8') }, }, } } /** After build, LAN shell is emitted as indexLocal.html; rename to index.html so nginx and PWA still use /. */ function emitLocalnetShellAsIndexHtml (mode: string): Plugin { if (mode !== LOCALNET_MODE) { return { name: 'emit-localnet-shell-as-index-noop' } } return { name: 'emit-localnet-shell-as-index-html', closeBundle () { const outDir = path.join(rootDir, 'build') const from = path.join(outDir, 'indexLocal.html') const to = path.join(outDir, 'index.html') if (existsSync(from)) { if (existsSync(to)) unlinkSync(to) renameSync(from, to) } }, } } // https://vitejs.dev/config/ export default defineConfig(({ command, mode }): UserConfig => { const useLocalnetShell = mode === LOCALNET_MODE let gitHash = 'unknown' try { gitHash = execSync('git rev-parse --short HEAD').toString().trim() } catch {} const buildDate = new Date().toISOString() return { define: { __BUILD_DATE__: JSON.stringify(buildDate), __GIT_HASH__: JSON.stringify(gitHash), }, plugins: [ useLocalnetShellHtml(mode, command), emitLocalnetShellAsIndexHtml(mode), react(), tsconfigPaths(), VitePWA({ registerType: 'autoUpdate', // Automatically updates the service worker when a new version is available workbox: { skipWaiting: true, //makes the new worker call skip waiting clientsClaim: true, //makes it claim control of clients as soon as it activates globPatterns: ['**/*.{js,css,html,png,jpg,svg}'], // Cache all common asset types maximumFileSizeToCacheInBytes: 20 * 1024 * 1024, // 20 MiB cleanupOutdatedCaches: true, navigateFallback: "/index.html", runtimeCaching: [ { urlPattern: ({ request }) => request.destination === 'document', handler: 'NetworkFirst', options: { cacheName: 'html-cache', }, }, { urlPattern: ({ request }) => ['style', 'script', 'worker'].includes(request.destination), handler: 'StaleWhileRevalidate', options: { cacheName: 'assets-cache', }, }, { //this forces all api calls to go through the network urlPattern: ({ url }) => url.pathname.startsWith('/v1/'), handler: 'NetworkOnly', options: { cacheName: 'api-cache', }, }, ] }, }), ], build: { outDir: './build', sourcemap: true, minify: false, target: 'esnext', rollupOptions: { input: { main: path.join( rootDir, useLocalnetShell ? 'indexLocal.html' : 'index.html' ), }, } }, esbuild: { keepNames: true, // Prevent function name mangling }, } })