import { copyFileSync, existsSync } from 'node:fs' import { resolve } from 'node:path' import { defineConfig, type Plugin } from 'vite' import react from '@vitejs/plugin-react' import { renderFallbackHtml } from './src/fallback/inject' function fallbackPlugin(): Plugin { return { name: 'inject-fallback', transformIndexHtml: (html) => renderFallbackHtml(html), } } // chiptune3's chiptune3.worklet.js does `import libopenmptPromise from // './libopenmpt.worklet.js'` — a plain relative import. Vite's `new URL(..., // import.meta.url)` handling (used internally by chiptune3.js to load the // worklet) copies chiptune3.worklet.js into the build output verbatim but // does not follow that nested relative import, so libopenmpt.worklet.js is // missing from `dist/` and AudioWorklet.addModule() fails at runtime // ("Unable to load a worklet's module."). Copy the sibling file next to the // emitted worklet chunk so the relative import resolves in prod too. function chiptuneWorkletFix(): Plugin { let outDir = 'dist' let assetsDir = 'assets' return { name: 'chiptune3-worklet-fix', configResolved(config) { outDir = config.build.outDir assetsDir = config.build.assetsDir }, closeBundle() { const src = resolve('node_modules/chiptune3/libopenmpt.worklet.js') if (!existsSync(src)) { throw new Error( 'chiptuneWorkletFix: libopenmpt.worklet.js not found — chiptune3 layout changed?', ) } const dest = resolve(outDir, assetsDir, 'libopenmpt.worklet.js') copyFileSync(src, dest) }, } } export default defineConfig({ plugins: [react(), fallbackPlugin(), chiptuneWorkletFix()], optimizeDeps: { // In dev, esbuild's dep pre-bundling copies chiptune3.js into // node_modules/.vite/deps/, which breaks its internal // `new URL('./chiptune3.worklet.js', import.meta.url)` (the worklet // sibling file isn't copied along). Excluding it keeps chiptune3 // served straight from node_modules, where the relative lookup works. exclude: ['chiptune3'], }, test: { exclude: ['node_modules', 'dist', 'e2e'], }, })