mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-09-30 11:12:35 +02:00
PR #1195 (d6a31393) set Vite's base: '' to emit relative asset URLs
in the built index.html, intended as a partial improvement for
path-prefixed reverse proxies. The relative URLs broke every deep
SPA route on initial load: popup windows, direct URL paste, page
refresh on /camera/<id>, /projects/<id>, /groups/<id>/edit,
/external/<id>, /files/trash, and the SpoolBuddy kiosk paths.
Browser resolves ./assets/index-XXX.js against the document URL.
For /camera/<id>, that gives /camera/assets/index-XXX.js — the SPA
catch-all returns index.html (text/html) for that path, and modern
browsers refuse to execute HTML as a JS module under
X-Content-Type-Options: nosniff. Hence the empty-popup symptom
reported on #1221 across P1S / P2S / X1 / Docker / git / Chrome /
Firefox / Brave / Safari, plus the quieter "blank page on refresh"
on every other deep route.
Reverts the two PR #1195 lines: removes base: '' from
vite.config.ts (Vite default '/' restored, emitting absolute asset
URLs /assets/..., /manifest.json, /sw-register.js) and reverts
register('sw.js') to register('/sw.js') in public/sw-register.js.
PR #1195's class of bug — path-prefixed reverse proxy users serving
Bambuddy at a subpath — was already explicitly closed as wontfix in
that thread because supporting it requires subpath-aware
bootstrapping (API_BASE, React Router basename, PWA manifest scope,
SW scope) for every user forever. The supported alternative for that
audience stays as documented in the #1195 closing comment: NPM
(Nginx Proxy Manager) addon + Cloudflare Tunnel at a real domain
with HTTPS, then HA Webpage panel embedding via
TRUSTED_FRAME_ORIGINS — that path doesn't depend on base: '' at all.
The trade-off is intentional: revert reaches every user impacted by
deep-route initial-load bugs (much larger population than
path-prefixed proxy users), in exchange for an already-wontfixed
subpath-proxy regression that has a working alternative.
112 lines
3.4 KiB
TypeScript
112 lines
3.4 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({
|
|
// Default base ('/') emits absolute asset URLs (/assets/...). Required so
|
|
// deep SPA routes (camera popup at /camera/<id>, /projects/<id>, kiosk
|
|
// /spoolbuddy/ams, refresh on any nested route) resolve their <script>
|
|
// and <link> tags to /assets/... instead of /<route-prefix>/assets/...,
|
|
// which the SPA fallback would otherwise return as text/html and the
|
|
// browser would refuse to execute (#1221). The earlier `base: ''` partial
|
|
// fix for subpath reverse proxies (#1195, wontfix) is reverted — that
|
|
// audience uses NPM + Cloudflare Tunnel at a real domain per the
|
|
// documented workaround, which doesn't depend on this setting.
|
|
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'),
|
|
},
|
|
},
|
|
})
|