import { defineConfig, type Plugin } from 'vite'; import react from '@vitejs/plugin-react'; import tailwindcss from '@tailwindcss/vite'; import path from 'path'; import { readFileSync } from 'fs'; // Vite invalidates its dep prebundle by hashing the nearest lockfile up from root — which in a // production install is the WORKSPACE's lockfile (backend deps only). `bloby update` swaps // frontend deps in the root node_modules without ever touching that lockfile, so prebundles // silently went stale across updates. Keying the cache dir by package version forces exactly one // clean re-optimize per release while keeping persistence across normal restarts. // (vite-dev.ts sweeps old .vite-v* dirs at boot.) let cacheVersion = 'dev'; try { cacheVersion = JSON.parse(readFileSync(path.resolve(__dirname, 'package.json'), 'utf-8')).version || 'dev'; } catch {} const workspaceClient = process.env.BLOBY_WORKSPACE ? path.join(process.env.BLOBY_WORKSPACE, 'client') : path.resolve(__dirname, 'workspace/client'); // Backs workspace-guard.js's reconnect staleness check: Vite's reload-on-reconnect // is suppressed client-side, and on reconnect the client fetches this stamp and // reloads ONLY if frontend code actually changed while it was disconnected. // Seeded with Date.now() (not 0) so a Vite/supervisor restart — which resets this // module — always reads as "changed" → clients reload after restarts, which is // exactly right (in-memory HMR state is gone). function blobyFeStamp(): Plugin { let stamp = Date.now(); return { name: 'bloby-fe-stamp', // hotUpdate (not legacy handleHotUpdate): fires on create/update/delete and // for .html edits that trigger full-reload. It runs once per environment, so // only count the client one. hotUpdate() { if (this.environment.name !== 'client') return; stamp++; }, configureServer(server) { // Reached through the supervisor's catch-all proxy to Vite. connect mounts // by prefix, so reject subpaths to keep this an exact match. server.middlewares.use('/__bloby/fe-stamp', (req, res, next) => { const rest = (req.url || '').split('?')[0]; if (rest !== '' && rest !== '/') return next(); res.setHeader('Content-Type', 'application/json'); res.setHeader('Cache-Control', 'no-store'); res.end(JSON.stringify({ stamp })); }); }, }; } export default defineConfig({ root: process.env.BLOBY_WORKSPACE ? path.join(process.env.BLOBY_WORKSPACE, 'client') : 'workspace/client', cacheDir: path.join(workspaceClient, 'node_modules', '.vite-v' + cacheVersion), resolve: { alias: { '@': process.env.BLOBY_WORKSPACE ? path.join(process.env.BLOBY_WORKSPACE, 'client/src') : path.resolve(__dirname, 'workspace/client/src'), }, // Prevent React dual-instance when workspace/node_modules has its own copy dedupe: ['react', 'react-dom'], }, build: { outDir: '../../dist', emptyOutDir: true, }, server: { port: 5173, proxy: { '/app/api': { target: 'http://localhost:7404', rewrite: (path) => path.replace(/^\/app/, ''), }, '/api': 'http://localhost:7400', }, warmup: { // The whole client graph, not just the entry — the old single-file warmup only // pre-transformed main.tsx, so the first browser hit still paid transform time for // every other module (felt hardest on Pi-class hardware after a restart). clientFiles: ['./src/main.tsx', './src/**/*.tsx', './src/**/*.ts', './src/**/*.css'], }, watch: { ignored: [ '**/app.db*', '**/.backend.log', '**/files/**', '**/.env', '**/backend/**', '**/*.db', '**/*.db-journal', '**/*.db-wal', '**/*.db-shm', '**/*.sqlite', '**/*.log', ], }, }, optimizeDeps: { include: [ 'react', 'react-dom', 'react-dom/client', 'react/jsx-runtime', 'react-router', 'driver.js', 'lucide-react', 'framer-motion', 'recharts', 'zustand', 'sonner', 'use-sync-external-store', 'use-sync-external-store/shim', // Statically imported by the workspace template's UI kit (button.tsx etc.) but missing // here — the first request for an un-prebundled dep triggers a mid-session // re-optimization AND a Vite-forced full page reload. Pre-bundle them up front. 'radix-ui', 'class-variance-authority', 'clsx', 'tailwind-merge', ], }, plugins: [react(), tailwindcss(), blobyFeStamp()], });