import { createRequire } from 'node:module' import react from '@vitejs/plugin-react' import { defineConfig } from 'vite' import eslintPlugin from 'vite-plugin-eslint' import svgr from 'vite-plugin-svgr' import path from 'path' import { fileURLToPath } from 'url' import { visualizer } from 'rollup-plugin-visualizer' const require = createRequire(import.meta.url) const svgo = require('vite-plugin-svgo') const __filename = fileURLToPath(import.meta.url) const __dirname = path.dirname(__filename) const isCloudflare = process.env.VITE_DEPLOY_TARGET === 'cloudflare' /** Resolve a package's ESM entry to an absolute path (pnpm-safe single instance). */ function esmEntry(id) { const cjs = require.resolve(id) const root = path.dirname(path.dirname(cjs)) // .../pkg/dist → .../pkg const pkg = require(path.join(root, 'package.json')) const rel = (typeof pkg.exports?.['.'] === 'object' && pkg.exports['.'].import) || (typeof pkg.exports === 'object' && pkg.exports.import) || pkg.module || pkg.exports?.['.'] || 'dist/index.js' return path.join(root, rel) } const codemirrorSingletons = { '@codemirror/state': esmEntry('@codemirror/state'), '@codemirror/view': esmEntry('@codemirror/view'), '@codemirror/language': esmEntry('@codemirror/language'), '@codemirror/commands': esmEntry('@codemirror/commands'), '@codemirror/autocomplete': esmEntry('@codemirror/autocomplete'), '@lezer/common': esmEntry('@lezer/common'), '@lezer/lr': esmEntry('@lezer/lr'), '@lezer/highlight': esmEntry('@lezer/highlight') } export default defineConfig({ base: isCloudflare ? '/' : './', // Cloudflare Pages needs absolute paths; Electron needs relative for file:// resolve: { alias: { three: path.resolve(__dirname, 'node_modules/three'), ...codemirrorSingletons }, dedupe: Object.keys(codemirrorSingletons) }, optimizeDeps: { // Do not prebundle CodeMirror — pnpm + esbuild duplicates @codemirror/state // and breaks FacetProvider instanceof checks. include: ['@ant-design/icons'], exclude: [ '@uiw/react-codemirror', 'codemirror', '@codemirror/state', '@codemirror/view', '@codemirror/language', '@codemirror/commands', '@codemirror/autocomplete', '@codemirror/theme-one-dark', '@codemirror/lang-javascript', '@codemirror/lang-xml', '@codemirror/lang-json', '@codemirror/lang-html', '@codemirror/lang-css', '@codemirror/lang-python', '@codemirror/lang-markdown', '@codemirror/lang-sql', '@codemirror/lang-java', '@codemirror/lang-cpp', '@codemirror/lang-rust', '@codemirror/lang-go', '@codemirror/lang-php', '@codemirror/lang-yaml', '@lezer/common', '@lezer/lr', '@lezer/highlight', '@lezer/javascript', '@lezer/xml' ] }, plugins: [ react(), svgo(), svgr(), eslintPlugin(), visualizer(), // Transform index.html paths to absolute when building for Cloudflare Pages isCloudflare && { name: 'cloudflare-html-paths', transformIndexHtml(html) { return html .replace(/href="\.\//g, 'href="/') .replace(/src="\.\//g, 'src="/') } } ].filter(Boolean), build: { outDir: 'build', chunkSizeWarningLimit: 2000, rollupOptions: { output: { manualChunks(id) { // --- CodeMirror if (id.includes('node_modules/@codemirror')) { return 'codemirror' } // --- Lezer if (id.includes('node_modules/@lezer')) { return 'lezer' } // --- Core Vendor (Ant Design, React, etc.) if ( id.includes('node_modules/antd') || id.includes('node_modules/@ant-design') || id.includes('node_modules/rc-') || id.includes('node_modules/antd-style') || id.includes('node_modules/@rc-component') || id.includes('node_modules/dayjs') || id.includes('node_modules/react') || id.includes('node_modules/react-dom') || id.includes('node_modules/react-router-dom') ) { return 'antd' } // --- Lodash if (id.includes('node_modules/lodash')) { return 'lodash' } // --- Three.js if (id.includes('node_modules/three')) { return 'three' } // --- GCode Preview if (id.includes('node_modules/gcode-preview')) { return 'gcode-preview' } // --- Online 3D Viewer if (id.includes('node_modules/online-3d-viewer')) { return 'online-3d-viewer' } // --- tsparticles if ( id.includes('node_modules/@tsparticles') || id.includes('node_modules/tsparticles') ) { return 'tsparticles' } // --- Tiptap if (id.includes('node_modules/@tiptap')) { return 'tiptap' } } } } }, server: { allowedHosts: ['dev.tombutcher.work'], host: '0.0.0.0', port: 5173, open: false } })