2026-02-24 20:44:16 +00:00
|
|
|
|
import { defineConfig, loadEnv } from 'vite'
|
|
|
|
|
|
import react from '@vitejs/plugin-react'
|
|
|
|
|
|
import path from 'path'
|
|
|
|
|
|
import { VitePWA } from 'vite-plugin-pwa'
|
|
|
|
|
|
|
2026-08-07 11:59:47 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Precache stratejisi
|
|
|
|
|
|
* ------------------------------------------------------------------
|
|
|
|
|
|
* `dist` klasörü ~55 MB (48 adet DevExtreme teması ~33 MB + lazy chunk'lar
|
|
|
|
|
|
* ~20 MB). Bunların tamamını precache etmek her deploy'da service worker'ın
|
|
|
|
|
|
* on-larca MB indirmesine, mobil/yavaş bağlantıda kurulumun timeout'a düşüp
|
|
|
|
|
|
* worker'ın `redundant` olmasına ve güncellemenin hiç tamamlanmamasına yol
|
|
|
|
|
|
* açıyordu.
|
|
|
|
|
|
*
|
|
|
|
|
|
* Bu yüzden precache SADECE uygulama kabuğudur (index.html + entry JS + CSS,
|
|
|
|
|
|
* ~2.8 MB). Geri kalan her şey (lazy chunk, tema CSS'i, görsel, font) runtime
|
|
|
|
|
|
* cache ile ilk kullanımda saklanır. Dosya adları hash'li olduğu için
|
|
|
|
|
|
* CacheFirst güvenlidir; hash'siz public dosyalarda StaleWhileRevalidate
|
|
|
|
|
|
* kullanılır.
|
|
|
|
|
|
*/
|
|
|
|
|
|
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',
|
|
|
|
|
|
// 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',
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
// Deploy'da silinen eski build dosyalarını precache manifestine sokmamak için
|
|
|
|
|
|
// runtime cache'e alınacak hash'li uygulama varlıkları.
|
|
|
|
|
|
const HASHED_ASSET_PATTERN = /\/assets\/.+\.(?:js|css|wasm)$/
|
|
|
|
|
|
|
|
|
|
|
|
export default defineConfig(({ mode }) => {
|
2026-02-24 20:44:16 +00:00
|
|
|
|
const env = loadEnv(mode, process.cwd(), '')
|
2026-08-07 11:59:47 +00:00
|
|
|
|
const isProduction = mode === 'production'
|
|
|
|
|
|
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'
|
2026-02-24 20:44:16 +00:00
|
|
|
|
|
2026-08-07 11:59:47 +00:00
|
|
|
|
const baseDomains = ['sozsoft.com', 'dev.sozsoft.com', '.sozsoft.com']
|
|
|
|
|
|
const allowedHosts = Array.from(new Set(['localhost', ...baseDomains]))
|
2026-02-24 20:44:16 +00:00
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
plugins: [
|
|
|
|
|
|
react(),
|
|
|
|
|
|
VitePWA({
|
2026-07-14 07:37:13 +00:00
|
|
|
|
// 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',
|
2026-08-07 11:59:47 +00:00
|
|
|
|
// Kayıt ve güncelleme akışı src/views/version/swRegistration.ts tarafından yönetilir.
|
2026-07-14 07:37:13 +00:00
|
|
|
|
injectRegister: false,
|
2026-02-24 20:44:16 +00:00
|
|
|
|
devOptions: {
|
2026-08-07 11:59:47 +00:00
|
|
|
|
enabled: enableDevSw,
|
|
|
|
|
|
type: 'module',
|
2026-02-24 20:44:16 +00:00
|
|
|
|
},
|
|
|
|
|
|
|
2026-08-07 11:59:47 +00:00
|
|
|
|
workbox: {
|
|
|
|
|
|
globDirectory: 'dist',
|
|
|
|
|
|
globPatterns: PRECACHE_GLOBS,
|
|
|
|
|
|
|
|
|
|
|
|
// Kabuk dosyaları büyük olabilir (entry CSS ~1.3 MB).
|
|
|
|
|
|
maximumFileSizeToCacheInBytes: 4 * 1024 * 1024,
|
|
|
|
|
|
|
|
|
|
|
|
// Yeni worker precache'i bitirdikten sonra uygulama SKIP_WAITING
|
|
|
|
|
|
// gönderir; clientsClaim açık sekmelerin kontrolünü o an devralır.
|
|
|
|
|
|
clientsClaim: true,
|
|
|
|
|
|
skipWaiting: false,
|
|
|
|
|
|
cleanupOutdatedCaches: true,
|
|
|
|
|
|
|
|
|
|
|
|
navigateFallback: '/index.html',
|
|
|
|
|
|
navigateFallbackAllowlist: [/^(?!\/__).*/],
|
|
|
|
|
|
navigateFallbackDenylist: [/^\/api\//, /^\/connect\//, /^\/swagger/, /\/version\.json$/],
|
|
|
|
|
|
|
|
|
|
|
|
runtimeCaching: [
|
|
|
|
|
|
{
|
|
|
|
|
|
// Sürüm/changelog bilgisi asla eski kalmamalı.
|
|
|
|
|
|
urlPattern: /\/version\.json$/,
|
|
|
|
|
|
handler: 'NetworkFirst',
|
|
|
|
|
|
options: {
|
|
|
|
|
|
cacheName: 'app-version-v2',
|
|
|
|
|
|
networkTimeoutSeconds: 3,
|
|
|
|
|
|
expiration: { maxEntries: 2, maxAgeSeconds: 24 * 60 * 60 },
|
|
|
|
|
|
cacheableResponse: { statuses: [0, 200] },
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
// Hash'li build çıktıları immutable'dır.
|
|
|
|
|
|
urlPattern: HASHED_ASSET_PATTERN,
|
|
|
|
|
|
handler: 'CacheFirst',
|
|
|
|
|
|
options: {
|
|
|
|
|
|
cacheName: 'app-assets-v3',
|
|
|
|
|
|
expiration: { maxEntries: 400, maxAgeSeconds: 60 * 24 * 60 * 60 },
|
|
|
|
|
|
cacheableResponse: { statuses: [0, 200] },
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
// public/css altındaki DevExtreme temaları hash'siz; içerik
|
|
|
|
|
|
// sürümle değişebildiği için arka planda tazelenir.
|
|
|
|
|
|
urlPattern: /\/css\/[\w.-]+\.css$/,
|
|
|
|
|
|
handler: 'StaleWhileRevalidate',
|
|
|
|
|
|
options: {
|
|
|
|
|
|
cacheName: 'dx-themes-v1',
|
|
|
|
|
|
expiration: { maxEntries: 12, maxAgeSeconds: 30 * 24 * 60 * 60 },
|
|
|
|
|
|
cacheableResponse: { statuses: [0, 200] },
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
urlPattern: /\.(?:woff2?|ttf|otf|eot)$/,
|
|
|
|
|
|
handler: 'CacheFirst',
|
|
|
|
|
|
options: {
|
|
|
|
|
|
cacheName: 'fonts-v1',
|
|
|
|
|
|
expiration: { maxEntries: 40, maxAgeSeconds: 180 * 24 * 60 * 60 },
|
|
|
|
|
|
cacheableResponse: { statuses: [0, 200] },
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
urlPattern: /\.(?:png|jpe?g|svg|gif|webp|ico)$/,
|
|
|
|
|
|
handler: 'CacheFirst',
|
|
|
|
|
|
options: {
|
|
|
|
|
|
cacheName: 'images-v2',
|
|
|
|
|
|
expiration: { maxEntries: 150, maxAgeSeconds: 30 * 24 * 60 * 60 },
|
|
|
|
|
|
cacheableResponse: { statuses: [0, 200] },
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
],
|
|
|
|
|
|
},
|
2026-02-24 20:44:16 +00:00
|
|
|
|
|
|
|
|
|
|
manifest: {
|
2026-02-28 21:37:34 +00:00
|
|
|
|
name: 'Sözsoft Platform',
|
|
|
|
|
|
short_name: 'Sözsoft Platform',
|
2026-02-24 20:44:16 +00:00
|
|
|
|
theme_color: '#FF99C8',
|
|
|
|
|
|
background_color: '#f0e7db',
|
2026-08-06 09:16:10 +00:00
|
|
|
|
display: 'browser',
|
2026-02-24 20:44:16 +00:00
|
|
|
|
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'],
|
2026-02-28 21:37:34 +00:00
|
|
|
|
description: 'Sözsoft Platform Application',
|
2026-02-24 20:44:16 +00:00
|
|
|
|
},
|
|
|
|
|
|
}),
|
|
|
|
|
|
],
|
|
|
|
|
|
|
|
|
|
|
|
server: {
|
|
|
|
|
|
open: true,
|
|
|
|
|
|
port: 3000,
|
|
|
|
|
|
watch: {
|
2026-08-07 11:59:47 +00:00
|
|
|
|
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'],
|
2026-02-24 20:44:16 +00:00
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
assetsInclude: ['**/*.md'],
|
|
|
|
|
|
|
|
|
|
|
|
resolve: {
|
|
|
|
|
|
alias: {
|
|
|
|
|
|
'@': path.join(__dirname, 'src'),
|
|
|
|
|
|
inferno: 'inferno/dist/index.esm.js',
|
|
|
|
|
|
'devextreme/ui': 'devextreme/esm/ui',
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
|
2026-08-07 11:59:47 +00:00
|
|
|
|
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-query',
|
|
|
|
|
|
'@tanstack/react-table',
|
|
|
|
|
|
'axios',
|
|
|
|
|
|
'dayjs',
|
|
|
|
|
|
'formik',
|
|
|
|
|
|
'yup',
|
|
|
|
|
|
'classnames',
|
|
|
|
|
|
'lodash',
|
|
|
|
|
|
'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',
|
|
|
|
|
|
],
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
esbuild: {
|
|
|
|
|
|
legalComments: 'none',
|
|
|
|
|
|
drop: isProduction ? ['debugger'] : [],
|
|
|
|
|
|
},
|
|
|
|
|
|
|
2026-02-24 20:44:16 +00:00
|
|
|
|
build: {
|
|
|
|
|
|
outDir: 'dist',
|
2026-08-07 11:59:47 +00:00
|
|
|
|
// 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ü).
|
2026-02-24 20:44:16 +00:00
|
|
|
|
chunkSizeWarningLimit: 2000,
|
|
|
|
|
|
sourcemap: false,
|
|
|
|
|
|
emptyOutDir: true,
|
2026-08-07 11:59:47 +00:00
|
|
|
|
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,
|
2026-02-24 20:44:16 +00:00
|
|
|
|
rollupOptions: {
|
|
|
|
|
|
output: {
|
2026-08-07 11:59:47 +00:00
|
|
|
|
// `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]'
|
2026-02-24 20:44:16 +00:00
|
|
|
|
if (/\.(png|jpe?g|svg|gif|ico|webp)$/.test(name ?? '')) {
|
|
|
|
|
|
return 'assets/img/[name]-[hash][extname]'
|
|
|
|
|
|
}
|
2026-08-07 11:59:47 +00:00
|
|
|
|
if (/\.(woff2?|ttf|otf|eot)$/.test(name ?? '')) {
|
|
|
|
|
|
return 'assets/fonts/[name]-[hash][extname]'
|
|
|
|
|
|
}
|
2026-02-24 20:44:16 +00:00
|
|
|
|
return 'assets/[name]-[hash][extname]'
|
|
|
|
|
|
},
|
2026-08-07 11:59:47 +00:00
|
|
|
|
// 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 / lodash / 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
|
|
|
|
|
|
},
|
2026-02-24 20:44:16 +00:00
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
preview: {
|
|
|
|
|
|
host: '0.0.0.0',
|
|
|
|
|
|
port: 80,
|
|
|
|
|
|
open: false,
|
|
|
|
|
|
allowedHosts,
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
define: {
|
|
|
|
|
|
'process.env': {},
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|