import { defineConfig, loadEnv } from 'vite' import type { Plugin } from 'vite' import react from '@vitejs/plugin-react' import path from 'path' import { VitePWA } from 'vite-plugin-pwa' /** * Precache stratejisi * ------------------------------------------------------------------ * İki kademe vardır: * * 1. KABUK (workbox precache manifest'i, aşağıdaki `PRECACHE_GLOBS`) * index.html + entry JS/CSS + her açılışta yüklenen vendor chunk'ları. * Kurulumda ZORUNLUDUR; biri inmezse service worker kurulumu başarısız olur * (yarım sürümle aktive olmak beyaz ekran demek). * * 2. BİLEŞENLER (`warmup.json`, bu dosyadaki plugin üretir) * Tüm lazy chunk'lar ve CSS'leri. Güncelleme penceresi açıkken aynı ilerleme * çubuğunda arka planda indirilir; böylece kullanıcı deploy'dan sonra ilk kez * bir ekrana girdiğinde chunk beklemez. Best-effort'tur: inemeyen dosya * kurulumu düşürmez, süre bütçesi aşılırsa kalanlar runtime cache'e kalır. * * `dist/css` altındaki 48 DevExtreme teması (~33 MB) bilerek dışarıdadır; * kullanıcı başına yalnızca bir tema gerekir, runtime cache yeterlidir. * * Service worker `src/sw.ts` içinde elle yazıldı (strategies: injectManifest); * çünkü güncelleme penceresinde indirilen her dosyanın adı/boyutu ve toplam * yüzdesi gösterilebilsin diye indirme progress yayınlıyor. */ const PRECACHE_GLOBS = [ 'index.html', // manifest.webmanifest ve manifest ikonları vite-plugin-pwa tarafından // otomatik eklenir; buraya yazmak workbox'ta "conflicting entries" hatası verir. 'favicon.ico', 'assets/js/entry-*.js', 'assets/js/vendor-react-*.js', 'assets/js/vendor-router-*.js', // Sadece entry CSS'i. Rapor tasarımcısı/görüntüleyici CSS'leri (~580 KB) // lazy yüklendiği için runtime cache'e bırakılır. 'assets/css/index-*.css', ] /** Arka planda ısıtılacak bileşenler: uygulamanın tüm JS/CSS chunk'ları. */ const WARMUP_FILE = /^assets\/(?:js|css)\/.+\.(?:js|css)$/ /** * Build çıktısındaki chunk adlarını ve (sıkıştırılmamış) boyutlarını * `dist/warmup.json` olarak yazar. Boyutun derleme anında bilinmesi, service * worker'ın ilerleme yüzdesini ilk saniyeden itibaren doğru göstermesini sağlar * (Content-Length gzip'li boyut olduğu için yüzde zıplıyordu). */ function warmupManifest(): Plugin { return { name: 'sozsoft-warmup-manifest', apply: 'build', enforce: 'post', generateBundle(_options, bundle) { const files = Object.values(bundle) .filter((output) => WARMUP_FILE.test(output.fileName)) .map((output) => { const source = output.type === 'chunk' ? output.code : output.source return { url: `/${output.fileName}`, size: typeof source === 'string' ? new TextEncoder().encode(source).byteLength : source.byteLength, } }) // vite-plugin-pwa service worker'ı ayrı bir build ile üretir; o build'de // eşleşen dosya olmaz. Boş liste yazmak gerçek manifest'i ezerdi. if (files.length === 0) return this.emitFile({ type: 'asset', fileName: 'warmup.json', source: JSON.stringify({ files }), }) }, } } export default defineConfig(({ mode }) => { const env = loadEnv(mode, process.cwd(), '') const usePolling = env.VITE_USE_POLLING === 'true' // Dev'de service worker varsayılan olarak kapalıdır; açık olduğunda HMR // sonrası eski chunk'lar cache'den servis edilip kafa karıştırıyor. const enableDevSw = env.VITE_PWA_DEV === 'true' const baseDomains = ['sozsoft.com', 'dev.sozsoft.com', '.sozsoft.com'] const allowedHosts = Array.from(new Set(['localhost', ...baseDomains])) return { plugins: [ react(), warmupManifest(), VitePWA({ // Yeni sürüm önce tamamen indirilir, ardından uygulama kontrollü olarak // yeni service worker'a geçer. Böylece eski ekran ile yeni chunk'lar karışmaz. registerType: 'prompt', // Kayıt ve güncelleme akışı src/views/version/swRegistration.ts tarafından yönetilir. injectRegister: false, // Precache indirmesinin ilerlemesini kullanıcıya gösterebilmek için // service worker elle yazıldı. strategies: 'injectManifest', srcDir: 'src', filename: 'sw.ts', devOptions: { enabled: enableDevSw, type: 'module', navigateFallback: 'index.html', }, injectManifest: { globDirectory: 'dist', globPatterns: PRECACHE_GLOBS, // Kabuk dosyaları büyük olabilir (entry CSS ~1.3 MB). maximumFileSizeToCacheInBytes: 4 * 1024 * 1024, }, manifest: { name: 'Sözsoft Platform', short_name: 'Sözsoft Platform', theme_color: '#FF99C8', background_color: '#f0e7db', // Tarayıcının "uygulamayı yükle" akışı (beforeinstallprompt) yalnızca // standalone/minimal-ui görüntü modlarında tetiklenir. display: 'standalone', start_url: '/', scope: '/', icons: [ { src: '/img/logo/logo-400.png', sizes: '400x400', type: 'image/png', purpose: 'any maskable', }, { src: '/img/logo/logo-192.png', sizes: '192x192', type: 'image/png', purpose: 'any maskable', }, { src: '/img/logo/logo-512.png', sizes: '512x512', type: 'image/png', purpose: 'any maskable', }, ], categories: ['business', 'productivity'], description: 'Sözsoft Platform Application', }, }), ], server: { open: true, port: 3000, watch: { usePolling, interval: usePolling ? 1000 : undefined, }, // Sık dokunulan giriş dosyalarını önceden transform ederek ilk // gezinmedeki dev sunucu gecikmesini azalt. warmup: { clientFiles: ['./src/main.tsx', './src/App.tsx', './src/components/layouts/index.ts'], }, }, assetsInclude: ['**/*.md'], resolve: { alias: { '@': path.join(import.meta.dirname, 'src'), inferno: 'inferno/dist/index.esm.js', 'devextreme/ui': 'devextreme/esm/ui', }, }, optimizeDeps: { // Barrel/CJS ağırlıklı paketleri dev cold-start'ta tek seferde // prebundle et; aksi halde ilk sayfa yüzlerce ayrı istek atıyor. include: [ 'react', 'react-dom', 'react-dom/client', 'react-router-dom', '@tanstack/react-table', 'axios', 'dayjs', 'formik', 'yup', 'classnames', 'easy-peasy', 'framer-motion', 'react-icons/fa', // CJS/UMD bundle. `exclude` edilirse interop bozulup // "Babel.transform is not a function" hatası veriyor. Ayrıca burada // listelenince dinamik import anındaki "new dependency optimized" // kaynaklı sayfa yenilemesi de olmuyor. '@babel/standalone', ], }, build: { outDir: 'dist', // Vite'ın varsayılan 'baseline-widely-available' hedefi bilerek korunuyor: // es2022'ye sabitlemek esbuild'i downlevel'a zorlayıp entry chunk'ı // ~200 KB büyütüyor (ölçüldü). chunkSizeWarningLimit: 2000, sourcemap: false, emptyOutDir: true, cssCodeSplit: true, // Modern tarayıcı hedefinde polyfill gereksiz; entry'den birkaç KB düşer. modulePreload: { polyfill: false }, // Build sonunda gzip boyutu hesaplamak bu boyutta bir çıktıda dakikalar sürüyor. reportCompressedSize: false, rollupOptions: { output: { // Eski `esbuild.legalComments: 'none'` karşılığı. `annotation` // (@__PURE__) ve `jsdoc` varsayılan olarak korunur; annotation // yorumları tree-shaking için gereklidir. // `debugger` ifadeleri oxc minifier tarafından zaten düşürülüyor // (compress.dropDebugger varsayılanı true), ayrıca ayar gerekmiyor. comments: { legal: false }, // `entry-` öneki precache glob'unun sadece gerçek entry chunk'ı // yakalamasını sağlar (node_modules kaynaklı `index-*.js` chunk'ları // yanlışlıkla precache'e girmesin). entryFileNames: 'assets/js/entry-[name]-[hash].js', chunkFileNames: 'assets/js/[name]-[hash].js', assetFileNames: ({ name }: { name?: string }) => { if (/\.css$/.test(name ?? '')) return 'assets/css/[name]-[hash][extname]' if (/\.(png|jpe?g|svg|gif|ico|webp)$/.test(name ?? '')) { return 'assets/img/[name]-[hash][extname]' } if (/\.(woff2?|ttf|otf|eot)$/.test(name ?? '')) { return 'assets/fonts/[name]-[hash][extname]' } return 'assets/[name]-[hash][extname]' }, // SADECE her açılışta zaten yüklenen (%100 eager) çekirdek vendor'lar // ayrılır; bunlar sürümler arası değişmediği için tarayıcı cache'inde // kalır ve deploy sonrası yeniden indirilmez. // // Not: framer-motion / formik gibi paketleri de buraya eklemek // ölçüldü ve zararlı çıktı — Rollup'ın otomatik chunk'lamasında bu // paketlerin bir kısmı yalnızca lazy view'lara giriyor; tek chunk'a // toplandıklarında ilk yükleme ~650 KB büyüyor. manualChunks(id) { if (!id.includes('node_modules')) return undefined const file = id.replace(/\\/g, '/') if (/\/node_modules\/(react|react-dom|scheduler)\//.test(file)) return 'vendor-react' if (/\/node_modules\/(react-router|react-router-dom|@remix-run)\//.test(file)) { return 'vendor-router' } return undefined }, }, }, }, preview: { host: '0.0.0.0', port: 80, open: false, allowedHosts, }, define: { 'process.env': {}, }, } })