116 lines
3.8 KiB
TypeScript
116 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;
|
||
|
|
}
|
||
|
|
}
|