vite.config.ts 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. import { defineConfig } from 'vite'
  2. import react from '@vitejs/plugin-react'
  3. import path from 'path'
  4. import fs from 'fs'
  5. import type { Connect } from 'vite'
  6. // Backend port for dev server proxy (default: 8000)
  7. const backendPort = process.env.BACKEND_PORT || '8000'
  8. const backendUrl = `http://localhost:${backendPort}`
  9. // Absolute path to the gcode_viewer directory at the repo root
  10. const gcodeViewerDir = path.resolve(__dirname, '../gcode_viewer')
  11. // MIME types for static files served from gcode_viewer/
  12. const MIME: Record<string, string> = {
  13. '.html': 'text/html; charset=utf-8',
  14. '.js': 'application/javascript',
  15. '.css': 'text/css',
  16. '.obj': 'model/obj',
  17. '.mtl': 'model/mtl',
  18. '.png': 'image/png',
  19. '.jpg': 'image/jpeg',
  20. '.svg': 'image/svg+xml',
  21. '.json': 'application/json',
  22. '.woff': 'font/woff',
  23. '.woff2':'font/woff2',
  24. }
  25. /**
  26. * Vite dev-server plugin: serves ../gcode_viewer/ at /gcode-viewer/
  27. * without needing a proxy to uvicorn. In production uvicorn handles it
  28. * via the StaticFiles mount in main.py.
  29. */
  30. function serveGcodeViewer() {
  31. return {
  32. name: 'serve-gcode-viewer',
  33. configureServer(server: { middlewares: Connect.Server }) {
  34. server.middlewares.use((req, res, next) => {
  35. const url = req.url ?? ''
  36. if (!url.startsWith('/gcode-viewer')) return next()
  37. // Strip prefix, default to index.html
  38. let rel = url.slice('/gcode-viewer'.length)
  39. if (rel === '' || rel === '/') rel = '/index.html'
  40. // Strip query string
  41. rel = rel.split('?')[0]
  42. const absPath = path.join(gcodeViewerDir, rel)
  43. try {
  44. const stat = fs.statSync(absPath)
  45. if (stat.isFile()) {
  46. const ext = path.extname(absPath).toLowerCase()
  47. res.setHeader('Content-Type', MIME[ext] ?? 'application/octet-stream')
  48. res.end(fs.readFileSync(absPath))
  49. return
  50. }
  51. } catch {
  52. // file not found — fall through to index.html
  53. }
  54. // SPA fallback: serve index.html for any unmatched /gcode-viewer/* path
  55. const index = path.join(gcodeViewerDir, 'index.html')
  56. if (fs.existsSync(index)) {
  57. res.setHeader('Content-Type', 'text/html; charset=utf-8')
  58. res.end(fs.readFileSync(index))
  59. return
  60. }
  61. next()
  62. })
  63. },
  64. }
  65. }
  66. export default defineConfig({
  67. plugins: [react(), serveGcodeViewer()],
  68. build: {
  69. outDir: '../static',
  70. emptyOutDir: true,
  71. chunkSizeWarningLimit: 3000,
  72. },
  73. server: {
  74. host: '0.0.0.0',
  75. proxy: {
  76. '/api/v1/ws': {
  77. target: backendUrl,
  78. ws: true,
  79. changeOrigin: true,
  80. },
  81. '/api': {
  82. target: backendUrl,
  83. changeOrigin: true,
  84. },
  85. },
  86. },
  87. resolve: {
  88. alias: {
  89. '@': path.resolve(__dirname, './src'),
  90. },
  91. },
  92. })