اگر آپ نے کبھی کسی TypeScript monorepò میں دو لائنوں کا bugfix پش کیا ہو اور اپنے build pipeline کو دس منٹ تک type checking میں مصروف دیکھا ہو، تو آپ پہلے ہی مسئلے کو سمجھ چکے ہیں۔ تاخیر bundling کی وجہ سے نہیں ہے۔ یہ minification کی وجہ سے بھی نہیں ہے۔ یہ وہ لمحہ ہے جب TypeScript آپ کی declaration files لکھنے کی کوشش کرتا ہے۔ ایک بھی .d.ts فائل جاری کرنے سے پہلے، compiler کو مکمل type graph کو حل کرنا پڑتا ہے۔ یہ imports کو تلاش کرتا ہے، generics کا جائزہ لیتا ہے، اور ایسے return types کا اندازہ لگاتا ہے جو شاید تین dependency layers کی گہرائی میں ہوں۔ وہ ایک فائل جسے آپ نے تبدیل کیا، ایک دوسری فائل کو متاثر کرتی ہے، جو تیسری کو متاثر کرتی ہے، اور اچانک compiler صرف یہ بتانے کے لیے کہ آپ کا function کیا return کرتا ہے، پورے repository میں سراغ رساں (detective) والا کام شروع کر دیتا ہے۔

TypeScript 6.0 اس عادت کو ختم کرنے کے لیے isolatedDeclarations متعارف کرواتا ہے۔

اصل رکاوٹ

Declaration files آپ کے کوڈ کا عوامی معاہدہ (public contract) ہوتی ہیں۔ جب کوئی دوسرا ڈویلپر آپ کا package import کرتا ہے، تو TypeScript source کے بجائے .d.ts فائلیں پڑھتا ہے۔ ان فائلوں کو درست طریقے سے تیار کرنے کا مطلب یہ ہے کہ compiler type checker کو نظر انداز نہیں کر سکتا۔ اسے آؤٹ پٹ کی ایک لائن لکھنے سے پہلے ہر shape، ہر union، اور ہر inferred return type کا علم ہونا ضروری ہے۔

پچاس فائلوں والے چھوٹے پروجیکٹ میں، یہ کام فوری ہو جاتا ہے۔ ہزاروں modules والے بڑے monorepo میں، یہ ایک مسلسل ڈراؤنا خواب بن جاتا ہے۔ کسی core package میں ایک utility type تبدیل کریں، اور compiler ہر consumer کا دوبارہ جائزہ لیتا ہے تاکہ یہ تصدیق کر سکے کہ inferred types اب بھی درست ہیں۔ build time صرف فائلوں کی تعداد کے ساتھ نہیں بلکہ dependency کی گہرائی کے ساتھ بڑھتا ہے۔ ایک معمولی سا refactor بھی پورے graph کی دوبارہ گنتی (recomputation) کا سبب بن سکتا ہے۔ ٹیمیں اکثر اسے ناگزیر سمجھ کر قبول کر لیتی ہیں۔ ایسا نہیں ہے۔

isolatedDeclarations کیسے کام کرتا ہے

نیا flag آپ اور compiler کے درمیان معاہدے کو بدل دیتا ہے۔ TypeScript سے اپنے exports کی types کا اندازہ لگانے (infer کرنے) کے بجائے، آپ خود انہیں annotate کرتے ہیں۔ یہ ایک تبدیلی عالمی معلومات (global knowledge) کی ضرورت کو ختم کر دیتی ہے۔ اب compiler کو آپ کے module کے لیے declarations جاری کرنے کے لیے آپ کی dependencies کا تجزیہ کرنے کی ضرورت نہیں ہے۔ یہ صرف اپنے سامنے موجود syntax کو دیکھتا ہے۔

اس کا مطلب ہے کہ ہر فائل اپنی .d.ts آؤٹ پٹ آزادانہ طور پر اور متوازی (in parallel) جاری کر سکتی ہے۔ Build tool کو declaration generation شروع کرنے سے پہلے مکمل type graph بنانے کی ضرورت نہیں ہے۔ esbuild اور swc جیسے tools، جو پہلے type checker نہ ہونے کی وجہ سے .d.ts emit کرنے سے گریز کرتے تھے، اب transpilation کی رفتار کے قریب declarations تیار کر سکتے ہیں۔ وہ براہ راست source سے explicit annotations پڑھتے ہیں اور کسی بھی type equation کو حل کیے بغیر متعلقہ type definitions لکھ دیتے ہیں۔

پہلے، declaration emit پر صرف tsc کی اجارہ داری تھی کیونکہ صرف official compiler کے پاس .d.ts فائلیں تیار کرنے کے لیے ضروری type information ہوتی تھی۔ تیز رفتار transpilers types کو ہٹا سکتے تھے یا syntax کو تبدیل کر سکتے تھے، لیکن وہ type definitions تیار نہیں کر سکتے تھے۔ isolatedDeclarations کے ساتھ، پورے ecosystem کے لیے declaration workflows کو سنبھالنے کے دروازے کھل جاتے ہیں۔ تبدیلی منطقی (logical) ہونے کے بجائے میکانکی (mechanical) ہو جاتی ہے۔ یہی وہ فرق ہے جو دس منٹ کے انتظار کو سیکنڈز میں بدل دیتا ہے۔

سمجھوتہ: Explicit اب نیا ڈیفالٹ ہے

رفتار کے ساتھ ایک سیدھا سا معاوضہ (price) آتا ہے۔ ہر exported function، class، اور variable میں ایک explicit type annotation ہونا ضروری ہے۔ TypeScript آپ کے لیے public API کا اندازہ لگانے (infer کرنے) سے انکار کر دے گا۔

ایک سادہ utility پر غور کریں:

export function getUser(id: number) {
  return fetchUser(id);
}

isolatedDeclarations فعال ہونے کے ساتھ، اس میں error آئے گا۔ Compiler fetchUser کے return type کا پتہ اس کا معائنہ کیے بغیر نہیں لگا سکتا، اور اس flag کے تحت