import path from "node:path"; import { defineConfig, loadEnv } from "vite"; import vue from "@vitejs/plugin-vue"; import { VitePWA } from "vite-plugin-pwa"; import IconsResolver from 'unplugin-icons/resolver'; import Icons from 'unplugin-icons/vite'; import Components from 'unplugin-vue-components/vite'; // avnac-vue ships source files (TypeScript + Vue). Resolve through the installed // package so Vite and TypeScript compile the same source in every environment. const PACKAGE_AVNAC_SRC = path.resolve(__dirname, 'node_modules/@venmail/avnac-vue/src') const PACKAGE_AVNAC_SRC_NORM = PACKAGE_AVNAC_SRC.replace(/\\/g, '/') const avnacImportRewriter = { name: 'avnac-import-rewriter', enforce: 'pre' as const, transform(code: string, id: string) { const normalized = id.replace(/\\/g, '/') const isAvnac = normalized.includes(PACKAGE_AVNAC_SRC_NORM) || normalized.includes('@venmail/avnac-vue/src/') || normalized.includes('@venmail+avnac-vue') if (isAvnac) { return { code: code .replace(/(['"])#\//g, '$1@avnac/') .replace(/(['"])\@\//g, '$1@avnac/'), map: null, } } }, } export default defineConfig(({ mode }) => { const env = loadEnv(mode, process.cwd(), ""); const shareBaseUrl = env.VITE_SHARE_BASE_URL || 'https://venia.cloud'; return { plugins: [ avnacImportRewriter, vue(), Components({ dirs: [], resolvers: [ IconsResolver({ prefix: 'i' }), ], }), Icons({ compiler: 'vue3', autoInstall: false, scale: 1, defaultClass: 'i-icon', }), VitePWA({ registerType: 'autoUpdate', injectRegister: 'auto', manifest: false, workbox: { globPatterns: [ '**/*.{js,css,html,ico,png,svg,woff,woff2,ttf,eot,json}', ], maximumFileSizeToCacheInBytes: 15 * 1024 * 1024, // SPA shell fallback. The app is responsible for showing offline UI when API calls fail. navigateFallback: '/index.html', navigateFallbackDenylist: [ /^\/api\//, /^\/storage\//, /^\/manifest\.json$/, /^\/sw\.js$/, ], // NOTE: We intentionally do NOT cache /api/* or /storage/* here. Those responses are // authenticated and user-scoped; caching them in a shared SW cache would leak data // across sessions on the same device. The app handles document offline storage via // its own local cache layer (see src/store/files.ts). runtimeCaching: [ { urlPattern: /^https:\/\/fonts\.(?:googleapis|gstatic)\.com\/.*/i, handler: 'CacheFirst', options: { cacheName: 'google-fonts', expiration: { maxEntries: 30, maxAgeSeconds: 60 * 60 * 24 * 365 }, cacheableResponse: { statuses: [0, 200] }, }, }, ], }, // Keep service worker disabled in dev to avoid HMR issues. Set to true only when testing PWA behavior. devOptions: { enabled: false, type: 'module', }, }), ], optimizeDeps: { // Exclude avnac-vue source from pre-bundling; avnacImportRewriter handles #/ at serve time. exclude: ['@venmail/avnac-vue', '@avnac'], // jszip is CJS and imported by avnac-vue (which is excluded above). include: ['jszip'], esbuildOptions: { plugins: [ { name: 'avnac-dep-scan-resolver', setup(build: any) { build.onResolve({ filter: /^[#@]\// }, (args: any) => { const importer = (args.importer || '').replace(/\\/g, '/') if ( importer.includes('/node_modules/@venmail/avnac-vue/src/') || importer.includes('@venmail+avnac-vue') || importer.includes(PACKAGE_AVNAC_SRC_NORM) ) { return { external: true } } }) }, }, ], }, }, server: { hmr: { overlay: false }, proxy: { "/api": { target: "http://127.0.0.1:8000", changeOrigin: true, secure: false, }, "/storage": { target: shareBaseUrl, changeOrigin: true, secure: true, }, }, }, build: { chunkSizeWarningLimit: 13000, target: 'es2020', minify: 'terser', terserOptions: { compress: { keep_infinity: true, passes: 1 }, }, rollupOptions: { onwarn(warning, warn) { const message = warning.message || ''; if (message.includes('is dynamically imported by') && message.includes('@venmail/avnac-vue')) { return; } warn(warning); }, output: { assetFileNames(assetInfo) { if (assetInfo.names?.some((n: string) => n.endsWith('.mjs'))) { return 'assets/[name]-[hash].js'; } return 'assets/[name]-[hash].[ext]'; }, manualChunks(id) { if (id.includes('node_modules/echarts') || id.includes('node_modules/zrender')) { return 'vendor-echarts'; } if (id.includes('node_modules/mermaid') || id.includes('node_modules/@mermaid-js')) { return 'vendor-mermaid'; } if (id.includes('node_modules/pdfjs-dist')) { return 'vendor-pdfjs'; } if (id.includes('node_modules/prosemirror')) { return 'vendor-prosemirror'; } if (id.includes('node_modules/dexie')) { return 'vendor-dexie'; } }, }, }, }, define: { 'import.meta.env.VITE_API_BASE_URL': JSON.stringify(env.VITE_API_BASE_URL), 'import.meta.env.VITE_AUTH_URL': JSON.stringify(env.VITE_AUTH_URL), 'import.meta.env.VITE_OAUTH_REDIRECT_URI': JSON.stringify(env.VITE_OAUTH_REDIRECT_URI), // The repository's deployment environments use SOCKET_BASE_URL. Expose it // under Vite's client-safe prefix for all collaboration clients. 'import.meta.env.VITE_SOCKET_BASE_URL': JSON.stringify(env.VITE_SOCKET_BASE_URL || env.SOCKET_BASE_URL), 'import.meta.env.SOCKET_BASE_URL': JSON.stringify(env.SOCKET_BASE_URL), }, css: { preprocessorOptions: { scss: { silenceDeprecations: ['import', 'legacy-js-api', 'global-builtin'], }, }, }, resolve: { alias: { "@": path.resolve(__dirname, "./src"), "@avnac": PACKAGE_AVNAC_SRC, "#": PACKAGE_AVNAC_SRC, fs: path.resolve(__dirname, './src/shims/node-empty.ts'), path: path.resolve(__dirname, './src/shims/node-empty.ts'), crypto: path.resolve(__dirname, './src/shims/node-empty.ts'), }, }, }; });