Bundle boyutu küçültme, çoğu zaman ölçmeden başlar. Bir araç çalıştırılır, birkaç paket silinir, belki dynamic import eklenir; sonuç yeterince küçülmüş gibi görünür. Peki gerçekten ne kadar küçüldü?

Küçülme sürecinde en çok atlanan adımlar, en hızlı kazandıranlar değil, en kolay atlananlardır: bağımlılık ağacının tamamını görmemek, tree shaking'in neden çalışmadığını anlamamak, polyfill'leri varsayılan olarak bırakmak. Her biri ayrı bir karar noktasıdır ve her biri yanlış yapıldığında geri tepebilir.

Atlanan kararlar genelde en hızlı kazandıranlar değil, en kolay atlananlardır. Her madde hem "neden gerekli" hem "ne zaman gereksiz veya ters etki yaratır" sorusunu taşır.

Bağımlılık Auditi: Paket Ağırlığını Gerçekten Görmek

Hangi paketin ne kadar yer kapladığını bilmeden küçültme yapamazsınız. npm ls veya yarn why komutları doğrudan boyut bilgisi vermez; bağımlılık ağacını gösterir. Boyutu görmek için ayrı bir adım gerekir: bundlephobia.com veya cost-of-modules gibi araçlar paket başına gzip boyutunu ve tree shaking uyumluluğunu gösterir. Bu bilgiyi bir kez değil, her yeni bağımlılık eklenmeden önce kontrol edin.

Audit için kontrol edilecekler:

  • package.json içindeki her bağımlılık hâlâ kullanılıyor mu? depcheck komutu kullanılmayan bağımlılıkları listeler ve bazen yıllardır aktif olmayan paketleri gün yüzüne çıkarır.
  • Birden fazla paket aynı işi mi yapıyor? Hem moment hem de date-fns aynı projede bulunabilir; ikisini birlikte tutmanın maliyeti iki katına çıkar ve hangisine güvenildiği belirsizleşir.
  • Devbağımlılık olması gereken paketler dependencies'te mi? Build araçları, linter'lar ve test kütüphaneleri devDependencies'e taşındığında production bundle'ı etkilemez.
  • Büyük bir paketin küçük bir alt kümesi mi kullanılıyor? lodash yerine lodash-es veya yalnızca ilgili fonksiyonu doğrudan import etmek, tree shaking desteğiyle birlikte bundle'ı önemli ölçüde küçültebilir.
  • Bir işlevi yapmak için yüklenen paketin aynı işlevi artık platform kendisi sunuyor mu? Modern tarayıcılar fetch, IntersectionObserver, ResizeObserver ve birçok array metodunu yerleşik olarak içerir.

Auditi bir kez yapıp bırakmayın. CI/CD pipeline'ına bundlesize veya benzer bir araç eklemek, boyutun farkında olmadan büyümesini engeller ve her PR'da regresyon erken yakalanır.

Tree Shaking Neden Çalışmıyor Olabilir?

Tree shaking çalıştığını düşündüğünüz an çalışmıyor olabilir. Build aracının tree shaking yaptığını raporlaması ile bundle'dan gerçekten kullanılmayan kodu çıkarması iki ayrı şeydir.

En yaygın neden CommonJS modülleridir. Webpack ve Rollup, require() kullanan kütüphaneleri tree shake edemez çünkü hangi export'ların kullanılacağı çalışma zamanına kadar bilinemez. Kütüphanenin package.json'ında "module" alanı varsa ESM derleme çıktısı sunuyor demektir; build aracınızın bunu tercih ettiğinden emin olun.

Kontrol edilecekler:

  • Kullandığınız kütüphanelerin package.json'ında "module" veya "exports" alanı var mı? Yoksa, yalnızca "main" varsa tree shaking büyük olasılıkla çalışmıyor.
  • sideEffects: false tanımlanmış mı? Bu alan olmadan Webpack tüm dosyaları dahil eder ve hiçbir kodu güvenle çıkaramaz.
  • Barrel dosyaları (index.js içinde export * from ...) tüm modülü yükletir. Doğrudan import tercih edilmeli: import { Button } from 'pkg/Button' gibi, import { Button } from 'pkg' yerine.
  • Webpack kullanıyorsanız optimization.usedExports: true ve optimization.sideEffects: true açık mı? Production mode bu ayarları varsayılan olarak etkinleştirir ama development modda kontrol edin.
  • CSS-in-JS kütüphaneleri genellikle sideEffects içerir ve tree shaking'e direnç gösterir; bu kütüphaneleri seçerken bundle boyutuna etkisini önceden öğrenin.

Barrel dosyası sorunu sessizce büyür. Tek bir bileşen import edildiğinde index.js üzerinden tüm kütüphane yükleniyor olabilir ve bu durum bundle analiz raporunu inceleyene kadar fark edilmez. Raporda bir kütüphanenin beklenden çok daha büyük göründüğü durumda bu ilk kontrol noktasıdır.

Dynamic Import Planlaması: Neyi Böleceğinizi Belirlemek

Her şeyi bölmeyin. Dynamic import her bileşene uygulanacak bir teknik değildir; küçük bileşenler bölündüğünde network waterfall artar, ek parse ve overhead maliyeti çıkar ve net kazanç görülmez. Büyük ama ilk render'da gerekmeyen parçalar bölündüğünde fark edilir.

Bölünmeye uygun adaylar:

  • Rota bazlı bileşenler: kullanıcı o sayfaya gitmeden yüklenmesi gerekmeyenler. React Router veya Next.js gibi framework'ler rota bazlı code splitting'i yerleşik olarak destekler.
  • Büyük üçüncü taraf kütüphaneler: grafik kütüphaneleri, zengin metin editörleri, harita araçları, PDF işleyiciler. Bunlar çoğu durumda 100 KB üzerindedir ve etkileşime kadar ertelenebilir.
  • Modal ve drawer gibi koşullu arayüz parçaları: kullanıcı tetiklemeden yüklenmeleri gerekmez; açıldıktan sonra yükleme gecikmesi çoğu kullanıcı tarafından fark edilmez.
  • Admin paneli, ayarlar sayfası, rapor ekranı gibi seyrek kullanılan ve yalnızca belirli rollere açık alanlar.

Bölünmeye uygun olmayan adaylar:

  • Her yerde görünen ortak bileşenler: header, navigation, form elemanları. Bunları bölmek cache'i parçalar ve sayfa geçişlerinde tekrar yüklenmelerine yol açar.
  • 10-15 KB'ın altındaki bileşenler. Ek network isteğinin parse ve round-trip maliyeti kazancı silebilir.
  • LCP'ye katkıda bulunan içerikler ve ilk görünümde yer alan kritik bileşenler. Bunları lazy load etmek LCP'yi doğrudan kötüleştirir.

Bölme kararı verirken şu soruyu sorun: bu kod ilk 3 saniyede ekranda görünecek mi? Görünecekse bölmeyin.

Dead Code Tespiti: Silmeden Önce Doğrulamak

Dead code iki türdür. Gerçekten hiç çalışmayan kod ve koşullu olarak çalışan ama belirli durumlarda hiç tetiklenmeyen kod. İkincisi daha tehlikelidir çünkü statik analiz araçları onu "kullanılan" olarak işaretler ve otomatik yöntemler gözden kaçırır.

Chrome DevTools'un Coverage paneli, sayfa yüklendiğinde ve kullanıcı etkileşimi sırasında hangi JavaScript satırlarının çalıştığını gösterir. Kırmızı işaretli bölgeler o akış sırasında çalışmayan kodlardır. Yüzde olarak değil, hangi blokların hiç çalışmadığı bilgisiyle karar verin.

Kontrol edilecekler:

  • Coverage panelinde kırmızı kalan kod blokları hiç tetiklenmiyor mu, yoksa yalnızca bu test akışında mı tetiklenmedi? Hata yönetimi, edge case dalları ve kullanıcı akışına bağlı kod bu ayrımı önemli kılar.
  • Kullanılmayan utils veya helpers dosyaları var mı? TypeScript kullanıyorsanız ts-prune veya knip export edilen ama hiç import edilmeyen sembolleri listeler.
  • Çıkarılmak istenen kod başka modüller tarafından import ediliyor mu? Import grafiğini kontrol etmeden silmeyin; bir dosya doğrudan kullanılmıyor olsa bile başka bir dosya üzerinden dolaylı olarak bağımlı olabilir.
  • Feature flag ile kapatılmış eski kod: hangi flagler hâlâ aktif, hangisi aylardır false durumda ve artık devreye girmeyecek?
  • A/B testi veya deneysel özellik kalıntıları: tamamlanan testlerin kazanan olmayan dalı temizlendi mi?

Silmek geri alınamaz sayılmaz; sürüm kontrol sistemi her zaman geri döndürme imkânı verir. Ama başlamadan önce Coverage ve import grafiğini birlikte değerlendirmek, yanlışlıkla aktif kodu çıkarmanın önüne geçer ve test kapsamı olmayan kod söz konusu olduğunda risk azalır.

Polyfill Gerekliliğini Sorgulamak

Polyfill'ler, desteklenmesi gerekmeyen tarayıcılar için de yüklenmeye devam eder. Hedef tarayıcı listesi güncellenmeden polyfill yükü küçülmez.

.browserslistrc veya package.json'daki browserslist yapılandırmanız ne zaman güncellendi? Çoğu projede bu dosya proje kurulurken yazılır ve bir daha açılmaz. Eski bir hedef listesiyle çalışırken Babel ve core-js, 2023-2025 sürümü tarayıcılarda yerleşik olarak bulunan onlarca özelliği polyfill olarak paketler ve bu ek kod hiçbir zaman kullanılmaz.

Kontrol edilecekler:

  • browserslist yapılandırmasında IE 11 veya last 10 versions gibi geniş hedefler var mı? defaults veya > 0.5%, last 2 versions, not dead daha güncel tarayıcıları hedefler ve polyfill yükünü doğrudan azaltır.
  • @babel/preset-env yapılandırmasında useBuiltIns değeri ne? 'entry' hedef listenizdeki her özellik için polyfill ekler; 'usage' yalnızca kodda gerçekten kullanılan özellikler için ekler ve çoğu durumda daha küçük çıktı üretir.
  • core-js sürümü 2 mi, 3 mü? Sürüm 2 artık güncellenmez ve sürüm 3'e kıyasla daha fazla gereksiz polyfill içerir.
  • Proje zaten TypeScript kullanıyorsa ve tsconfig.json'da target ile lib alanları yapılandırıldıysa, Babel polyfill'leriyle çelişip çelişmediğini kontrol edin; ikisi aynı anda çalıştığında çakışma olabilir.
  • Manuel eklenen polyfill dosyaları var mı? import 'whatwg-fetch' veya import 'promise-polyfill' gibi satırlar artık gereksiz olabilir.

Polyfill kararı "ata da zararı olmaz" mantığıyla verilmez. Her polyfill parse edilir, her parse milisaniye harcar ve bu maliyet, hiç ihtiyaç duymayan kullanıcıların cihazında da gerçekleşir.

Bundle Analiz Raporunu Okumak

Webpack Bundle Analyzer veya Rollup'un --visualize çıktısı, hangi modülün toplam bundle içinde ne kadar yer kapladığını ağaç haritası olarak gösterir. Raporu ilk açtığınızda dikkat çeken genellikle büyük vendor chunk'larıdır. Ama asıl sorun çoğu zaman daha küçük, tekrar eden modüllerde gizlidir.

Kontrol edilecekler:

  • Aynı paket birden fazla chunk'ta görünüyor mu? Bu durum, kod bölme yanlış yapılandırıldığında ortaya çıkar ve aynı kütüphane hem main bundle'a hem de lazy chunk'lara girer. optimization.splitChunks yapılandırması bu durumu önler.
  • Vendor chunk boyutu 500 KB'ı geçiyor mu? Geçiyorsa hangi paket en büyük payı alıyor? Bir paketin orantısız büyüklükte görünmesi, o paketin tamamının import edildiğinin işaretidir ve tree shaking sorunu olduğunu gösterir.
  • Source map dosyaları production bundle'a dahil edilmiş mi? Source map'ler geliştirme aracıdır; production'da sunucuya yüklenebilir ama tarayıcıya gönderilmemelidir.
  • Test dosyaları, fixture'lar veya __mocks__ dizinleri bundle'a girmiş mi? Bu nadir ama oluştuğunda onlarca KB boşa gider.
  • Locale dosyaları: moment veya benzer kütüphaneler tüm dil paketlerini varsayılan olarak dahil eder. Yalnızca kullanılan locale'ler ContextReplacementPlugin veya tree shaking uyumlu alternatiflerle kesilebilir.

Raporu sadece boyut sıralamasıyla değil, beklenti dışı giren modülleri arayarak okuyun. Orada olmaması gereken bir paketin neden orada olduğunu anlamak, o paketin kaldırılmasından bazen daha fazla kazandırır çünkü onu çeken bağımlılık zincirini keser.

Build Yapılandırmasını Production İçin Doğrulamak

Production mode açık değilse minification, dead code elimination ve birçok optimizasyon çalışmaz. Bu bariz gibi görünür; ama CI'da production build çalışırken lokal geliştirme sırasında yanlış yapılandırmayla ölçüm yapıldığında sonuçlar yanıltıcı olur.

Kontrol edilecekler:

  • Webpack'te mode: 'production' ayarlanmış mı? Bu ayar TerserPlugin ile minification'ı, NODE_ENV=production tanımını ve varsayılan optimizasyonların büyük bölümünü etkinleştirir.
  • console.log, debug bilgisi veya geliştirme amaçlı uyarılar production build'dan temizleniyor mu? Terser yapılandırmasında drop_console: true bunu otomatikleştirir.
  • React veya benzeri kütüphaneler NODE_ENV değerine bakarak development/production dalı seçer. DefinePlugin ile 'process.env.NODE_ENV': JSON.stringify('production') tanımlanmadan bu dallar doğru kesilmez.
  • Minification sonrası gzip veya Brotli boyutu ölçüldü mü? Ham JavaScript boyutu değil, transfer boyutu kullanıcıyı etkiler. Sunucu sıkıştırması etkin değilse minification tek başına yeterli olmaz ve yapılandırma kontrol edilmelidir.

Build süreci değiştiğinde boyutu yeniden ölçün. Bir araçtan diğerine geçiş, Webpack'ten Vite'a ya da ek bir plugin eklemek, beklenmedik boyut artışlarına yol açabilir ve sürüm geçişinden haftalar sonra fark edilebilir.

Kontrol listesi bir defaya mahsus değildir. Bundle boyutu, her bağımlılık güncellemesinde, her yeni özellik eklenmesinde ve her build yapılandırması değişikliğinde sessizce büyüyebilir. CI pipeline'a boyut eşiği koymak, bu büyümeyi fark edilir kılan en kolay mekanizmadır ve elle tekrar eden denetimi azaltır.

Hangi adımdan başlanacağı projeye göre değişir. Bağımlılık auditi hızlı kazanç sağlar; tree shaking sorununun çözülmesi daha derin bir yapılandırma değişikliği ister. Ölçüm olmadan yapılan küçültme tahmindir. Analiz raporu, Coverage verisi ve browserslist güncellemesi, tahmini gözleme çevirir.