Projek TypeScript semakin berkembang. Fail semakin banyak. Kebergantungan (dependencies) menjadi berselirat. Dan akhirnya, proses binaan (build) anda menemui jalan buntu yang tidak berkaitan dengan kerumitan logik anda, sebaliknya berkaitan dengan keperluan pengkompil (compiler) untuk membaca seluruh alam semesta sebelum ia dapat menulis satu fail deklarasi sahaja.
TypeScript 6.0 menangani perkara ini dengan isolatedDeclarations. Ciri ini memikirkan semula cara fail .d.ts dihasilkan. Daripada mengaitkan pengeluaran (emission) deklarasi kepada saluran paip (pipeline) semakan jenis (type-checking) yang lengkap, ia membolehkan pengkompil mengeluarkan fail tersebut dengan melihat setiap fail sumber secara berasingan (in isolation). Hasilnya ialah proses binaan yang boleh berjalan secara selari merentasi beribu-ribu fail, berbanding merangkak melalui graf kebergantungan anda satu demi satu.
Kekangan Sebenar
Pada masa ini, penjanaan fail deklarasi adalah operasi bersiri. Apabila anda mengaktifkan --declaration dan menjalankan pengkompil, TypeScript tidak dapat mengeluarkan fail .d.ts untuk modul tertentu sehingga ia memahami sepenuhnya setiap jenis (type) yang disentuh oleh modul tersebut. Jika utils.ts mengimport jenis daripada types.ts, dan types.ts mengambil sesuatu daripada api.ts, pengkompil mesti menyelesaikan rantaian tersebut sebelum ia dapat menerangkan apa yang dieksport oleh utils.ts.
Dalam monorepo yang besar, kesan berantai ini sangat dahsyat. Satu fail tunggal berhampiran akar (root) graf import anda boleh menyekat pengeluaran deklarasi untuk beratus-ratus fail hiliran (downstream). CPU anda mempunyai lapan teras, tetapi tujuh daripadanya terbiar tanpa kerja sementara TypeScript berusaha keras membina semula bentuk setiap antara muka (interface) merentasi sempadan pakej. Pengkompil sedang melakukan kerja yang perlu, tetapi kaitan antara semakan jenis dan pengeluaran deklarasi bermakna anda menanggung kos penuh analisis rentas fail walaupun anda hanya mahu jenis permukaan awam (public surface types) ditulis ke dalam cakera.
Bagaimana IsolatedDeclarations Mengubah Peraturan
isolatedDeclarations memutuskan kaitan tersebut. Apabila bendera (flag) ini diaktifkan, pengkompil bersetuju untuk mengeluarkan fail .d.ts bagi fail sumber tanpa bertanya kepada fail lain tentang maksud sesuatu perkara. Ia melakukan ini dengan memerlukan kontrak yang mudah: setiap simbol yang dieksport mesti membawa anotasi jenis (type annotation) yang eksplisit dan jelas pada lokasi ia diisytiharkan.
Jika pengkompil dapat melihat jenis penuh yang ditulis terus di dalam sumber, ia tidak perlu melakukan inferens (inference). Ia tidak perlu mengejar import. Ia tidak perlu tahu sama ada pengenal pasti User dalam fail lain adalah sebuah antara muka (interface), alias jenis (type alias), atau kelas. Ia hanya mengeluarkan apa yang anda tulis.
Ini bermakna fail A dan fail B boleh menjana deklarasi mereka secara serentak. Pengatur binaan (build orchestrator) boleh menyerahkan setiap fail kepada bebenang (thread) yang berasingan. Transpiler pantas yang sebelum ini melangkau penjanaan .d.ts kerana kekurangan penyemak jenis yang lengkap kini boleh menghasilkan fail deklarasi juga, kerana kerjanya menjadi bersifat sintaksik semata-mata.
Pertukaran (Tradeoff): Tuliskan Ia
Kelajuan tidak datang secara percuma. Anda mesti berhenti bergantung pada inferens jenis untuk apa sahaja yang anda eksport. Setiap fungsi, kelas, pemboleh ubah, dan pemalar awam memerlukan jenisnya dinyatakan secara eksplisit. Jika TypeScript perlu mengira jenis dengan melihat pernyataan pulangan (return statement) atau menyelesaikan argumen generik, isolatedDeclarations akan menghasilkan ralat.
Berikut adalah gambaran praktikalnya. Tanpa bendera tersebut, anda mungkin menulis:
export function fetchUser(id: number) {
return fetch(`/users/${id}`).then(r => r.json());
}
TypeScript membuat inferens jenis pulangan dengan memeriksa fetch, kemudian Promise.prototype.then, kemudian fungsi tanpa nama yang memulangkan r.json(). Untuk mengeluarkan .d.ts, pengkompil perlu melakukan semua analisis tersebut.
Dengan isolatedDeclarations diaktifkan, anda mesti membuat anotasi pada eksport tersebut:
interface User {
id: number;
email: string;
}
export function fetchUser(id: number): Promise<User> {
return fetch(`/users/${id}`).then(r => r.json());
}
Sekarang pengkompil dapat melihat Promise<User> dengan serta-merta. Ia mengeluarkan deklarasi tersebut dan meneruskan tugas seterusnya.
Peraturan ini terpakai secara meluas. Tataliti (arrays) yang dieksport memerlukan jenis eksplisit dan bukannya melalui inferens daripada elemen-elemennya. Objek yang dieksport memerlukan anotasi jenis eksplisit jika bentuknya penting kepada pengguna (consumers). Fungsi generik memerlukan jenis pulangan dan kekangan (constraints) mereka kelihatan pada lokasi deklarasi. Anda tidak boleh mengeksport hasil daripada jenis pemetaan (mapped type) yang kompleks tanpa memberikannya alias jenis bernama yang ditulis sepenuhnya.
Kelebihannya ialah API awam anda menjadi dokumentasi kendiri. Pengguna—dan pengkompil—tidak lagi perlu melakukan kejuruteraan berbalik (reverse-engineer) terhadap niat anda daripada butiran pelaksanaan. Jenis-jenis tersebut adalah satu kontrak yang sengaja ditetapkan.
Ke Mana Masa Digunakan
Dalam kod asas (codebase) yang besar, impaknya adalah serta-merta. Masa binaan yang mengambil masa beritit-minit boleh turun kepada saat sahaja kerana pengeluaran deklarasi tidak lagi menjadi penghalang utama. Setiap fail dikeluarkan secara bebas, jadi proses ini berskala mengikut jumlah teras yang anda miliki, bukan mengikut kedalaman graf import anda.
This also changes what tools you can use. Transpilers like esbuild and swc are already lightning-fast at turning TypeScript into JavaScript, but many teams still run tsc separately just to produce .d.ts files. With isolatedDeclarations, those fast tools can handle both jobs. They do not need to replicate TypeScript's entire type system to generate declarations; they only need to parse syntax and copy the explicit types you provided. That makes end-to-end TypeScript builds with alternative toolchains far more viable.
Distributed and incremental builds get simpler too. In continuous integration, a remote cache or a sharded build can emit declarations for a package without downloading its full transitive dependency graph first. If the types are explicit in the source, the build shard has everything it needs.
What Stays the Same
The constraint applies only to exports. Inside a module, life continues as normal. Local variables, private class members, and unexported helper functions can still rely on full type inference. TypeScript will happily infer the type of a loop variable or a closure parameter without complaint.
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);
}
Only the exported function signature needed an annotation. The internal machinery stays loose and expressive. This keeps the authoring burden tolerable. You are not switching to a fully explicit style everywhere; you are simply formalizing the contract at the boundary of each module.
Is It Right for Your Codebase?
Adopting isolatedDeclarations shifts where you spend your time. You invest a few extra keystrokes when you write an export, and in exchange you stop paying interest on every build. For library authors, this is often an easy sell. Public APIs should probably be annotated anyway. For application developers working inside a closed monorepo, the upfront cost can feel like unnecessary ceremony. But if your team measures build time in coffee breaks, the trade becomes attractive quickly.
You can adopt it incrementally. Enable the flag, run the compiler, and fix the errors it surfaces on exported symbols. The error messages tell you exactly which public-facing types are implicit. Fix those, leave the internals alone, and watch your declaration step accelerate.
One thing to remember: this flag does not make TypeScript's type checker itself faster. If you want quicker feedback in your editor or faster tsc --noEmit runs, you still need project references, stricter file inclusion, or other architectural fixes. isolatedDeclarations specifically targets the emission of .d.ts files. It is a build optimization, not a type-checking optimization.
The Real Takeaway
isolatedDeclarations asks you to treat your public types as first-class artifacts. Stop making the compiler deduce them. Write them down. Once you do, the compiler stops crawling through your entire dependency graph every time it needs to generate a declaration file. It emits in parallel, tools like esbuild and swc handle full TypeScript workflows, and your monorepo builds stop dragging.
The cost moves from build time to authoring time. For most growing teams, that is a trade worth making.
