import { defineConfig, loadEnv, type Plugin } from 'vite'
import path from 'path'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'
// Use relative imports here. The '@' alias is configured in resolve.alias
// below and only takes effect during bundling — Node cannot resolve it when
// loading vite.config.ts. Bun resolves tsconfig paths natively, masking the
// issue, but Node does not.
import { normalizeApiPrefix, normalizeWebuiPrefix } from './src/lib/pathPrefix'
/**
* Inject `` into index.html.
*
* This mirrors what the FastAPI server does at request time in production
* (see `SmartStaticFiles._inject_runtime_config` in
* `lightrag/api/lightrag_server.py`). Doing it in dev too means the SPA
* always reads its prefix the same way, so behaviour matches between
* `bun run dev` and a production deploy.
*
* Only `VITE_DEV_API_PREFIX` is read; the WebUI mount path is fixed at
* `/webui` (matching the backend's hardcoded `WEBUI_PATH`), so the
* injected `webuiPrefix` follows the production formula
* `apiPrefix + "/webui/"` automatically.
*/
function lightragRuntimeConfigPlugin(env: Record): Plugin {
const apiPrefix = normalizeApiPrefix(env.VITE_DEV_API_PREFIX)
const webuiPrefix = normalizeWebuiPrefix(apiPrefix ? `${apiPrefix}/webui/` : '')
const payload = JSON.stringify({ apiPrefix, webuiPrefix }).replace(
/<\//g,
'<\\/'
)
const snippet = ``
return {
name: 'lightrag-dev-runtime-config',
apply: 'serve',
transformIndexHtml(html: string) {
return html.replace('', snippet)
}
}
}
// https://vite.dev/config/
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), '')
// Dev-only: prefix every proxied endpoint with the simulated site
// prefix so e.g. `/site01/documents/...` is forwarded to the backend
// running with LIGHTRAG_API_PREFIX=/site01.
const devApiPrefix = normalizeApiPrefix(env.VITE_DEV_API_PREFIX)
return {
plugins: [react(), tailwindcss(), lightragRuntimeConfigPlugin(env)],
resolve: {
alias: {
'@': path.resolve(__dirname, './src')
},
// Force all modules to use the same katex instance
// This ensures mhchem extension registered in main.tsx is available to rehype-katex
dedupe: ['katex']
},
// Relative base: asset URLs in index.html become `./assets/...` so the
// built bundle works under any reverse-proxy mount point. The browser
// resolves them against the current document URL — which means the
// server MUST serve index.html at a URL ending in '/' (the existing
// /webui → /webui/ redirect already handles this).
base: './',
build: {
outDir: path.resolve(__dirname, '../lightrag/api/webui'),
emptyOutDir: true,
chunkSizeWarningLimit: 3800,
rollupOptions: {
// Let Vite handle chunking automatically to avoid circular dependency issues
output: {
// Ensure consistent chunk naming format
chunkFileNames: 'assets/[name]-[hash].js',
// Entry file naming format
entryFileNames: 'assets/[name]-[hash].js',
// Asset file naming format
assetFileNames: 'assets/[name]-[hash].[ext]'
}
}
},
server: {
proxy: env.VITE_API_PROXY === 'true' && env.VITE_API_ENDPOINTS ?
Object.fromEntries(
env.VITE_API_ENDPOINTS.split(',').map(endpoint => [
devApiPrefix + endpoint,
{
target: env.VITE_BACKEND_URL || 'http://localhost:9621',
changeOrigin: true
// No rewrite: the backend already understands its own prefix
// via FastAPI's root_path, so forward the path verbatim.
}
])
) : {}
}
}
})