import { defineConfig, loadEnv } from 'vite'; import vue from '@vitejs/plugin-vue'; import { VitePWA, VitePWAOptions } from 'vite-plugin-pwa'; import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite'; import { fileURLToPath, URL } from 'node:url'; import path from 'node:path'; const pwaOptions: Partial = { registerType: 'autoUpdate', strategies: 'injectManifest', srcDir: 'src', filename: 'sw.ts', devOptions: { enabled: true, type: 'module', navigateFallback: 'index.html', suppressWarnings: true, }, manifest: { name: 'mitlist', short_name: 'mitlist', description: 'mitlist pwa', theme_color: '#fff8f0', background_color: '#f3f3f3', display: 'standalone', orientation: 'portrait', icons: [ { src: 'icons/icon-128x128.png', sizes: '128x128', type: 'image/png' }, { src: 'icons/icon-192x192.png', sizes: '192x192', type: 'image/png' }, { src: 'icons/icon-256x256.png', sizes: '256x256', type: 'image/png' }, { src: 'icons/icon-384x384.png', sizes: '384x384', type: 'image/png' }, { src: 'icons/icon-512x512.png', sizes: '512x512', type: 'image/png' }, ], }, injectManifest: { globPatterns: [ '**/*.{js,css,html,ico,png,svg,woff2}', 'offline.html', ], globIgnores: [ '**/node_modules/**', '**/dist/**', 'sw.js', 'dev-sw.js', 'index.html', ], maximumFileSizeToCacheInBytes: 15 * 1024 * 1024, // 15MB }, workbox: { cleanupOutdatedCaches: true, sourcemap: true, }, }; export default ({ mode }: { mode: string }) => { const env = loadEnv(mode, process.cwd(), ''); const isProduction = mode === 'production'; return defineConfig({ plugins: [ vue(), VitePWA(pwaOptions), VueI18nPlugin({ include: [path.resolve(path.dirname(fileURLToPath(import.meta.url)), './src/i18n/**.json')], strictMessage: false, runtimeOnly: false, compositionOnly: false, }), ], resolve: { alias: { '@': fileURLToPath(new URL('./src', import.meta.url)), }, }, define: { __PWA_FALLBACK_HTML__: JSON.stringify('/index.html'), __PWA_SERVICE_WORKER_REGEX__: JSON.stringify('^(sw|workbox)-.*\\.js$'), 'process.env.MODE': JSON.stringify(process.env.NODE_ENV), 'process.env.PROD': JSON.stringify(process.env.NODE_ENV === 'production'), }, build: { // Advanced code splitting configuration rollupOptions: { output: { // Vendor chunk splitting for better caching manualChunks: { // Core Vue ecosystem 'vue-vendor': ['vue', 'vue-router', 'pinia'], // UI Framework chunks 'headlessui': ['@headlessui/vue'], 'icons': ['@heroicons/vue'], // Large utility libraries 'date-utils': ['date-fns'], 'http-client': ['axios'], // Chart/visualization libraries (if used) 'charts': ['chart.js', 'vue-chartjs'].filter(dep => { try { require.resolve(dep); return true; } catch { return false; } }), // Development tools (only in dev) ...(isProduction ? {} : { 'dev-tools': ['@vue/devtools-api'] }) }, // Optimize chunk file names for caching chunkFileNames: (chunkInfo) => { const facadeModuleId = chunkInfo.facadeModuleId ? chunkInfo.facadeModuleId.split('/').pop()?.replace(/\.\w+$/, '') || 'chunk' : 'chunk'; // Route-based chunks get descriptive names if (chunkInfo.facadeModuleId?.includes('/pages/')) { return `pages/[name]-[hash].js`; } if (chunkInfo.facadeModuleId?.includes('/components/')) { return `components/[name]-[hash].js`; } if (chunkInfo.facadeModuleId?.includes('/layouts/')) { return `layouts/[name]-[hash].js`; } return `chunks/${facadeModuleId}-[hash].js`; }, // Optimize entry and asset naming entryFileNames: `assets/[name]-[hash].js`, assetFileNames: (assetInfo) => { const info = assetInfo.name?.split('.') || []; const _ext = info[info.length - 1]; // Organize assets by type for better caching strategies if (/\.(png|jpe?g|gif|svg|webp|avif)$/i.test(assetInfo.name || '')) { return `images/[name]-[hash][extname]`; } if (/\.(woff2?|eot|ttf|otf)$/i.test(assetInfo.name || '')) { return `fonts/[name]-[hash][extname]`; } if (/\.css$/i.test(assetInfo.name || '')) { return `styles/[name]-[hash][extname]`; } return `assets/[name]-[hash][extname]`; } }, // External dependencies that should not be bundled external: isProduction ? [] : [ // In development, externalize heavy dev dependencies ] }, // Performance optimizations target: 'esnext', minify: isProduction ? 'terser' : false, // Terser options for production ...(isProduction && { terserOptions: { compress: { drop_console: true, // Remove console.log in production drop_debugger: true, pure_funcs: ['console.log', 'console.info', 'console.debug'] // Remove specific console methods }, mangle: { safari10: true // Ensure Safari 10+ compatibility }, format: { comments: false // Remove comments } } }), // Source map configuration sourcemap: isProduction ? false : 'inline', // CSS code splitting cssCodeSplit: true, // Asset size warnings chunkSizeWarningLimit: 1000, // 1MB warning threshold // Enable/disable asset inlining assetsInlineLimit: 4096 // 4KB threshold for base64 inlining }, // Performance optimizations for development optimizeDeps: { include: [ 'vue', 'vue-router', 'pinia', '@headlessui/vue', '@heroicons/vue/24/outline', '@heroicons/vue/24/solid', 'date-fns' ], exclude: [ // Exclude heavy dependencies that should be loaded lazily ] }, // Development server configuration server: { open: true, proxy: { '/api': { target: env.VITE_API_BASE_URL, changeOrigin: true, // Add caching headers for API responses in development configure: (proxy, _options) => { proxy.on('proxyRes', (proxyRes, req, _res) => { // Add cache headers for static API responses if (req.url?.includes('/categories') || req.url?.includes('/users/me')) { proxyRes.headers['Cache-Control'] = 'max-age=300'; // 5 minutes } }); } }, }, // Enable gzip compression in dev middlewareMode: false, }, // Preview server configuration (for production builds) preview: { port: 4173, strictPort: true, open: true } }); };