225 lines
8.6 KiB
TypeScript
225 lines
8.6 KiB
TypeScript
import { defineConfig } from "vite";
|
|
import react from "@vitejs/plugin-react";
|
|
import svgr from "vite-plugin-svgr";
|
|
import { resolve } from "path";
|
|
// Test comment: webhook restart test
|
|
|
|
// Custom plugin to bypass Vite 6.1.0 host check bug
|
|
// The host check middleware runs early, so we intercept and remove it
|
|
const bypassHostCheck = () => {
|
|
return {
|
|
name: 'bypass-host-check',
|
|
configureServer(server) {
|
|
// Remove host check middleware by intercepting the middleware stack
|
|
const originalUse = server.middlewares.use.bind(server.middlewares);
|
|
server.middlewares.use = function(path, ...handlers) {
|
|
// Filter out any handler that checks for allowedHosts
|
|
const filteredHandlers = handlers.filter(handler => {
|
|
if (typeof handler === 'function') {
|
|
const handlerStr = handler.toString();
|
|
// Skip handlers that check for "allowedHosts" or "not allowed"
|
|
if (handlerStr.includes('allowedHosts') || handlerStr.includes('not allowed')) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
});
|
|
return originalUse(path, ...filteredHandlers);
|
|
};
|
|
|
|
// Also remove from existing stack
|
|
if (Array.isArray(server.middlewares.stack)) {
|
|
server.middlewares.stack = server.middlewares.stack.filter(middleware => {
|
|
if (middleware && middleware.handle) {
|
|
const handlerStr = middleware.handle.toString();
|
|
return !handlerStr.includes('allowedHosts') && !handlerStr.includes('not allowed');
|
|
}
|
|
return true;
|
|
});
|
|
}
|
|
}
|
|
};
|
|
};
|
|
|
|
// Plugin to serve marketing.html for SPA routing in marketing dev server
|
|
const serveMarketingSPA = () => {
|
|
return {
|
|
name: 'serve-marketing-spa',
|
|
configureServer(server) {
|
|
const isMarketingDev = server.config.server.port === 5174;
|
|
|
|
if (isMarketingDev) {
|
|
// Handle SPA routing: serve marketing.html for all routes that don't match actual files
|
|
server.middlewares.use((req, res, next) => {
|
|
const url = req.url?.split('?')[0] || '/'; // Remove query string
|
|
|
|
// Skip for Vite internal paths and static assets
|
|
if (
|
|
url.startsWith('/@') || // Vite internal (@vite/client, @fs, etc.)
|
|
url.startsWith('/node_modules') || // Node modules
|
|
url.startsWith('/src') || // Source files
|
|
url.startsWith('/api') || // API calls
|
|
url === '/marketing.html' // Already the marketing HTML
|
|
) {
|
|
return next();
|
|
}
|
|
|
|
// Check if URL has a file extension (static asset)
|
|
const hasFileExtension = /\.\w+$/.test(url);
|
|
if (hasFileExtension) {
|
|
return next(); // Let Vite handle static assets
|
|
}
|
|
|
|
// For all route-like paths (no extension), serve marketing.html
|
|
// This enables SPA routing for direct URL access (e.g., /product, /pricing)
|
|
// and browser back/forward navigation
|
|
req.url = '/marketing.html';
|
|
next();
|
|
});
|
|
}
|
|
}
|
|
};
|
|
};
|
|
|
|
// https://vite.dev/config/
|
|
export default defineConfig(({ mode, command }) => {
|
|
// Always enable dev server config when running dev command (serve)
|
|
const isDev = mode === "development" || command === "serve";
|
|
const isMarketingBuild = mode === "marketing";
|
|
|
|
// Check if we're running marketing dev server (port 5174)
|
|
const isMarketingDev = process.env.PORT === "5174" || process.argv.includes("--port") && process.argv[process.argv.indexOf("--port") + 1] === "5174";
|
|
|
|
return {
|
|
// Set root HTML file for marketing dev server
|
|
root: __dirname,
|
|
publicDir: "public",
|
|
plugins: [
|
|
bypassHostCheck(), // Workaround for Vite 6.1.0 host check bug
|
|
...(process.env.PORT === "5174" || process.argv.includes("--port") && process.argv[process.argv.indexOf("--port") + 1] === "5174" ? [serveMarketingSPA()] : []),
|
|
react(),
|
|
svgr({
|
|
svgrOptions: {
|
|
icon: true,
|
|
// This will transform your SVG to a React component
|
|
exportType: "named",
|
|
namedExport: "ReactComponent",
|
|
},
|
|
}),
|
|
],
|
|
// Resolve configuration to prevent duplicate React instances
|
|
resolve: {
|
|
dedupe: ['react', 'react-dom'],
|
|
alias: {
|
|
react: resolve(__dirname, 'node_modules/react'),
|
|
'react-dom': resolve(__dirname, 'node_modules/react-dom'),
|
|
},
|
|
},
|
|
// Optimize dependency pre-bundling
|
|
optimizeDeps: {
|
|
include: [
|
|
// Core React dependencies - prevent multiple instances
|
|
"react",
|
|
"react-dom",
|
|
"react/jsx-runtime",
|
|
// Only pre-bundle frequently used, small dependencies
|
|
"clsx",
|
|
"tailwind-merge",
|
|
"zustand",
|
|
"zustand/middleware", // Required for persist middleware used in stores
|
|
// Include apexcharts for proper module resolution (skip during marketing-only build)
|
|
...(isMarketingBuild ? [] : ["apexcharts", "react-apexcharts"]),
|
|
// Force include fast-deep-equal for react-dnd compatibility
|
|
"fast-deep-equal",
|
|
],
|
|
// Exclude heavy dependencies that are only used in specific pages
|
|
// They will be lazy-loaded when needed
|
|
exclude: [
|
|
"@fullcalendar/core",
|
|
"@fullcalendar/daygrid",
|
|
"@fullcalendar/interaction",
|
|
"@fullcalendar/list",
|
|
"@fullcalendar/react",
|
|
"@fullcalendar/timegrid",
|
|
"@react-jvectormap/core",
|
|
"@react-jvectormap/world",
|
|
"swiper",
|
|
],
|
|
esbuildOptions: {
|
|
target: 'es2020',
|
|
},
|
|
},
|
|
// Build configuration for code splitting
|
|
build: {
|
|
// Enable minification (esbuild is faster than terser)
|
|
minify: 'esbuild',
|
|
// Enable CSS code splitting
|
|
cssCodeSplit: true,
|
|
// Optimize chunk size
|
|
rollupOptions: {
|
|
input: isMarketingBuild
|
|
? {
|
|
marketing: resolve(__dirname, "marketing.html"),
|
|
}
|
|
: {
|
|
main: resolve(__dirname, "index.html"),
|
|
marketing: resolve(__dirname, "marketing.html"),
|
|
},
|
|
output: {
|
|
// Optimize chunk file names
|
|
chunkFileNames: 'assets/js/[name]-[hash].js',
|
|
entryFileNames: 'assets/js/[name]-[hash].js',
|
|
assetFileNames: 'assets/[ext]/[name]-[hash].[ext]',
|
|
},
|
|
},
|
|
// Increase chunk size warning limit (default is 500kb)
|
|
chunkSizeWarningLimit: 600,
|
|
// Enable source maps in dev only
|
|
sourcemap: isDev,
|
|
// Report compressed sizes
|
|
reportCompressedSize: true,
|
|
},
|
|
// Server configuration - ALWAYS apply (not conditional)
|
|
// This ensures server config is loaded even with --force flag
|
|
server: {
|
|
host: "0.0.0.0", // Allow external connections (for Docker)
|
|
port: isMarketingBuild ? 5174 : 5173, // Different port for marketing dev
|
|
strictPort: false, // Allow port fallback if port is busy
|
|
// Vite 6.1.0: Host check is skipped if allowedHosts === true
|
|
// Set to true to allow all hosts (safe behind Caddy reverse proxy)
|
|
// Note: We patched Vite source to always skip host check, but keeping this for safety
|
|
allowedHosts: true,
|
|
watch: {
|
|
usePolling: true, // Needed for file watching in Docker
|
|
ignored: [
|
|
'**/node_modules/**',
|
|
'**/.git/**', // CRITICAL: Ignore git directory to prevent restart on commits
|
|
'**/dist/**',
|
|
'**/build/**',
|
|
'**/.vscode/**',
|
|
'**/.idea/**',
|
|
'**/Dockerfile*', // Ignore all Dockerfiles
|
|
'**/Caddyfile*', // Ignore Caddyfiles
|
|
'**/*.md', // Ignore markdown files
|
|
'**/container_startup.sh', // Ignore startup script
|
|
'**/.dockerignore',
|
|
'**/.gitignore',
|
|
],
|
|
interval: 1000, // Poll every 1 second (default is 100ms)
|
|
},
|
|
hmr: {
|
|
// Behind reverse proxy - explicitly set host and port to public domain
|
|
// Client connects via public domain on default HTTPS port (443)
|
|
// Caddy automatically proxies WebSocket upgrades from 443 to dev port
|
|
protocol: "wss",
|
|
host: isMarketingDev ? "igny8.com" : "app.igny8.com", // Marketing uses igny8.com, main app uses app.igny8.com
|
|
clientPort: 443, // Explicitly set to 443 (HTTPS default) to prevent localhost fallback
|
|
// This ensures client connects to wss://domain.com:443
|
|
// and prevents fallback to localhost:5173/5174
|
|
},
|
|
// Increase timeout for slow connections and dependency pre-bundling
|
|
timeout: 120000, // 120 seconds (2 minutes) to match Caddy timeout
|
|
},
|
|
};
|
|
});
|