import { sentryVitePlugin } from '@sentry/vite-plugin' import tailwindcss from '@tailwindcss/vite' import vue from '@vitejs/plugin-vue' import dotenv from 'dotenv' import { FileSystemIconLoader } from 'unplugin-icons/loaders' import IconsResolver from 'unplugin-icons/resolver' import Icons from 'unplugin-icons/vite' import Components from 'unplugin-vue-components/vite' import { defineConfig } from 'vite' import type { UserConfig } from 'vite' import { createHtmlPlugin } from 'vite-plugin-html' import vueDevTools from 'vite-plugin-vue-devtools' import { comfyAPIPlugin, generateImportMapPlugin } from './build/plugins' dotenv.config() const IS_DEV = process.env.NODE_ENV === 'development' const SHOULD_MINIFY = process.env.ENABLE_MINIFY === 'true' // vite dev server will listen on all addresses, including LAN and public addresses const VITE_REMOTE_DEV = process.env.VITE_REMOTE_DEV === 'true' const DISABLE_TEMPLATES_PROXY = process.env.DISABLE_TEMPLATES_PROXY === 'true' const DISABLE_VUE_PLUGINS = process.env.DISABLE_VUE_PLUGINS === 'true' const GENERATE_SOURCEMAP = process.env.GENERATE_SOURCEMAP !== 'false' // CLOUD PERFORMANCE: ImportMap entries for Vue/PrimeVue temporarily disabled (see generateImportMapPlugin below) // This reduces 600+ HTTP requests to ~8 bundled files for better cloud deployment performance // Hardcoded to staging cloud for testing frontend changes against cloud backend const DEV_SERVER_COMFYUI_URL = process.env.DEV_SERVER_COMFYUI_URL || 'https://testcloud.comfy.org' // To use local backend, change to: 'http://127.0.0.1:8188' // Optional: Add API key to .env as STAGING_API_KEY if needed for authentication const addAuthHeaders = (proxy: any) => { proxy.on('proxyReq', (proxyReq: any, _req: any, _res: any) => { const apiKey = process.env.STAGING_API_KEY if (apiKey) { proxyReq.setHeader('X-API-KEY', apiKey) } }) } const DISTRIBUTION = (process.env.DISTRIBUTION || 'localhost') as | 'desktop' | 'localhost' | 'cloud' export default defineConfig({ base: '', server: { host: VITE_REMOTE_DEV ? '0.0.0.0' : undefined, watch: { ignored: [ './browser_tests/**', './node_modules/**', './tests-ui/**', '.eslintcache', '*.config.{ts,mts}', '**/.git/**', '**/.github/**', '**/.nx/**', '**/*.{test,spec}.ts', '**/coverage/**', '**/dist/**', '**/playwright-report/**', '**/test-results/**' ] }, proxy: { '/internal': { target: DEV_SERVER_COMFYUI_URL, changeOrigin: true, secure: false, configure: addAuthHeaders }, '/api': { target: DEV_SERVER_COMFYUI_URL, changeOrigin: true, secure: false, configure: (proxy, _options) => { addAuthHeaders(proxy) }, // Return empty array for extensions API as these modules // are not on vite's dev server. bypass: (req, res, _options) => { if (req.url === '/api/extensions') { res.end(JSON.stringify([])) return false // Return false to indicate request is handled } // Bypass multi-user auth check from staging if (req.url === '/api/users') { res.setHeader('Content-Type', 'application/json') res.end(JSON.stringify({})) // Return empty object to simulate single-user mode return false // Return false to indicate request is handled } return null } }, '/ws': { target: DEV_SERVER_COMFYUI_URL, ws: true, changeOrigin: true, secure: false, configure: addAuthHeaders }, '/workflow_templates': { target: DEV_SERVER_COMFYUI_URL, changeOrigin: true, secure: false, configure: addAuthHeaders }, // Proxy extension assets (images/videos) under /extensions to the ComfyUI backend '/extensions': { target: DEV_SERVER_COMFYUI_URL, changeOrigin: true, secure: false }, // Proxy docs markdown from backend '/docs': { target: DEV_SERVER_COMFYUI_URL, changeOrigin: true, secure: false }, ...(!DISABLE_TEMPLATES_PROXY ? { '/templates': { target: DEV_SERVER_COMFYUI_URL, changeOrigin: true, secure: false } } : {}), '/testsubrouteindex': { target: 'http://localhost:5173', rewrite: (path) => path.substring('/testsubrouteindex'.length) } } }, plugins: [ ...(!DISABLE_VUE_PLUGINS ? [vueDevTools(), vue(), createHtmlPlugin({})] : [vue()]), tailwindcss(), comfyAPIPlugin(IS_DEV), generateImportMapPlugin([ // TEMPORARY CLOUD OPTIMIZATION: Vue/PrimeVue entries commented out for better performance // // CONTEXT: ImportMap generates 600+ individual files (recursiveDependence: true) which // causes poor performance in cloud deployment due to lack of CDN cache headers. // This selective approach bundles Vue/PrimeVue normally (~3-5 chunks) while preserving // the ImportMap system for future extension API imports. // // PERFORMANCE IMPACT: // - Before: 600+ HTTP requests to dist/assets/lib/ directory // - After: ~8 bundled JS files with proper compression // - Cloud load time improvement: significant reduction in initial requests // // LONG-TERM CONSIDERATIONS: // 1. Extension ecosystem: Core extensions don't import PrimeVue directly, // so this change doesn't break existing extensions // 2. Future extensions: May need ImportMap for dynamic Vue component imports // 3. Deployment optimization: Implement proper CDN cache headers as permanent solution // 4. Hybrid approach: Could selectively enable ImportMap for specific packages only // // TO RESTORE FULL IMPORTMAP: Uncomment entries below and rebuild // NOTE: Verify extension compatibility before restoring in production // // { // name: 'vue', // pattern: 'vue', // entry: './dist/vue.esm-browser.prod.js' // }, // { // name: 'vue-i18n', // pattern: 'vue-i18n', // entry: './dist/vue-i18n.esm-browser.prod.js' // }, // { // name: 'primevue', // pattern: /^primevue\/?.*/, // entry: './index.mjs', // recursiveDependence: true // This generates 600+ files // }, // { // name: '@primevue/themes', // pattern: /^@primevue\/themes\/?.*/, // entry: './index.mjs', // recursiveDependence: true // }, // { // name: '@primevue/forms', // pattern: /^@primevue\/forms\/?.*/, // entry: './index.mjs', // recursiveDependence: true, // override: { // '@primeuix/forms': { // entry: '' // } // } // } ]), Icons({ compiler: 'vue3', customCollections: { comfy: FileSystemIconLoader('packages/design-system/src/icons') } }), Components({ dts: true, resolvers: [ IconsResolver({ customCollections: ['comfy'] }) ], dirs: ['src/components', 'src/layout', 'src/views'], deep: true, extensions: ['vue'], directoryAsNamespace: true }), // Sentry sourcemap upload plugin // Only runs during cloud production builds when all Sentry env vars are present // Requires: SENTRY_AUTH_TOKEN, SENTRY_ORG, SENTRY_PROJECT env vars ...(DISTRIBUTION === 'cloud' && process.env.SENTRY_AUTH_TOKEN && process.env.SENTRY_ORG && process.env.SENTRY_PROJECT && !IS_DEV ? [ sentryVitePlugin({ org: process.env.SENTRY_ORG, project: process.env.SENTRY_PROJECT, authToken: process.env.SENTRY_AUTH_TOKEN, sourcemaps: { // Delete source maps after upload to prevent public access filesToDeleteAfterUpload: ['**/*.map'] } }) ] : []) ], build: { minify: SHOULD_MINIFY ? 'esbuild' : false, target: 'es2022', sourcemap: GENERATE_SOURCEMAP, rollupOptions: { treeshake: true, output: { manualChunks: (id) => { if (!id.includes('node_modules')) { return undefined } if (id.includes('primevue') || id.includes('@primeuix')) { return 'vendor-primevue' } if (id.includes('@tiptap')) { return 'vendor-tiptap' } if (id.includes('chart.js')) { return 'vendor-chart' } if (id.includes('three') || id.includes('@xterm')) { return 'vendor-visualization' } if (id.includes('/vue') || id.includes('pinia')) { return 'vendor-vue' } return 'vendor-other' } } } }, esbuild: { minifyIdentifiers: SHOULD_MINIFY, keepNames: true, minifySyntax: SHOULD_MINIFY, minifyWhitespace: SHOULD_MINIFY, pure: SHOULD_MINIFY ? [ 'console.log', 'console.debug', 'console.info', 'console.trace', 'console.dir', 'console.dirxml', 'console.group', 'console.groupCollapsed', 'console.groupEnd', 'console.table', 'console.time', 'console.timeEnd', 'console.timeLog', 'console.count', 'console.countReset', 'console.profile', 'console.profileEnd', 'console.clear' ] : [] }, test: { globals: true, environment: 'happy-dom', setupFiles: ['./vitest.setup.ts'] }, define: { __COMFYUI_FRONTEND_VERSION__: JSON.stringify( process.env.npm_package_version ), __SENTRY_ENABLED__: JSON.stringify( !(process.env.NODE_ENV === 'development' || !process.env.SENTRY_DSN) ), __SENTRY_DSN__: JSON.stringify(process.env.SENTRY_DSN || ''), __ALGOLIA_APP_ID__: JSON.stringify(process.env.ALGOLIA_APP_ID || ''), __ALGOLIA_API_KEY__: JSON.stringify(process.env.ALGOLIA_API_KEY || ''), __USE_PROD_CONFIG__: process.env.USE_PROD_CONFIG === 'true', __DISTRIBUTION__: JSON.stringify(DISTRIBUTION) }, resolve: { alias: { '@/utils/formatUtil': '/packages/shared-frontend-utils/src/formatUtil.ts', '@/utils/networkUtil': '/packages/shared-frontend-utils/src/networkUtil.ts', '@': '/src' } }, optimizeDeps: { exclude: ['@comfyorg/comfyui-electron-types'], entries: ['index.html'] } }) satisfies UserConfig as UserConfig