TypeScript Yapılandırması Production Bundle'ı Nasıl Etkiler?
tsconfig.json ayarları yalnızca tip kontrolünü değil, üretilen JavaScript'in boyutunu ve tree-shaking uyumluluğunu da belirler. target, module ve importHelpers seçeneklerinin production bundle üzerindeki somut etkisi.
TypeScript kullanan projelerde sık karşılaşılan bir yanlış anlama şu: derleyici tüm tip bilgisini siler, dolayısıyla tsconfig.json ayarları çıktı kodunu etkilemez. Tip açıklamaları gerçekten silinir, bu konuda bir yanılgı yok; ancak bu, TypeScript'in bundle üzerinde sıfır etkiye sahip olduğu anlamına gelmez. tsconfig.json içindeki bayraklar, üretilen JavaScript'in boyutunu, yapısını ve tree-shaking uyumluluğunu doğrudan belirler.
Yanlış seçilen bir target değeri, modern tarayıcılarda zaten desteklenen söz dizimini gereksiz yere polyfill'e dönüştürür. Yanlış seçilen module değeri, bundler'ın statik analiz yapmasını engeller ve kullanılmayan kodu bundle'dan atmasının önünü kapatır. Her iki hata da tip sisteminden bağımsızdır; saf JavaScript yazıyor olsaydınız da aynı sonuçla karşılaşırdınız. TypeScript burada yalnızca bir katman ekler, ama o katmanın kendi ayarları vardır.
Bu nedenle tsconfig'i "derleme ayarı" olarak değil, "production çıktısını şekillendiren yapılandırma" olarak okumak gerekir. Hangi bayrağın neyi tetiklediğini, hangi kombinasyonun helper kod ürettiğini ve hangi seçeneğin yalnızca geliştirme deneyimini etkilediğini anlamak, gereksiz kilobaytları ayıklamak için ilk adımdır.
target Ayarı Çıktı Boyutunu Neden Etkiler?
target değeri, TypeScript derleyicisinin hangi JavaScript söz dizimini aşağı uyumlu koda dönüştüreceğini belirler. "target": "ES5" seçildiğinde, arrow function, class, destructuring, template literal ve async/await gibi yapıların tamamı ES5 karşılıklarına çevrilir. Bu dönüşüm neredeyse her zaman daha uzun koddur.
Async/await bunu somutlaştırır. target: "ES2017" seçildiğinde TypeScript bu söz dizimini olduğu gibi bırakır; tarayıcı zaten desteklediği için ek koda gerek yoktur. target: "ES5" seçildiğindeyse TypeScript, her async fonksiyonu __awaiter ve __generator adlı iki helper fonksiyonuna sarmalar. Bu helper'lar küçük değildir; projenizde onlarca async fonksiyon varsa aynı helper kodu her dosyaya inline eklenir ve toplam boyut çarpılır.
Bugün hedef kitlesi hâlâ IE11 olan bir proje neredeyse kalmadı. Çoğu proje için ES2019 veya ES2020 güvenli bir hedef noktasıdır; modern tarayıcıların büyük çoğunluğu bu sürümleri destekler, bundler da bu söz dizimi üzerinde daha verimli statik analiz yapabilir. Hedefi geriye çekmek için somut bir destek kısıtlaması olmadığı sürece düşük bir target seçmek yalnızca helper kodu üretmeye yarar.
module Değeri Tree-Shaking'i Nasıl Kırar?
module seçeneği, TypeScript'in import/export ifadelerini hangi modül formatında emit edeceğini belirler. "CommonJS" ile "ESNext" arasındaki fark bundle boyutunu doğrudan etkiler.
CommonJS (require/module.exports) dinamik bir yapıdır. Bir modülün hangi parçasının kullanıldığı yalnızca runtime'da bilinebilir; statik analiz her zaman mümkün değildir. Rollup, Vite veya Webpack gibi bundler'lar, CommonJS modüllerinden kullanılmayan dışa aktarımları güvenle atamaz. Kullanılmayan bir yardımcı fonksiyon import edilmişse, o fonksiyon bundle'a dahil edilir. Tree-shaking çalışmıyor değildir; çalışamaz, çünkü referansın canlı mı yoksa ölü mü olduğunu derleme zamanında bilemez.
"ESNext" veya "ES2022" seçildiğinde TypeScript import/export söz dizimini değiştirmez, statik kalır. Bundler'lar statik import grafiğini analiz ederek kullanılmayan dışa aktarımları ayıklayabilir. Özellikle yalnızca birkaç yardımcı fonksiyon kullandığınız büyük kütüphanelerde bu fark hissedilir boyuta taşınabilir. Production build yapılandırmasında modül formatı seçimi ve bundler uyumu sık sık gözden kaçırılan bir noktadır.
module: "ESNext" seçildiğinde moduleResolution da buna uygun olmalıdır. Genellikle "bundler" veya "node16" tercih edilir. İkisi uyumsuz olduğunda çeşitli import çözümleme hataları ortaya çıkar. En yaygın senaryo şudur: module: "ESNext" yazılmış, ama moduleResolution eski değerinde kalmış; bundler ESM formatını görür, ancak TypeScript bazı path'leri doğru çözemez ve build kırılır. Bu durumda geliştiriciler sorunu modül formatına değil araçlara bağlar ve gerçek nedeni kaçırır.
strict Modu Bundle'ı Direkt Etkilemez, Ama Tehlikesi Var
strict: true, noImplicitAny, strictNullChecks, strictFunctionTypes gibi birçok bayrağı aynı anda açar. Bu bayraklar tamamen tip düzeyinde çalışır; emit edilen JavaScript'i değiştirmezler. Bu anlamda doğrudur: strict: true veya false seçmek bundle boyutunu doğrudan değiştirmez.
Tehlike dolaylı yoldan gelir. strict: false olan bir projede any türü serbest dolaşır. Tip güvencesi olmadan yazılan kod, gereksiz null kontrolü, ek dallanma ve savunmacı blokları da beraberinde getirir. Bu kod bundle'a girer ve çoğunlukla çok az çalışan yollarda bulunur.
Bir diğer dolaylı etki: sıkı tip kontrolü olmayan projelerde kütüphane entegrasyonları belirsizleşir. Geliştiriciler kullandıkları şeyin ne olduğundan emin olmadıklarında, ihtiyaçları olup olmadığından bağımsız olarak fazla import yapma eğilimi gösterir. Tree-shaking ne kadar iyi olursa olsun, import edilmeyen kodu analiz edemez; gereksiz import'ların öncelikle kaldırılması gerekir. Bu mesele, özellikle çok bileşenli uygulamalarda bileşen başına taşınan kütüphane yüküyle doğrudan bağlantılıdır.
downlevelIteration Helper Patlamasını Nasıl Tetikler?
for...of, spread ve destructuring gibi iterator protokolüne dayanan yapılar, target değeri düştüğünde basit döngülere dönüştürülür. Bir dizi üzerinde for...of kullanıyorsanız TypeScript bunu index tabanlı bir döngüye çevirir ve ek boyut eklenmez. Basit dönüşüm.
Ancak downlevelIteration: true açılırsa durum değişir. Bu bayrak, custom iterator protokolünü (Symbol.iterator) tam anlamıyla desteklemek için daha kapsamlı emit üretir. Her dosyaya __values, __read, __spreadArray gibi helper fonksiyonlar eklenir. Bu fonksiyonlar tekrarlanır: 50 bileşenli bir projede aynı helper'lar 50 kez bundle'a girebilir, çünkü varsayılan davranış inline eklemedir.
Ne zaman gereklidir? Gerçekten custom iterator yazan, Set, Map veya generator fonksiyonu döndüren API'lerle çalışan projeler için anlamlıdır. Yalnızca diziler üzerinde dönen bir proje için downlevelIteration: true gereksizdir ve peşin bir helper maliyeti öder. Bu tekrarı engellemek için bir sonraki bölümde ele alınan importHelpers devreye girer; ancak asıl soruyu sormak daha kısadır: bu bayrağa gerçekten ihtiyacınız var mı?
importHelpers ve tslib: Helper Şişmesini Önleme
TypeScript'in ürettiği helper fonksiyonlar (__awaiter, __generator, __spreadArray vb.) varsayılan olarak her dosyaya inline eklenir. Yüz dosyalık bir projede her dosyada aynı __awaiter bloğu tekrar eder. Bundler bu tekrarları birleştiremez, çünkü her biri bağımsız bir fonksiyon tanımıdır.
importHelpers: true açıldığında TypeScript bu fonksiyonları inline üretmez, bunun yerine tslib paketinden import eder. tslib bu helper'ların tek merkezi kaynağıdır; bundler da bu merkezi kaynağı bir kez bundle'a alır, dosya sayısından bağımsız olarak. İlke basittir: aynı kodu tekrar tekrar gömmek yerine bir kez al, ihtiyaç duyan her yer oraya baksın.
Yalnızca importHelpers: true yazmak yeterli değildir. tslib'in devDependencies değil dependencies altında kurulu olması gerekir. Yalnızca devDependencies'e eklenirse üretim build'inde tslib bulunamaz ve derleme başarısız olur. Helper sayısı az olan küçük projelerde bu ayarın etkisi sınırlı kalır; ancak büyük projelerde veya düzinelerce async fonksiyon barındıran uygulamalarda fark ölçülebilir olur. Monorepo yapılarında performans disiplinini korurken bu tür konfigürasyon kararları birikimli etkiyle kendini gösterir; tek başına küçük görünen her seçenek, paket sayısı arttıkça katlanır.
sourceMap ve declaration Seçeneklerinin Production'a Sızması
Kaynak haritalar (sourceMap: true) geliştirme sürecinde hata ayıklamayı kolaylaştırır. Production build'ine inline eklenen kaynak haritalar (inlineSourceMap: true) ise dosya boyutunu önemli ölçüde şişirir; kaynak harita bazen ana koddan büyük olabilir. Son kullanıcı bu veriyi indirir, ancak asla kullanamaz.
Doğru yaklaşım: production'da sourceMap: true ile ayrı .map dosyaları üretmek ve bu dosyaları hata izleme servisine göndermek, son kullanıcıya iletmemek. inlineSourceMap ve inlineSources kombinasyonuyla .map dosyaları bundle içine gömüldüğünde kullanıcı her oturumda bu veriyi indirir. Bundler'ın bu seçenekleri override edip etmediğini kontrol etmek gerekir; bazı build araçları TypeScript'in kaynak harita ayarını yok sayıp kendi yapılandırmasını uygular.
declaration: true ise TypeScript tip tanımı dosyaları (.d.ts) üretir. Yayınlanan kütüphaneler için zorunludur, uygulama kodu için gereksizdir. Uygulama reposunda declaration: true açık kaldıysa .d.ts dosyaları üretilir; bundler bunları bundle'a dahil etmez, ama build süresi uzar ve çıktı dizini gereksiz dosyalarla dolar. declarationMap: true de aynı şekilde yalnızca kütüphane repoları için anlamlıdır.
Yapılandırmaları Build Çıktısıyla Doğrulamak
tsconfig değişikliklerinin gerçek etkisini görmek için build çıktısını incelemek gerekir. Yalnızca tsc --noEmit ile tip hata kontrolü yapmak çıktı boyutunu doğrulamaz; emit'i açıp çıktıyı gözlemlemek ayrı bir adımdır.
İlk adım: tsc ile JavaScript çıktısı üretip üretilen .js dosyalarını incelemek. Helper fonksiyonlar inline mı, yoksa tslib'den import mu ediliyor? Async/await olduğu gibi mi bırakıldı, yoksa __awaiter'a mı dönüştürüldü? Bu soruları üretilen koda bakarak yanıtlayabilirsiniz; tahmin gerekmez.
İkinci adım: bundler üzerinden geçirip son bundle'ı analiz etmek. Tree-shaking'in gerçekten çalışıp çalışmadığını anlamak için module: "CommonJS" ile "ESNext" arasında geçiş yapıp bundle boyutlarını karşılaştırın. Kütüphane seçiminin bundle maliyetine etkisinde de aynı ölçüm mantığı geçerlidir: ayarı değiştir, build al, boyutu oku. Yayın öncesi sistematik kontroller bu tür konfigürasyon noktalarını atlamadan geçmeye yardımcı olur.
TypeScript'in tip bilgisini silmesi, tsconfig'in etkisiz olduğu anlamına gelmez. target, module, importHelpers ve kaynak harita seçenekleri birlikte çalışarak üretilen JavaScript'in boyutunu, yapısını ve bundler uyumluluğunu belirler. Yanlış kombinasyon, tip sisteminden bağımsız olarak gereksiz helper kodu, tree-shaking kırılması veya inline kaynak harita şişmesi üretir.
Hangi ayarın bundle'ı neden etkilediğini anlamak, sezgiye değil çıktıya bakarak mümkün olur. tsc emit'i ve son bundle büyüklüğü, konfigürasyon kararlarının doğrulanabileceği tek yer burası; geri kalan her şey tahmin.