/// import { defineConfig, type Plugin } from 'vite' import react from '@vitejs/plugin-react' import path from 'path' import { visualizer } from 'rollup-plugin-visualizer' // https://vitejs.dev/config/ export default defineConfig(({ mode }) => { const isProduction = mode === 'production' return { plugins: [ react(), // Plugin pour générer des nonces CSP { name: 'csp-nonce', generateBundle(_options: any, bundle: any) { // Générer un nonce pour chaque build const nonce = Buffer.from(Math.random().toString()).toString('base64') // Injecter le nonce dans les scripts inline for (const fileName in bundle) { const chunk = bundle[fileName] if (chunk.type === 'chunk' && chunk.code) { chunk.code = chunk.code.replace( /__CSP_NONCE__/g, nonce ) } } } } as Plugin, // Bundle analyzer for production builds (isProduction && visualizer({ filename: 'dist/bundle-analysis.html', open: false, gzipSize: true, brotliSize: true, })) as Plugin, ].filter(Boolean), test: { globals: true, environment: 'jsdom', setupFiles: './src/setupTests.ts', css: true, }, resolve: { alias: { '@': path.resolve(__dirname, './src'), '@components': path.resolve(__dirname, './src/components'), '@features': path.resolve(__dirname, './src/features'), '@services': path.resolve(__dirname, './src/services'), '@hooks': path.resolve(__dirname, './src/hooks'), '@utils': path.resolve(__dirname, './src/utils'), '@types': path.resolve(__dirname, './src/types'), }, }, server: { port: 5173, host: true, headers: { 'Content-Security-Policy': (() => { const basePolicy = [ "default-src 'self'", "worker-src 'self' blob:", "img-src 'self' data: https: blob:", "connect-src 'self' ws: wss: http: https:", "font-src 'self' data:", "object-src 'none'", "base-uri 'self'", "form-action 'self'", "frame-ancestors 'none'", "upgrade-insecure-requests" ]; // Production: Remove unsafe-inline and unsafe-eval, use nonces // Dev: Keep unsafe-inline for Vite HMR, but remove unsafe-eval if (isProduction) { basePolicy.push( "script-src 'self' 'nonce-__CSP_NONCE__' blob:", "style-src 'self' 'nonce-__CSP_NONCE__'" ); } else { basePolicy.push( "script-src 'self' 'unsafe-inline' blob:", "style-src 'self' 'unsafe-inline'" ); } return basePolicy.join('; '); })() } }, build: { outDir: 'dist', sourcemap: isProduction, target: 'esnext', minify: 'esbuild', rollupOptions: { output: { // Manual chunk splitting removed to avoid path resolution errors // manualChunks: { ... }, chunkFileNames: (chunkInfo) => { const facadeModuleId = chunkInfo.facadeModuleId ? chunkInfo.facadeModuleId.split('/').pop()?.replace('.tsx', '').replace('.ts', '') : 'chunk'; return `js/${facadeModuleId}-[hash].js`; }, entryFileNames: 'js/[name]-[hash].js', assetFileNames: (assetInfo) => { const extType = assetInfo.name?.split('.').pop(); if (/png|jpe?g|svg|gif|tiff|bmp|ico/i.test(extType || '')) { return 'images/[name]-[hash][extname]'; } if (/woff2?|eot|ttf|otf/i.test(extType || '')) { return 'fonts/[name]-[hash][extname]'; } return 'assets/[name]-[hash][extname]'; }, }, }, chunkSizeWarningLimit: 1000, }, optimizeDeps: { include: [ 'react', 'react-dom', 'zustand', 'date-fns', 'zod', 'dompurify', '@tanstack/react-virtual', ], exclude: ['@vite/client', '@vite/env'], }, css: { devSourcemap: !isProduction, }, define: { __CSP_NONCE__: JSON.stringify('__CSP_NONCE__') } } })