reverse-proxy/src/services/ViteService.ts
2025-06-12 01:33:06 -04:00

115 lines
3.8 KiB
TypeScript

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;
}
}