TypeScript projects grow. Files multiply. Dependencies tangle. And eventually, your build hits a wall that has nothing to do with the complexity of your logic and everything to do with the compiler needing to read the entire universe before it can write a single declaration file.

TypeScript 6.0 addresses this with isolatedDeclarations. The feature rethinks how .d.ts files are born. Instead of tying declaration emission to the full type-checking pipeline, it lets the compiler emit those files by looking at each source file in isolation. The result is a build process that can run in parallel across thousands of files rather than crawling through your dependency graph one link at a time.

The Real Bottleneck

Right now, generating declaration files is a serial operation. When you enable --declaration and run the compiler, TypeScript cannot emit a .d.ts file for a given module until it fully understands every type that module touches. If utils.ts imports types from types.ts, and types.ts pulls in something from api.ts, the compiler must resolve that chain before it can describe what utils.ts exports.

In a large monorepo, this cascade is brutal. A single file near the root of your import graph can block declaration emission for hundreds of downstream files. Your CPU has eight cores, but seven of them sit idle while TypeScript painstakingly reconstructs the shape of every interface across package boundaries. The compiler is doing necessary work, but the coupling between type checking and declaration emission means you pay the full cost of cross-file analysis even when you only want the public surface types written to disk.

How IsolatedDeclarations Changes the Rules

isolatedDeclarations breaks that coupling. When the flag is enabled, the compiler agrees to emit a .d.ts file for a source file without asking any other file what anything means. It does this by requiring a simple contract: every exported symbol must carry an explicit, visible type annotation at the site where it is declared.

If the compiler can see the full type written right there in the source, it does not need to perform inference. It does not need to chase imports. It does not need to know whether the identifier User in another file is an interface, a type alias, or a class. It simply emits exactly what you wrote.

This means file A and file B can generate their declarations simultaneously. A build orchestrator can hand each file to a separate thread. Fast transpilers that previously skipped .d.ts generation because they lacked a full type checker can now produce declaration files too, because the work becomes purely syntactic.

The Tradeoff: Write It Down

Speed does not come free. You must stop relying on type inference for anything you export. Every public function, class, variable, and constant needs its type spelled out explicitly. If TypeScript has to compute the type by looking at a return statement or resolving a generic argument, isolatedDeclarations will error.

Here is what that looks like in practice. Without the flag, you might write:

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

TypeScript infers the return type by inspecting fetch, then Promise.prototype.then, then the anonymous function returning r.json(). To emit a .d.ts, the compiler needs to perform all of that analysis.

With isolatedDeclarations enabled, you must annotate the export:

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

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

Now the compiler sees Promise<User> immediately. It emits the declaration and moves on.

This rule applies broadly. Exported arrays need explicit types instead of having them inferred from their elements. Exported objects need explicit type annotations if their shape matters to consumers. Generic functions need their return types and constraints visible at the declaration site. You cannot export the result of a complex mapped type without giving it a named type alias that is written out in full.

The upside is that your public API becomes self-documenting. Consumers—and the compiler—no longer have to reverse-engineer your intent from implementation details. The types are a deliberate contract.

Where the Time Goes

In a large codebase, the impact is immediate. Build times that stretch into minutes can drop to seconds because declaration emission stops being the long pole in the tent. Each file emits independently, so the process scales with the number of cores you have, not with the depth of your import graph.

يغير هذا أيضًا الأدوات التي يمكنك استخدامها. فالمحولات البرمجية (Transpilers) مثل esbuild و swc سريعة للغاية بالفعل في تحويل TypeScript إلى JavaScript، ولكن لا تزال العديد من الفرق تشغل tsc بشكل منفصل فقط لإنتاج ملفات .d.ts. ومع استخدام isolatedDeclarations ، يمكن لهذه الأدوات السريعة القيام بالمهمتين معًا؛ فهي لا تحتاج إلى محاكاة نظام أنواع TypeScript بالكامل لإنشاء التصريحات (declarations)، بل تحتاج فقط إلى تحليل البنية (syntax) ونسخ الأنواع الصريحة التي قدمتها. وهذا يجعل عمليات بناء TypeScript الشاملة باستخدام سلاسل أدوات (toolchains) بديلة أكثر جدوى بكثير.

تصبح عمليات البناء الموزعة والتدريجية أبسط أيضًا. ففي التكامل المستمر (continuous integration)، يمكن لذاكرة التخزين المؤقت عن بُعد أو للبناء المجزأ (sharded build) إصدار التصريحات لحزمة ما دون الحاجة إلى تنزيل رسم التبعيات المتعدية (transitive dependency graph) الكامل الخاص بها أولاً. إذا كانت الأنواع صريحة في المصدر، فسيكون لدى جزء البناء كل ما يحتاجه.

ما الذي سيبقى كما هو

ينطبق القيد فقط على التصديرات (exports). فداخل الوحدة البرمجية (module)، تستمر الأمور كالمعتاد. لا تزال المتغيرات المحلية، وأعضاء الفئات الخاصة (private class members)، والدوال المساعدة غير المصدرة يمكنها الاعتماد على الاستنتاج الكامل للأنواع. سيقوم TypeScript بكل سرور باستنتاج نوع متغير الحلقة أو معامل الإغلاق (closure parameter) دون أي مشكلة.

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);
}

احتاج توقيع الدالة المُصدرة فقط إلى توضيح (annotation). تظل الآلية الداخلية مرنة ومعبرة، مما يحافظ على جعل عبء الكتابة مقبولاً. أنت لا تنتقل إلى أسلوب صريح بالكامل في كل مكان؛ بل تقوم ببساطة بإضفاء الطابع الرسمي على العقد عند حدود كل وحدة برمجية.

هل هذا مناسب لقاعدة الكود الخاصة بك؟

إن اعتماد isolatedDeclarations يغير طريقة استهلاكك لوقتك. فأنت تستثمر بضع ضغطات إضافية على المفاتيح عند كتابة عملية تصدير، وفي المقابل تتوقف عن دفع "الفوائد" مع كل عملية بناء. بالنسبة لمؤلفي المكتبات، غالبًا ما يكون هذا أمرًا سهل الإقناع به، إذ يجب على أي حال توضيح واجهات برمجة التطبيقات (APIs) العامة. أما بالنسبة لمطوري التطبيقات الذين يعملون داخل مستودع واحد مغلق (monorepo)، فقد تبدو التكلفة المسبقة وكأنها إجراءات غير ضرورية. ولكن إذا كان فريقك يقيس وقت البناء باستراحات القهوة، فستصبح هذه المقايضة جذابة بسرعة.

يمكنك اعتمادها تدريجيًا. قم بتفعيل العلامة (flag)، ثم قم بتشغيل المترجم، وأصلح الأخطاء التي تظهر في الرموز المصدرة. ستخبرك رسائل الخطأ بالضبط أي الأنواع المواجهة للجمهور هي أنواع ضمنية. قم بإصلاحها، واترك الأجزاء الداخلية كما هي، وشاهد تسارع خطوة إصدار التصريحات لديك.

شيء واحد يجب تذكره: هذه العلامة لا تجعل مدقق الأنواع في TypeScript نفسه أسرع. إذا كنت تريد تعليقات (feedback) أسرع في المحرر الخاص بك أو تشغيلًا أسرع لـ tsc --noEmit ، فلا تزال بحاجة إلى مراجع المشروع (project references)، أو تضمين ملفات أكثر صرامة، أو إصلاحات معمارية أخرى. يستهدف isolatedDeclarations تحديدًا إصدار ملفات .d.ts. إنه تحسين لعملية البناء، وليس تحسينًا لعملية تدقيق الأنواع.

الخلاصة الحقيقية

يطلب منك isolatedDeclarations معاملة أنواعك العامة كعناصر أساسية (first-class artifacts). توقف عن جعل المترجم يستنتجها؛ بل اكتبها بوضوح. وبمجرد القيام بذلك، سيتوقف المترجم عن التنقل عبر رسم التبعيات الكامل الخاص بك في كل مرة يحتاج فيها إلى إنشاء ملف تصريح. سيقوم بالإصدار بالتوازي، وستتعامل أدوات مثل esbuild و swc مع سير عمل TypeScript الكامل، وستتوقف عمليات بناء الـ monorepo الخاصة بك عن التباطؤ.

تنتقل التكلفة من وقت البناء إلى وقت الكتابة. بالنسبة لمعظم الفرق المتنامية، تعد هذه مقايضة تستحق القيام بها.