mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-09-30 03:01:21 +02:00
Vite's default base of '/' baked absolute asset URLs into the built
index.html (/assets/..., /manifest.json, /img/..., /sw-register.js),
so any path-prefixed reverse proxy (Traefik, nginx subpath, Cloudflare
Tunnel with path routing) served the SPA as a blank white page — the
browser requested assets from the host root and got HTML or text/plain
back, triggering MIME-mismatch errors on every stylesheet/script.
Set base: '' in vite.config.ts so the HTML transform emits relative
URLs everywhere. Update public/sw-register.js to register('sw.js')
(relative) so SW scope auto-pins to whatever subpath the document
loaded from.
Out of scope: API_BASE in client.ts is still absolute. The supported
HA embedding path remains Webpage panel + TRUSTED_FRAME_ORIGINS, not
HA Ingress (subpath-aware SPA bootstrapping has too many failure modes
around PWA scope, push subscriptions, and deep-link reloads to take on
in core). Documented explicitly in docker.md.
Reported by @Spegeli, follow-up to #1167.
107 lines
3.0 KiB
TypeScript
107 lines
3.0 KiB
TypeScript
import { defineConfig } from 'vite'
|
|
import react from '@vitejs/plugin-react'
|
|
import path from 'path'
|
|
import fs from 'fs'
|
|
import type { Connect } from 'vite'
|
|
|
|
// Backend port for dev server proxy (default: 8000)
|
|
const backendPort = process.env.BACKEND_PORT || '8000'
|
|
const backendUrl = `http://localhost:${backendPort}`
|
|
|
|
// Absolute path to the gcode_viewer directory at the repo root
|
|
const gcodeViewerDir = path.resolve(__dirname, '../gcode_viewer')
|
|
|
|
// MIME types for static files served from gcode_viewer/
|
|
const MIME: Record<string, string> = {
|
|
'.html': 'text/html; charset=utf-8',
|
|
'.js': 'application/javascript',
|
|
'.css': 'text/css',
|
|
'.obj': 'model/obj',
|
|
'.mtl': 'model/mtl',
|
|
'.png': 'image/png',
|
|
'.jpg': 'image/jpeg',
|
|
'.svg': 'image/svg+xml',
|
|
'.json': 'application/json',
|
|
'.woff': 'font/woff',
|
|
'.woff2':'font/woff2',
|
|
}
|
|
|
|
/**
|
|
* Vite dev-server plugin: serves ../gcode_viewer/ at /gcode-viewer/
|
|
* without needing a proxy to uvicorn. In production uvicorn handles it
|
|
* via the StaticFiles mount in main.py.
|
|
*/
|
|
function serveGcodeViewer() {
|
|
return {
|
|
name: 'serve-gcode-viewer',
|
|
configureServer(server: { middlewares: Connect.Server }) {
|
|
server.middlewares.use((req, res, next) => {
|
|
const url = req.url ?? ''
|
|
if (!url.startsWith('/gcode-viewer')) return next()
|
|
|
|
// Strip prefix, default to index.html
|
|
let rel = url.slice('/gcode-viewer'.length)
|
|
if (rel === '' || rel === '/') rel = '/index.html'
|
|
// Strip query string
|
|
rel = rel.split('?')[0]
|
|
|
|
const absPath = path.join(gcodeViewerDir, rel)
|
|
|
|
try {
|
|
const stat = fs.statSync(absPath)
|
|
if (stat.isFile()) {
|
|
const ext = path.extname(absPath).toLowerCase()
|
|
res.setHeader('Content-Type', MIME[ext] ?? 'application/octet-stream')
|
|
res.end(fs.readFileSync(absPath))
|
|
return
|
|
}
|
|
} catch {
|
|
// file not found — fall through to index.html
|
|
}
|
|
|
|
// SPA fallback: serve index.html for any unmatched /gcode-viewer/* path
|
|
const index = path.join(gcodeViewerDir, 'index.html')
|
|
if (fs.existsSync(index)) {
|
|
res.setHeader('Content-Type', 'text/html; charset=utf-8')
|
|
res.end(fs.readFileSync(index))
|
|
return
|
|
}
|
|
|
|
next()
|
|
})
|
|
},
|
|
}
|
|
}
|
|
|
|
export default defineConfig({
|
|
// Empty base emits relative asset URLs (./assets/... instead of /assets/...)
|
|
// so the built SPA loads correctly when served at any subpath — HA Ingress,
|
|
// nginx/Traefik path prefix, Cloudflare Tunnel path routing, etc. (#1195).
|
|
base: '',
|
|
plugins: [react(), serveGcodeViewer()],
|
|
build: {
|
|
outDir: '../static',
|
|
emptyOutDir: true,
|
|
chunkSizeWarningLimit: 3000,
|
|
},
|
|
server: {
|
|
host: '0.0.0.0',
|
|
proxy: {
|
|
'/api/v1/ws': {
|
|
target: backendUrl,
|
|
ws: true,
|
|
changeOrigin: true,
|
|
},
|
|
'/api': {
|
|
target: backendUrl,
|
|
changeOrigin: true,
|
|
},
|
|
},
|
|
},
|
|
resolve: {
|
|
alias: {
|
|
'@': path.resolve(__dirname, './src'),
|
|
},
|
|
},
|
|
})
|