import { fileURLToPath, URL } from 'node:url'; import type { PluginOption } from 'vite'; import { defineConfig, loadEnv } from 'vite'; import react from '@vitejs/plugin-react'; import { visualizer } from 'rollup-plugin-visualizer'; function normalizeModuleId(id: string): string { return id.replace(/\\/g, '/'); } function manualChunkForModule(id: string): string | undefined { const normalizedId = normalizeModuleId(id); if (!normalizedId.includes('/node_modules/')) { return undefined; } // React core is a safe leaf (it imports none of the other vendors), so it can // live in its own chunk. Everything else — three, @react-three/*, the spring // animation libs, state and ui utils — is interdependent: splitting it across // chunks produced a circular chunk graph (vendor-three -> vendor -> vendor-three) // whose ESM init order tripped a temporal-dead-zone "Cannot access X before // initialization" crash, blanking the page. Keeping it in one chunk lets Rollup // order the bindings correctly. if ( normalizedId.includes('/react/') || normalizedId.includes('/react-dom/') || normalizedId.includes('/scheduler/') ) { return 'vendor-react'; } return 'vendor'; } function assetFileNameFor(assetName: string | undefined): string { const safeName = assetName ?? 'asset'; const extension = safeName.split('.').pop()?.toLowerCase() ?? ''; if (['glb', 'gltf', 'bin', 'ktx2'].includes(extension)) { return 'assets/models/[name]-[hash][extname]'; } if (['hdr', 'exr'].includes(extension)) { return 'assets/environments/[name]-[hash][extname]'; } if (['woff', 'woff2', 'ttf', 'otf'].includes(extension)) { return 'assets/fonts/[name]-[hash][extname]'; } if (['png', 'jpg', 'jpeg', 'webp', 'avif', 'svg'].includes(extension)) { return 'assets/images/[name]-[hash][extname]'; } return 'assets/[name]-[hash][extname]'; } export default defineConfig(({ mode }) => { const env = loadEnv(mode, process.cwd(), ''); const shouldAnalyze = mode === 'analyze' || env.ANALYZE === 'true'; const shouldEmitSourceMaps = mode !== 'production' || env.VITE_ENABLE_SOURCE_MAPS === 'true'; const plugins: PluginOption[] = [react()]; if (shouldAnalyze) { plugins.push( visualizer({ filename: 'dist/bundle-report.html', title: 'Mechanica bundle analysis', template: 'treemap', gzipSize: true, brotliSize: true, open: false }) as PluginOption ); } return { plugins, resolve: { alias: { '@': fileURLToPath(new URL('./src', import.meta.url)) } }, server: { host: '0.0.0.0', port: 5173, strictPort: false }, preview: { host: '0.0.0.0', port: 4173, strictPort: false }, build: { target: 'es2020', outDir: 'dist', assetsDir: 'assets', cssCodeSplit: true, sourcemap: shouldEmitSourceMaps, chunkSizeWarningLimit: 750, rollupOptions: { output: { manualChunks: manualChunkForModule, chunkFileNames: 'assets/js/[name]-[hash].js', entryFileNames: 'assets/js/[name]-[hash].js', assetFileNames: (assetInfo) => assetFileNameFor(assetInfo.name) } } } }; });