import express from 'express'; import fs from 'fs/promises'; import react from '@vitejs/plugin-react'; import tailwindcss from '@tailwindcss/vite'; import path from 'path'; import { fileURLToPath } from 'url'; import logger from '../utils/logger.js'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); export class ViteService { private static server: any = null; static async setupMiddleware(app: express.Application, isProduction: boolean = false, base: string = '/') { try { const webRoot = path.resolve(__dirname, '../web'); const distDir = path.resolve(__dirname, '../../dist/web'); if (!isProduction) { // Add CSP middleware for development app.use((req, res, next) => { res.setHeader('Content-Security-Policy', "default-src 'self'; " + "script-src 'self' 'unsafe-inline' 'unsafe-eval'; " + "connect-src 'self' ws: wss: http: https:; " + "style-src 'self' 'unsafe-inline';" ); next(); }); // Create vite server const vite = await import('vite'); const viteServer = await vite.createServer({ plugins: [react(), tailwindcss()], root: webRoot, server: { middlewareMode: true }, appType: 'spa', base, build: { outDir: distDir, emptyOutDir: true, }, resolve: { alias: { '@': webRoot, }, }, }); app.use(base, viteServer.middlewares); this.server = viteServer; logger.info(`🎨 Vite dev middleware setup on ${base}`); } else { // Production mode - serve static files const publicDir = distDir; // Serve static assets app.use(base, express.static(publicDir)); logger.info(`📦 Static files served from ${publicDir} on ${base}`); } // SPA fallback middleware - works for both dev and prod, but only for non-API routes app.use((req, res, next) => { // Skip API routes and static assets if (req.path.startsWith('/api/') || req.path.includes('.')) { return next(); } if (!isProduction) { // In development, let Vite handle SPA routing return next(); } // In production, serve index.html for SPA routes const indexPath = path.join(distDir, 'index.html'); fs.readFile(indexPath, 'utf-8') .then(index => { res.set('content-type', 'text/html'); res.send(index); }) .catch(error => { logger.error('Failed to serve index.html:', error); res.status(404).send('Frontend not built. Please run build first.'); }); }); } catch (error) { logger.error('Failed to setup Vite middleware:', error); throw error; } } static async stop() { if (this.server) { await this.server.close(); logger.info('Vite server stopped'); this.server = null; } } static isRunning() { return this.server !== null; } // Legacy method for backward compatibility static async startDevServer(port: number = 3001) { logger.warn('startDevServer is deprecated. Use setupMiddleware instead.'); return null; } }