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" />
/// <reference types="vite/client" />
import { defineConfig, loadEnv } from 'vite'
import react from '@vitejs/plugin-react'
import { VitePWA } from 'vite-plugin-pwa'
import path from 'path'
import { defineConfig, loadEnv, UserConfig, ConfigEnv } from 'vite';
import react from '@vitejs/plugin-react';
import process from 'node:process';
import { VitePWA } from 'vite-plugin-pwa';
// https://vitejs.dev/config/
export default defineConfig(({ mode }) => {
// Load env file based on mode (development, production, etc.)
// This loads .env.development for dev mode
const env = loadEnv(mode, process.cwd(), '')
export default defineConfig(async ({ mode }: ConfigEnv): Promise<UserConfig> => {
// Load env file based on mode in correct order
const env = loadEnv(mode, process.cwd(), 'VITE_');
// Check if we're behind an SSL proxy
const isSSLProxy = env.VITE_FRONTEND_SITE_URL?.startsWith('https://')
// Determine base URL from hostname
const base = '/'; // Always use root path, let nginx handle the routing
// Determine if we're in production based on mode and VITE_DEV flag
const isProd = mode === 'production' && env.VITE_DEV !== 'true';
// Determine client-side env vars to expose
const envPrefix = 'VITE_'
const clientEnv = Object.fromEntries(
Object.entries(env)
.filter(([key]) => key.startsWith(envPrefix))
.map(([key, value]) => [`import.meta.env.${key}`, JSON.stringify(value)])
)
return {
plugins: [
react(),
VitePWA({
registerType: 'autoUpdate',
injectRegister: 'inline',
strategies: 'injectManifest',
srcDir: 'src',
filename: 'sw.ts',
registerType: 'prompt',
injectRegister: 'auto',
devOptions: {
enabled: false,
type: 'module'
enabled: true,
type: 'module',
},
manifest: {
name: 'ClassroomCopilot',
short_name: 'CC',
start_url: base,
scope: base,
display: 'fullscreen',
name: 'Classroom Copilot',
short_name: 'Classroom Copilot',
description: 'AI-powered teaching assistant',
theme_color: '#ffffff',
background_color: '#ffffff',
theme_color: '#000000',
display: 'standalone',
scope: '/',
start_url: '/',
icons: [
{
src: '/icons/icon-192x192.png',
sizes: '192x192',
type: 'image/png',
purpose: 'any'
},
{
src: '/icons/icon-512x512.png',
sizes: '512x512',
type: 'image/png',
purpose: 'any'
},
{
src: '/icons/icon-192x192-maskable.png',
sizes: '192x192',
type: 'image/png',
purpose: 'maskable'
purpose: 'maskable',
},
{
src: '/icons/icon-512x512-maskable.png',
sizes: '512x512',
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}\./,
// Exclude development resources and source files
globIgnores: [
'**/node_modules/**/*',
'sw.js',
'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}`
}))
})
]
}
})
},
workbox: {
globPatterns: ['**/*.{js,css,html,ico,png,svg,json,vue,txt,woff2}'],
cleanupOutdatedCaches: true,
clientsClaim: true,
skipWaiting: true,
},
}),
],
// Define client-side env vars
define: {
// Make env variables available globally
'process.env': env,
// Ensure import.meta.env has our variables
'import.meta.env.VITE_FRONTEND_SITE_URL': JSON.stringify(env.VITE_FRONTEND_SITE_URL),
'import.meta.env.VITE_SUPABASE_URL': JSON.stringify(env.VITE_SUPABASE_URL),
'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),
...clientEnv,
// Explicitly define VITE_DEV for runtime checks
'import.meta.env.VITE_DEV': JSON.stringify(env.VITE_DEV || 'false'),
'import.meta.env.DEV': mode === 'development',
'import.meta.env.PROD': mode === 'production',
},
envPrefix: 'VITE_',
base,
server: {
host: '0.0.0.0',
port: parseInt(env.VITE_PORT_FRONTEND || '5173'),
watch: {
usePolling: env.VITE_DEV === 'true',
ignored: ['**/node_modules/**', '**/dist/**']
strictPort: true,
// 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}`,
],
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']
// Allow all origins for development
cors: true,
},
build: {
sourcemap: !isProd,
manifest: true,
minify: isProd ? 'terser' : false,
terserOptions: isProd ? {
compress: {
drop_debugger: true
outDir: 'dist',
sourcemap: mode === 'development',
},
resolve: {
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']
}
};
});
})