sozsoft-platform/ui/vite.config.ts
2026-08-07 14:59:47 +03:00

282 lines
10 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { defineConfig, loadEnv } from 'vite'
import react from '@vitejs/plugin-react'
import path from 'path'
import { VitePWA } from 'vite-plugin-pwa'
/**
* 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 }) => {
const env = loadEnv(mode, process.cwd(), '')
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'
const baseDomains = ['sozsoft.com', 'dev.sozsoft.com', '.sozsoft.com']
const allowedHosts = Array.from(new Set(['localhost', ...baseDomains]))
return {
plugins: [
react(),
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,
devOptions: {
enabled: enableDevSw,
type: 'module',
},
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] },
},
},
],
},
manifest: {
name: 'Sözsoft Platform',
short_name: 'Sözsoft Platform',
theme_color: '#FF99C8',
background_color: '#f0e7db',
display: 'browser',
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(__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-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'] : [],
},
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: {
// `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 / 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
},
},
},
},
preview: {
host: '0.0.0.0',
port: 80,
open: false,
allowedHosts,
},
define: {
'process.env': {},
},
}
})