275 lines
10 KiB
TypeScript
275 lines
10 KiB
TypeScript
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': {},
|
||
},
|
||
}
|
||
})
|