import path from "node:path"; import react from "@vitejs/plugin-react-swc"; import { defineConfig } from "vitest/config"; // https://vitejs.dev/config/ export default defineConfig(() => ({ // In a native Capacitor WebView the bundle is served from the local // filesystem, so assets must be referenced with RELATIVE paths ('./…'). // Absolute '/…' paths are the classic cause of a white screen on device. // The regular web deploy keeps '/' (served from a domain root). base: process.env.CAPACITOR_BUILD ? './' : '/', server: { host: "::", port: 8080, }, plugins: [ react(), ], test: { globals: true, environment: 'jsdom', setupFiles: './src/test/setup.ts', // Only run the app's own tests. Skill templates under .agents/ carry their // own *.test.tsx that assume a different project layout (e.g. they import // @/lib/getDisplayName, which doesn't exist here) and must never run as // part of this app's suite. Build artifacts are excluded too. include: ['src/**/*.{test,spec}.{ts,tsx}'], exclude: ['node_modules', 'dist', 'android', 'ios', '.agents', 'cvm-server'], onConsoleLog(log) { return !log.includes("React Router Future Flag Warning"); }, env: { DEBUG_PRINT_LIMIT: '0', }, }, resolve: { alias: { "@": path.resolve(__dirname, "./src"), }, }, optimizeDeps: { exclude: ['@lightninglabs/lnc-web'], }, build: { // Strip console.log/warn/debug in production (keeps console.error for real errors) minify: 'esbuild', commonjsOptions: { transformMixedEsModules: true, }, rollupOptions: { // Bundling policy — the distinction that matters is STATIC vs DYNAMIC import, // and whether the package resolves on the build machine: // // BUNDLED (absent from this list): // - @capacitor/core — imported STATICALLY (lib/platform.ts). It MUST be // bundled: leaving it external emitted a bare `import "@capacitor/core"` // specifier the native WebView can't resolve → white-screen boot crash. // Capacitor guarantees core is installed, so bundling always resolves. // // EXTERNAL (listed below): // - @capacitor/camera — used only via a GUARDED dynamic import() on native // (components/CameraCapture.tsx). It may not be installed on every build // machine, and it's optional/native-only, so it must stay external. The // dynamic import() remains a runtime call that degrades gracefully. // - @capacitor/app|haptics|keyboard|status-bar — NOT installed and never // statically imported (accessed via window.Capacitor.Plugins at runtime). // // NOTE: Amber signing no longer uses the npm `nostr-signer-capacitor-plugin`. // It now goes through our OWN native plugin `AmberIntent` // (android-templates/AmberIntentPlugin.java), which implements the Amethyst // foreground-intent NIP-55 flow and is reached via registerPlugin('AmberIntent') // from @capacitor/core (already bundled). No bare specifier to externalize. external: [ '@capacitor/camera', '@capacitor/app', '@capacitor/haptics', '@capacitor/keyboard', '@capacitor/status-bar', ], output: process.env.CAPACITOR_BUILD ? { // Single bundle for Capacitor APK — avoids 100+ chunk file requests // that fail silently in Android WebView local file serving manualChunks: undefined, inlineDynamicImports: true, } : { // Split large vendor libraries into separate cacheable chunks. // Users only re-download a chunk when that specific library updates. manualChunks(id) { // Map — Leaflet is large (~150KB), isolate it so non-map pages don't pay for it if (id.includes('leaflet') || id.includes('ngeohash')) { return 'vendor-map'; } // Nostr core — changes infrequently, worth caching separately if (id.includes('nostr-tools') || id.includes('@nostrify') || id.includes('@noble')) { return 'vendor-nostr'; } // Bitcoin / Lightning — large, rarely changes if ( id.includes('@cashu') || id.includes('bitcoinjs-lib') || id.includes('@scure') || id.includes('@bitcoinerlab') || id.includes('ecpair') || id.includes('@getalby') || id.includes('npubcash') ) { return 'vendor-bitcoin'; } // React ecosystem — very stable, long cache life. // IMPORTANT: `react` (core, with useLayoutEffect etc.) MUST live in // the SAME chunk as react-dom / react-router. If react-dom is split // into vendor-react but core `react` lands elsewhere, react-dom runs // before React is defined → "Cannot read properties of undefined // (reading 'useLayoutEffect')". The `/react/` and scheduler matches // below keep the whole React runtime together and loaded first. if ( id.includes('node_modules/react/') || id.includes('node_modules/react-dom/') || id.includes('node_modules/scheduler/') || id.includes('react-router') || id.includes('react/jsx-runtime') ) { return 'vendor-react'; } // Radix UI components — large but stable if (id.includes('@radix-ui')) { return 'vendor-radix'; } // TanStack Query if (id.includes('@tanstack')) { return 'vendor-query'; } }, }, }, }, // Strip console.log/warn/debug calls from production build esbuild: { drop: ['debugger'], pure: ['console.log', 'console.warn', 'console.debug'], }, }));