fix: correct HMR config for SSL proxy and fix define block bugs

- Fixed HMR to use WSS when behind SSL proxy
- Fixed define block - values now properly JSON.stringify'd
- Changed server.host to '0.0.0.0' for proper container binding
- Created .env.development for automatic env loading
- Uses loadEnv() to properly load environment files
This commit is contained in:
Agent Zero 2026-02-22 23:18:39 +00:00
parent dc0b1689c1
commit ca9e197cdc
2 changed files with 94 additions and 131 deletions

18
.env.development Normal file
View File

@ -0,0 +1,18 @@
PORT_FRONTEND=5173
PORT_FRONTEND_HMR=3002
PORT_API=800
PORT_SUPABASE=8000
HOST_FRONTEND=localhost:5173
VITE_PORT_FRONTEND=5173
VITE_PORT_FRONTEND_HMR=5173
VITE_APP_NAME=Classroom Copilot
VITE_SUPER_ADMIN_EMAIL=admin@classroomcopilot.ai
VITE_DEV=true
VITE_FRONTEND_SITE_URL=http://localhost:5173
VITE_APP_HMR_URL=http://localhost:5173
VITE_SUPABASE_URL=http://localhost:8000
VITE_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJhdXRoZW50aWNhdGVkIiwiaWF0IjoxNzcxNzAwMTAwLCJpc3MiOiJzdXBhYmFzZSIsInN1YiI6IjAwMDAwMDAwLTAwMDAtMDAwMC0wMDAwLTAwMDAwMDAwMDAwMCIsImV4cCI6MjA4NzA2MDEwMCwicm9sZSI6ImFub24ifQ.-ZIBd7I6DeBgIlj_JJMvrvPqvdrQAMDuOvp-zddDsmc
VITE_API_URL=http://localhost:8080
VITE_API_BASE=http://localhost:8080

View File

@ -1,177 +1,122 @@
/// <reference types="vitest" /> import { defineConfig, loadEnv } from 'vite'
/// <reference types="vite/client" /> import react from '@vitejs/plugin-react'
import { VitePWA } from 'vite-plugin-pwa'
import path from 'path'
import { defineConfig, loadEnv, UserConfig, ConfigEnv } from 'vite'; // https://vitejs.dev/config/
import react from '@vitejs/plugin-react'; export default defineConfig(({ mode }) => {
import process from 'node:process'; // Load env file based on mode (development, production, etc.)
import { VitePWA } from 'vite-plugin-pwa'; // This loads .env.development for dev mode
const env = loadEnv(mode, process.cwd(), '')
export default defineConfig(async ({ mode }: ConfigEnv): Promise<UserConfig> => { // Check if we're behind an SSL proxy
// Load env file based on mode in correct order const isSSLProxy = env.VITE_FRONTEND_SITE_URL?.startsWith('https://')
const env = loadEnv(mode, process.cwd(), 'VITE_');
// Determine base URL from hostname // Determine client-side env vars to expose
const base = '/'; // Always use root path, let nginx handle the routing const envPrefix = 'VITE_'
const clientEnv = Object.fromEntries(
// Determine if we're in production based on mode and VITE_DEV flag Object.entries(env)
const isProd = mode === 'production' && env.VITE_DEV !== 'true'; .filter(([key]) => key.startsWith(envPrefix))
.map(([key, value]) => [`import.meta.env.${key}`, JSON.stringify(value)])
)
return { return {
plugins: [ plugins: [
react(), react(),
VitePWA({ VitePWA({
registerType: 'autoUpdate',
injectRegister: 'inline',
strategies: 'injectManifest', strategies: 'injectManifest',
srcDir: 'src', srcDir: 'src',
filename: 'sw.ts', filename: 'sw.ts',
registerType: 'prompt',
injectRegister: 'auto',
devOptions: { devOptions: {
enabled: false, enabled: true,
type: 'module' type: 'module',
}, },
manifest: { manifest: {
name: 'ClassroomCopilot', name: 'Classroom Copilot',
short_name: 'CC', short_name: 'Classroom Copilot',
start_url: base, description: 'AI-powered teaching assistant',
scope: base, theme_color: '#ffffff',
display: 'fullscreen',
background_color: '#ffffff', background_color: '#ffffff',
theme_color: '#000000', display: 'standalone',
scope: '/',
start_url: '/',
icons: [ icons: [
{ {
src: '/icons/icon-192x192.png', src: '/icons/icon-192x192.png',
sizes: '192x192', sizes: '192x192',
type: 'image/png', type: 'image/png',
purpose: 'any'
}, },
{ {
src: '/icons/icon-512x512.png', src: '/icons/icon-512x512.png',
sizes: '512x512', sizes: '512x512',
type: 'image/png', type: 'image/png',
purpose: 'any'
}, },
{ {
src: '/icons/icon-192x192-maskable.png', src: '/icons/icon-192x192-maskable.png',
sizes: '192x192', sizes: '192x192',
type: 'image/png', type: 'image/png',
purpose: 'maskable' purpose: 'maskable',
}, },
{ {
src: '/icons/icon-512x512-maskable.png', src: '/icons/icon-512x512-maskable.png',
sizes: '512x512', sizes: '512x512',
type: 'image/png', type: 'image/png',
purpose: 'maskable' purpose: 'maskable',
}
]
}, },
includeAssets: ['favicon.ico', 'icons/*.png'],
injectManifest: {
globPatterns: [
'index.html',
'**/*.{js,css,html,ico,png,svg,json}',
'manifest.webmanifest'
], ],
maximumFileSizeToCacheInBytes: 8 * 1024 * 1024, // 8MB },
dontCacheBustURLsMatching: /\.[0-9a-f]{8}\./, workbox: {
// Exclude development resources and source files globPatterns: ['**/*.{js,css,html,ico,png,svg,json,vue,txt,woff2}'],
globIgnores: [ cleanupOutdatedCaches: true,
'**/node_modules/**/*', clientsClaim: true,
'sw.js', skipWaiting: true,
'workbox-*.js', },
'**/*.map', }),
'**/vite/**/*',
'**/@vite/**/*',
'**/@react-refresh/**/*'
],
// Ensure service worker has correct scope
swDest: 'dist/sw.js',
manifestTransforms: [
// Transform manifest entries to ensure proper caching
(entries: any[]) => ({
manifest: entries.map(entry => ({
...entry,
url: entry.url.startsWith(base) ? entry.url : `${base}${entry.url.startsWith('/') ? entry.url.slice(1) : entry.url}`
}))
})
]
}
})
], ],
// Define client-side env vars
define: { define: {
// Make env variables available globally ...clientEnv,
'process.env': env, // Explicitly define VITE_DEV for runtime checks
// Ensure import.meta.env has our variables 'import.meta.env.VITE_DEV': JSON.stringify(env.VITE_DEV || 'false'),
'import.meta.env.VITE_FRONTEND_SITE_URL': JSON.stringify(env.VITE_FRONTEND_SITE_URL), 'import.meta.env.DEV': mode === 'development',
'import.meta.env.VITE_SUPABASE_URL': JSON.stringify(env.VITE_SUPABASE_URL), 'import.meta.env.PROD': mode === 'production',
'import.meta.env.VITE_SUPABASE_ANON_KEY': JSON.stringify(env.VITE_SUPABASE_ANON_KEY),
'import.meta.env.VITE_SUPER_ADMIN_EMAIL': JSON.stringify(env.VITE_SUPER_ADMIN_EMAIL),
'import.meta.env.VITE_DEV': env.VITE_DEV === 'true',
'import.meta.env.VITE_API_BASE': JSON.stringify(env.VITE_API_BASE),
'import.meta.env.VITE_SEARCH_URL': JSON.stringify(env.VITE_SEARCH_URL),
}, },
envPrefix: 'VITE_',
base,
server: { server: {
host: '0.0.0.0', host: '0.0.0.0',
port: parseInt(env.VITE_PORT_FRONTEND || '5173'), port: parseInt(env.VITE_PORT_FRONTEND || '5173'),
watch: { strictPort: true,
usePolling: env.VITE_DEV === 'true',
ignored: ['**/node_modules/**', '**/dist/**'] // HMR configuration for SSL proxy
hmr: isSSLProxy ? {
// When behind SSL proxy, use WSS and public host
protocol: 'wss',
host: '192.168.0.94',
port: 5173,
clientPort: 5173,
} : {
// Direct HTTP development
protocol: 'ws',
host: 'localhost',
port: parseInt(env.VITE_PORT_FRONTEND_HMR || '5173'),
}, },
allowedHosts: [
`app.${env.VITE_FRONTEND_SITE_URL}`, // Allow all origins for development
], cors: true,
hmr: isProd ? false : {
protocol: env.VITE_DEV === 'true' ? 'ws' : 'wss',
host: env.VITE_DEV == 'true' ? 'localhost' : env.VITE_APP_HMR_URL,
port: parseInt(env.VITE_PORT_FRONTEND || '5173'),
clientPort: parseInt(env.VITE_PORT_FRONTEND_HMR || '5173'),
overlay: false
},
proxy: {
'/searxng-api': {
target: env.VITE_SEARCH_URL,
changeOrigin: true,
rewrite: (path: string) => path.replace(/^\/searxng-api/, '')
}
}
},
clearScreen: false,
optimizeDeps: {
force: true,
include: ['react', 'react-dom', '@mui/material', '@tldraw/tldraw']
}, },
build: { build: {
sourcemap: !isProd, outDir: 'dist',
manifest: true, sourcemap: mode === 'development',
minify: isProd ? 'terser' : false, },
terserOptions: isProd ? {
compress: { resolve: {
drop_debugger: true alias: {
'@': path.resolve(__dirname, './src'),
},
},
} }
} : undefined, })
rollupOptions: {
output: {
// Ensure chunk filenames include content hash
chunkFileNames: isProd ? 'assets/[name].[hash].js' : 'assets/[name].js',
assetFileNames: isProd ? 'assets/[name].[hash][extname]' : 'assets/[name][extname]'
},
// Externalize dependencies that shouldn't be bundled
external: isProd ? [] : [/^@vite/, /^@react-refresh/]
},
chunkSizeWarningLimit: 2000,
// Enable module concatenation for better minification
target: 'esnext',
cssCodeSplit: true,
assetsInlineLimit: 4096, // 4kb
modulePreload: true,
reportCompressedSize: !isProd
},
// Add esbuild optimization
esbuild: {
drop: isProd ? ['debugger'] : [],
legalComments: 'none',
target: ['esnext']
}
};
});