← back to Watches

vite.config.js

211 lines

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { VitePWA } from 'vite-plugin-pwa';
import viteCompression from 'vite-plugin-compression';

export default defineConfig({
  plugins: [
    react(),

    // Brotli compression for production
    viteCompression({
      verbose: true,
      disable: false,
      threshold: 1024,
      algorithm: 'brotliCompress',
      ext: '.br',
      compressionOptions: {
        level: 11
      }
    }),

    // Gzip compression as fallback
    viteCompression({
      verbose: true,
      disable: false,
      threshold: 1024,
      algorithm: 'gzip',
      ext: '.gz'
    }),

    // Enhanced PWA with Workbox
    VitePWA({
      registerType: 'autoUpdate',
      includeAssets: ['favicon.ico', 'robots.txt', 'sitemap.xml'],
      workbox: {
        globPatterns: ['**/*.{js,css,html,ico,png,jpg,jpeg,svg,webp,woff,woff2}'],
        runtimeCaching: [
          {
            urlPattern: /^https:\/\/fonts\.googleapis\.com\/.*/i,
            handler: 'CacheFirst',
            options: {
              cacheName: 'google-fonts-cache',
              expiration: {
                maxEntries: 10,
                maxAgeSeconds: 60 * 60 * 24 * 365 // 1 year
              },
              cacheableResponse: {
                statuses: [0, 200]
              }
            }
          },
          {
            urlPattern: /^https:\/\/fonts\.gstatic\.com\/.*/i,
            handler: 'CacheFirst',
            options: {
              cacheName: 'gstatic-fonts-cache',
              expiration: {
                maxEntries: 10,
                maxAgeSeconds: 60 * 60 * 24 * 365
              },
              cacheableResponse: {
                statuses: [0, 200]
              }
            }
          },
          {
            urlPattern: /\/api\/watches$/,
            handler: 'NetworkFirst',
            options: {
              cacheName: 'api-watches',
              expiration: {
                maxEntries: 50,
                maxAgeSeconds: 60 * 5 // 5 minutes
              },
              networkTimeoutSeconds: 10
            }
          },
          {
            urlPattern: /\/api\/.*/,
            handler: 'NetworkFirst',
            options: {
              cacheName: 'api-cache',
              expiration: {
                maxEntries: 100,
                maxAgeSeconds: 60 * 10 // 10 minutes
              },
              networkTimeoutSeconds: 10
            }
          }
        ],
        navigateFallback: '/index.html',
        navigateFallbackDenylist: [/^\/api/]
      },
      manifest: {
        name: 'Omega Watch Price History',
        short_name: 'Omega Watches',
        description: 'Historical price tracking for 32 iconic Omega timepieces',
        theme_color: '#DC143C',
        background_color: '#1F2937',
        display: 'standalone',
        icons: [
          {
            src: '/icons/icon-192x192.png',
            sizes: '192x192',
            type: 'image/png'
          },
          {
            src: '/icons/icon-512x512.png',
            sizes: '512x512',
            type: 'image/png'
          }
        ]
      }
    })
  ],

  server: {
    port: 5173,
    proxy: {
      '/api': {
        target: 'http://localhost:7600',
        changeOrigin: true
      }
    }
  },

  build: {
    outDir: 'dist',
    emptyOutDir: true,

    // Advanced minification
    minify: 'terser',
    terserOptions: {
      compress: {
        drop_console: true,
        drop_debugger: true,
        pure_funcs: ['console.log', 'console.info', 'console.debug'],
        passes: 2
      },
      mangle: {
        safari10: true
      }
    },

    // Aggressive code splitting
    rollupOptions: {
      output: {
        manualChunks: {
          // React core (critical)
          'react-core': ['react', 'react-dom', 'react/jsx-runtime'],

          // Chart library (heavy, lazy load)
          'chart-lib': ['chart.js', 'react-chartjs-2'],

          // Animation library (heavy, lazy load)
          'animation-lib': ['framer-motion'],

          // Utilities (small, can be combined)
          'utils': ['fuse.js']
        },

        // Optimize chunk names for caching
        chunkFileNames: 'assets/[name]-[hash].js',
        entryFileNames: 'assets/[name]-[hash].js',
        assetFileNames: 'assets/[name]-[hash].[ext]'
      }
    },

    // Chunk size warnings
    chunkSizeWarningLimit: 800,

    // Enable CSS code splitting
    cssCodeSplit: true,

    // No source maps in production
    sourcemap: false,

    // Target modern browsers
    target: 'es2020',

    // Optimize dependencies
    commonjsOptions: {
      transformMixedEsModules: true
    },

    // CSS minification
    cssMinify: true,

    // Optimize asset inlining
    assetsInlineLimit: 4096
  },

  optimizeDeps: {
    include: [
      'react',
      'react-dom',
      'react/jsx-runtime'
    ],
    exclude: [
      'chart.js',
      'react-chartjs-2',
      'framer-motion'
    ]
  },

  // Performance optimization
  esbuild: {
    logOverride: { 'this-is-undefined-in-esm': 'silent' }
  }
});