TypeScript projeleri büyür. Dosyalar çoğalır. Bağımlılıklar birbirine dolanır. Ve sonunda, build işleminiz mantığınızın karmaşıklığıyla değil, derleyicinin tek bir deklarasyon dosyası yazabilmek için tüm evreni okumak zorunda kalmasıyla ilgili bir duvara çarpar.

TypeScript 6.0, bu sorunu isolatedDeclarations ile ele alıyor. Bu özellik, .d.ts dosyalarının nasıl oluşturulduğunu yeniden düşünüyor. Deklarasyon üretimini (declaration emission) tam tip kontrolü (type-checking) hattına bağlamak yerine, derleyicinin bu dosyaları her bir kaynak dosyaya tek başına bakarak üretmesine olanak tanıyor. Sonuç, bağımlılık grafiğinizde her bağlantıyı tek tek takip etmek yerine, binlerce dosya üzerinde paralel olarak çalışabilen bir build sürecidir.

Gerçek Darboğaz

Şu anda, deklarasyon dosyaları oluşturmak seri bir işlemdir. --declaration bayrağını etkinleştirip derleyiciyi çalıştırdığınızda, TypeScript, ilgili modülün dokunduğu her tipi tam olarak anlamadan o modül için bir .d.ts dosyası üretemez. Eğer utils.ts, types.ts dosyasından tipler içe aktarıyorsa ve types.ts de api.ts dosyasından bir şeyler çekiyorsa, derleyici utils.ts dosyasının neyi dışa aktardığını tanımlamadan önce bu zinciri çözmek zorundadır.

Büyük bir monorepo'da bu zincirleme etki yıkıcıdır. İçe aktarma grafiğinizin köküne yakın tek bir dosya, yüzlerce alt dosya için deklarasyon üretimini engelleyebilir. İşlemciniz sekiz çekirdekli olabilir ancak TypeScript paket sınırları arasındaki her bir arayüzün (interface) yapısını titizlikle yeniden oluştururken bu çekirdeklerin yedisi boşta bekler. Derleyici gerekli işi yapmaktadır, ancak tip kontrolü ile deklarasyon üretimi arasındaki bu sıkı bağ, sadece halka açık (public) yüzey tiplerinin diske yazılmasını isteseniz bile dosya arası analizlerin tüm maliyetine katlanmanız gerektiği anlamına gelir.

IsolatedDeclarations Kuralları Nasıl Değiştiriyor?

isolatedDeclarations bu bağı koparır. Bayrak etkinleştirildiğinde derleyici, başka hiçbir dosyaya bir şeyin ne anlama geldiğini sormadan bir kaynak dosyası için .d.ts dosyası üretmeyi kabul eder. Bunu basit bir sözleşme şart koşarak yapar: dışa aktarılan her sembol, tanımlandığı yerde açık ve görünür bir tip anotasyonu (type annotation) taşımalıdır.

Eğer derleyici, doğrudan kaynak kodda yazılı olan tam tipi görebiliyorsa, çıkarım (inference) yapmasına gerek kalmaz. İçe aktarmaların peşine düşmesine gerek yoktur. Başka bir dosyadaki User tanımlayıcısının bir interface mi, bir type alias mı yoksa bir class mı olduğunu bilmesine gerek yoktur. Sadece yazdığınız şeyi tam olarak üretir.

Bu, A dosyasının ve B dosyasının deklarasyonlarını eş zamanlı olarak oluşturabileceği anlamına gelir. Bir build orkestratörü her dosyayı ayrı bir iş parçacığına (thread) verebilir. Daha önce tam bir tip kontrolcüsünden yoksun oldukları için .d.ts üretimini atlayan hızlı transpiler'lar da artık deklarasyon dosyaları üretebilir, çünkü iş tamamen sözdizimsel (syntactic) hale gelir.

Taviz: Açıkça Yazın

Hız bedava gelmez. Dışa aktardığınız herhangi bir şey için tip çıkarımına (type inference) güvenmeyi bırakmalısınız. Her halka açık fonksiyon, sınıf, değişken ve sabit için tipi açıkça belirtilmelidir. Eğer TypeScript, bir dönüş ifadesine (return statement) bakarak veya bir generic argümanı çözerek tipi hesaplamak zorunda kalırsa, isolatedDeclarations hata verecektir.

Bunun pratikte nasıl göründüğüne bakalım. Bayrak olmadan şöyle yazabilirsiniz:

export function fetchUser(id: number) {
  return fetch(`/users/${id}`).then(r => r.json());
}

TypeScript dönüş tipini sırasıyla fetch, ardından Promise.prototype.then ve son olarak r.json() döndüren anonim fonksiyonu inceleyerek çıkarır. Bir .d.ts dosyası üretmek için derleyicinin tüm bu analizi yapması gerekir.

isolatedDeclarations etkinleştirildiğinde, dışa aktarımı şu şekilde anotasyonla belirtmelisiniz:

interface User {
  id: number;
  email: string;
}

export function fetchUser(id: number): Promise<User> {
  return fetch(`/users/${id}`).then(r => r.json());
}

Artık derleyici Promise<User> tipini anında görür. Deklarasyonu üretir ve devam eder.

Bu kural geniş kapsamlıdır. Dışa aktarılan dizilerin (arrays), elemanlarından çıkarım yapmak yerine açık tiplere ihtiyacı vardır. Dışa aktarılan nesnelerin (objects), yapıları tüketiciler için önemliyse açık tip anotasyonlarına ihtiyacı vardır. Generic fonksiyonların dönüş tiplerinin ve kısıtlamalarının (constraints) tanımlandığı yerde görünür olması gerekir. Karmaşık bir mapped type sonucunu, tam olarak yazılmış isimlendirilmiş bir type alias vermeden dışa aktaramazsınız.

Avantajı ise halka açık API'nizin kendi kendini belgeleyen (self-documenting) bir hale gelmesidir. Tüketiciler —ve derleyici— artık niyetinizi uygulama detaylarından tersine mühendislik yaparak anlamak zorunda kalmazlar. Tipler, kasıtlı bir sözleşmedir.

Zaman Nereye Gidiyor?

Büyük bir kod tabanında etkisi anında hissedilir. Dakikalar süren build süreleri saniyelere düşebilir, çünkü deklarasyon üretimi artık sürecin en büyük yavaşlatıcısı olmaktan çıkar. Her dosya bağımsız olarak üretilir, bu nedenle süreç içe aktarma grafiğinizin derinliğiyle değil, sahip olduğunuz çekirdek sayısıyla ölçeklenir.

Bu durum kullanabileceğiniz araçları da değiştirir. esbuild ve swc gibi transpiler'lar TypeScript'i JavaScript'e dönüştürme konusunda zaten ışık hızındadır, ancak birçok ekip sadece .d.ts dosyaları üretmek için hala ayrı olarak tsc çalıştırıyor. isolatedDeclarations ile bu hızlı araçlar her iki işi de halledebilir. Deklarasyonları üretmek için TypeScript'in tüm tip sistemini kopyalamalarına gerek yoktur; sadece söz dizimini ayrıştırmaları ve sağladığınız açık tipleri kopyalamaları yeterlidir. Bu, alternatif araç zincirleriyle uçtan uca TypeScript derlemelerini çok daha uygulanabilir kılar.

Dağıtık ve artımlı (incremental) derlemeler de basitleşiyor. Sürekli entegrasyonda (continuous integration), uzak bir önbellek veya parçalanmış (sharded) bir derleme, bir paketin tüm geçişli bağımlılık grafiğini (transitive dependency graph) önce indirmeye gerek kalmadan deklarasyonlarını üretebilir. Eğer kaynak kodda tipler açıkça belirtilmişse, derleme parçası ihtiyacı olan her şeye sahiptir.

Neler Aynı Kalıyor

Kısıtlama yalnızca export'lara uygulanır. Bir modülün içinde hayat normal akışında devam eder. Yerel değişkenler, özel sınıf üyeleri ve dışa aktarılmamış yardımcı fonksiyonlar hala tam tip çıkarımına (type inference) güvenebilir. TypeScript, bir döngü değişkeninin veya bir closure parametresinin tipini şikayet etmeden memnuniyetle çıkaracaktır.

export function calculateTotal(items: Item[]): number {
  // Local variable: inference is fine
  const taxRate = 0.08;
  
  // Private class member inside a local class: inference is fine
  class Helper {
    private cache = new Map();
  }
  
  return items.reduce((sum, item) => sum + item.price * (1 + taxRate), 0);
}

Sadece dışa aktarılan fonksiyon imzası bir anotasyon gerektiriyordu. Dahili mekanizma esnek ve ifade gücü yüksek kalmaya devam eder. Bu, yazım yükünü katlanılabilir kılar. Her yerde tamamen açık (explicit) bir stile geçmiyorsunuz; sadece her modülün sınırındaki sözleşmeyi resmileştiriyorsunuz.

Kod Tabanınız İçin Doğru mu?

isolatedDeclarations kullanmaya başlamak, zamanınızı nerede harcadığınızı değiştirir. Bir export yazarken birkaç ekstra tuş vuruşu harcarsınız ve karşılığında her derlemede "faiz ödemeyi" bırakırsınız. Kütüphane yazarları için bu genellikle kolayca kabul gören bir durumdur. Kamuya açık API'lar zaten muhtemelen anotasyon gerektiriyordur. Kapalı bir monorepo içinde çalışan uygulama geliştiricileri için başlangıç maliyeti gereksiz bir formalite gibi gelebilir. Ancak ekibiniz derleme süresini kahve molalarıyla ölçüyorsa, bu takas hızla cazip hale gelir.

Bunu kademeli olarak uygulayabilirsiniz. Bayrağı (flag) etkinleştirin, derleyiciyi çalıştırın ve dışa aktarılan sembollerde ortaya çıkan hataları düzeltin. Hata mesajları size hangi dışa açık tiplerin örtük (implicit) olduğunu tam olarak söyler. Bunları düzeltin, dahili kısımları kendi haline bırakın ve deklarasyon adımınızın hızlanmasını izleyin.

Unutulmaması gereken bir şey var: bu bayrak TypeScript'in tip denetleyicisini (type checker) kendisini hızlandırmaz. Editörünüzde daha hızlı geri bildirim veya daha hızlı tsc --noEmit çalıştırmaları istiyorsanız, hala proje referanslarına (project references), daha sıkı dosya dahil etme kurallarına veya diğer mimari düzeltmelere ihtiyacınız vardır. isolatedDeclarations özellikle .d.ts dosyalarının üretilmesini hedefler. Bu bir derleme optimizasyonudur, tip denetimi optimizasyonu değil.

Asıl Çıkarım

isolatedDeclarations, kamuya açık tiplerinizi birinci sınıf varlıklar (first-class artifacts) olarak ele almanızı ister. Derleyicinin onları tahmin etmesine izin vermeyi bırakın. Onları açıkça yazın. Bunu yaptığınızda, derleyici her deklarasyon dosyası üretmesi gerektiğinde tüm bağımlılık grafınızda dolaşmayı bırakır. Paralel olarak çıktı üretir, esbuild ve swc gibi araçlar tam TypeScript iş akışlarını yönetir ve monorepo derlemeleriniz hantallaşmayı bırakır.

Maliyet, derleme süresinden yazım süresine kayar. Büyümekte olan çoğu ekip için bu, yapmaya değer bir takastır.