vite.config.ts 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. import { createRequire } from 'node:module'
  2. import { readdirSync, readFileSync } from 'node:fs'
  3. import { defineConfig } from 'vite'
  4. import type { Plugin } from 'vite'
  5. import react from '@vitejs/plugin-react'
  6. import path from 'path'
  7. // Backend port for dev server proxy (default: 8000)
  8. const backendPort = process.env.BACKEND_PORT || '8000'
  9. const backendUrl = `http://localhost:${backendPort}`
  10. // pdf.js keeps these out of its bundle and fetches them at runtime (#2976):
  11. // CJK text needs the CMaps, non-embedded fonts the standard font files, and
  12. // JPEG2000/JBIG2 images and ICC colour the wasm decoders. Published under the
  13. // bundle's own assets directory so the existing /assets mount serves them —
  14. // PdfPreviewModal builds the matching URLs from its PDFJS_ASSET_BASE.
  15. const PDFJS_RUNTIME_DIRS = ['cmaps', 'iccs', 'standard_fonts', 'wasm']
  16. const PDFJS_RUNTIME_PREFIX = 'assets/pdfjs'
  17. /** Publish pdf.js's runtime data directories next to the bundle. */
  18. function pdfjsRuntimeAssets(): Plugin {
  19. const require = createRequire(__filename)
  20. const packageDir = path.dirname(require.resolve('pdfjs-dist/package.json'))
  21. // Published path -> file on disk. Built once, and used as an allowlist by
  22. // the dev-server handler so no request can escape the package directory.
  23. const files = new Map<string, string>()
  24. for (const dir of PDFJS_RUNTIME_DIRS) {
  25. for (const entry of readdirSync(path.join(packageDir, dir), { withFileTypes: true })) {
  26. if (!entry.isFile()) continue
  27. files.set(`${PDFJS_RUNTIME_PREFIX}/${dir}/${entry.name}`, path.join(packageDir, dir, entry.name))
  28. }
  29. }
  30. return {
  31. name: 'bambuddy:pdfjs-runtime-assets',
  32. generateBundle() {
  33. for (const [fileName, source] of files) {
  34. this.emitFile({ type: 'asset', fileName, source: readFileSync(source) })
  35. }
  36. },
  37. configureServer(server) {
  38. server.middlewares.use((req, res, next) => {
  39. const source = files.get((req.url ?? '').split('?')[0].replace(/^\//, ''))
  40. if (!source) return next()
  41. res.setHeader('Content-Type', source.endsWith('.wasm') ? 'application/wasm' : 'application/octet-stream')
  42. res.end(readFileSync(source))
  43. })
  44. },
  45. }
  46. }
  47. export default defineConfig({
  48. // Default base ('/') emits absolute asset URLs (/assets/...). Required so
  49. // deep SPA routes (camera popup at /camera/<id>, /projects/<id>, kiosk
  50. // /spoolbuddy/ams, refresh on any nested route) resolve their <script>
  51. // and <link> tags to /assets/... instead of /<route-prefix>/assets/...,
  52. // which the SPA fallback would otherwise return as text/html and the
  53. // browser would refuse to execute (#1221). The earlier `base: ''` partial
  54. // fix for subpath reverse proxies (#1195, wontfix) is reverted — that
  55. // audience uses NPM + Cloudflare Tunnel at a real domain per the
  56. // documented workaround, which doesn't depend on this setting.
  57. plugins: [react(), pdfjsRuntimeAssets()],
  58. build: {
  59. outDir: '../static',
  60. emptyOutDir: true,
  61. chunkSizeWarningLimit: 3000,
  62. // Support floor is Safari 16.0 / iOS 16.0 (see
  63. // scripts/check-browser-baseline.mjs, #2971). Without an explicit target
  64. // the bundler keeps newer syntax verbatim — pdf.js ships class static
  65. // initialisation blocks (Safari 16.4+), which would parse-fail the whole
  66. // chunk on iOS 16.0-16.3 (#2976). This lowers such syntax at build time;
  67. // regex features are NOT lowered, which is why the baseline check script
  68. // still exists alongside this setting.
  69. target: 'safari16',
  70. rolldownOptions: {
  71. // occt-import-js (STEP previews, #2976) is an Emscripten build that
  72. // requires `path` and `crypto`, but only inside its ENVIRONMENT_IS_NODE
  73. // branches; in the browser it takes the fetch/getRandomValues paths.
  74. // Vite externalizes both and warns on every build. Drop just those two
  75. // warnings for that one package so a new externalization still shows.
  76. onLog(level, log, defaultHandler) {
  77. if (
  78. level === 'warn' &&
  79. /Module "(path|crypto)" has been externalized for browser compatibility, imported by "[^"]*\/node_modules\/occt-import-js\//.test(
  80. log.message,
  81. )
  82. ) {
  83. return
  84. }
  85. defaultHandler(level, log)
  86. },
  87. },
  88. },
  89. server: {
  90. host: '0.0.0.0',
  91. proxy: {
  92. '/api/v1/ws': {
  93. target: backendUrl,
  94. ws: true,
  95. changeOrigin: true,
  96. },
  97. '/api': {
  98. target: backendUrl,
  99. changeOrigin: true,
  100. },
  101. },
  102. },
  103. resolve: {
  104. alias: {
  105. '@': path.resolve(__dirname, './src'),
  106. },
  107. },
  108. })