import path from 'path' import react from '@vitejs/plugin-react' import { Plugin, PluginOption, defineConfig } from 'vite' /** * Plugin to enable SPA fallback for vite preview. * In dev mode, Vite handles SPA routing automatically. * In preview mode, we need to rewrite page-like URLs to /index.html * so the static file server (sirv) serves the SPA entry point. */ function spaFallbackPlugin(): Plugin { return { name: 'spa-fallback', configurePreviewServer(server) { server.middlewares.use((req, res, next) => { const url = req.url || '/' const pathname = url.split('?')[0] const ext = path.extname(pathname) // If this looks like a page request (no file extension), // rewrite to index.html so sirv serves the SPA if (!ext) { req.url = '/index.html' + (url.includes('?') ? url.substring(url.indexOf('?')) : '') } next() }) }, } } const PR_NUMBER = process.env.VERCEL_GIT_PULL_REQUEST_ID function getEnv() { if (!process.env.VERCEL_ENV) { return 'development' } if (PR_NUMBER !== undefined && PR_NUMBER !== '') { return 'preview' } if (process.env.VERCEL_ENV === 'production') { return 'production' } return 'canary' } const env = getEnv() // eslint-disable-next-line no-console console.log('build env:', env) function urlOrLocalFallback(mode: string, url: string | undefined, localFallbackPort: number) { if (url) { return JSON.stringify(url) } if (mode === 'development') { // in dev, vite lets us inline javascript expressions - so we return a template string that // will be evaluated on the client return '`http://${location.hostname}:' + localFallbackPort + '`' } else { // in production, we have to fall back to a hardcoded value return JSON.stringify(`http://localhost:${localFallbackPort}`) } } const TLDRAW_BEMO_URL_STRING = env === 'production' ? 'https://demo.tldraw.xyz' : env === 'canary' ? 'https://canary-demo.tldraw.xyz' : PR_NUMBER ? `https://pr-${PR_NUMBER}-demo.tldraw.xyz` : undefined export default defineConfig(({ mode }) => ({ plugins: [spaFallbackPlugin(), react(), exampleReadmePlugin()], root: path.join(__dirname, 'src'), publicDir: path.join(__dirname, 'public'), build: { outDir: path.join(__dirname, 'dist'), assetsInlineLimit: 0, target: 'es2022', minify: false, }, oxc: { target: 'es2022', }, server: { port: 5420, allowedHosts: true, }, preview: { port: 5420, }, clearScreen: false, optimizeDeps: { exclude: ['@tldraw/assets'], }, define: { 'process.env.TLDRAW_ENV': JSON.stringify(process.env.VERCEL_ENV ?? 'development'), 'process.env.TLDRAW_DEPLOY_ID': JSON.stringify( process.env.VERCEL_GIT_COMMIT_SHA ?? `local-${Date.now()}` ), 'process.env.TLDRAW_BEMO_URL': urlOrLocalFallback(mode, TLDRAW_BEMO_URL_STRING, 8989), 'process.env.TLDRAW_IMAGE_URL': urlOrLocalFallback( mode, env === 'development' ? undefined : 'https://images.tldraw.xyz', 8786 ), }, })) function exampleReadmePlugin(): PluginOption { return { name: 'example-readme', async transform(src, id) { const [filePath, query] = id.split('?') const isContentQuery = query?.split('&').includes('content') const match = filePath.match(/examples\/src\/examples\/(.+)\/README.md$/) if (!match) return const separator = '\n