TypeScript প্রজেক্ট বড় হতে থাকে। ফাইলের সংখ্যা বাড়তে থাকে। ডিপেন্ডেন্সিগুলো জটিল হয়ে ওঠে। এবং শেষ পর্যন্ত, আপনার বিল্ড এমন এক বাধার সম্মুখীন হয় যার সাথে আপনার লজিকের জটিলতার কোনো সম্পর্ক নেই, বরং এর সম্পর্ক রয়েছে কম্পাইলারের সাথে—যেখানে একটি মাত্র ডিক্লারেশন ফাইল লেখার জন্য কম্পাইলারকে পুরো মহাবিশ্ব পড়ে ফেলতে হয়।
TypeScript 6.0 এই সমস্যার সমাধান করে isolatedDeclarations-এর মাধ্যমে। এই ফিচারটি .d.ts ফাইল তৈরির প্রক্রিয়াকে নতুনভাবে চিন্তা করে। ডিক্লারেশন এমিশনকে (declaration emission) সম্পূর্ণ টাইপ-চেকিং পাইপলাইনের সাথে যুক্ত না করে, এটি কম্পাইলারকে প্রতিটি সোর্স ফাইল আলাদাভাবে (in isolation) দেখে সেই ফাইলগুলো তৈরি করতে দেয়। এর ফলে একটি বিল্ড প্রসেস তৈরি হয় যা আপনার ডিপেন্ডেন্সি গ্রাফের প্রতিটি লিঙ্ক একে একে অনুসরণ করার পরিবর্তে হাজার হাজার ফাইলের ওপর সমান্তরালভাবে (in parallel) চলতে পারে।
আসল বাধা
বর্তমানে, ডিক্লারেশন ফাইল তৈরি করা একটি সিরিয়াল অপারেশন। যখন আপনি --declaration এনাবল করেন এবং কম্পাইলার চালান, TypeScript কোনো নির্দিষ্ট মডিউলের জন্য .d.ts ফাইল তৈরি করতে পারে না যতক্ষণ না সেটি ওই মডিউলের সাথে যুক্ত প্রতিটি টাইপ পুরোপুরি বুঝতে পারে। যদি utils.ts ফাইলটি types.ts থেকে টাইপ ইমপোর্ট করে, এবং types.ts ফাইলটি api.ts থেকে কিছু নিয়ে আসে, তবে utils.ts কী এক্সপোর্ট করছে তা বর্ণনা করার আগে কম্পাইলারকে অবশ্যই সেই চেইনটি সমাধান করতে হবে।
একটি বড় মনোরিপো (monorepo)-তে এই প্রভাব অত্যন্ত ভয়াবহ হতে পারে। আপনার ইমপোর্ট গ্রাফের রুট বা শুরুর দিকের একটি মাত্র ফাইল শত শত ডাউনস্ট্রিম ফাইলের ডিক্লারেশন এমিশনকে আটকে দিতে পারে। আপনার CPU-তে আটটি কোর থাকতে পারে, কিন্তু TypeScript যখন প্রতিটি প্যাকেজ বাউন্ডারির মধ্যে প্রতিটি ইন্টারফেসের গঠন অত্যন্ত নিখুঁতভাবে পুনর্গঠন করতে ব্যস্ত থাকে, তখন তার সাতটি কোর অলস বসে থাকে। কম্পাইলার প্রয়োজনীয় কাজই করছে, কিন্তু টাইপ চেকিং এবং ডিক্লারেশন এমিশনের মধ্যকার এই কাপলিংয়ের (coupling) কারণে, আপনি যখন শুধুমাত্র পাবলিক সারফেস টাইপগুলো ডিস্কে লিখতে চান, তখনও আপনাকে ক্রস-ফাইল অ্যানালাইসিসের পুরো মূল্য দিতে হয়।
কীভাবে IsolatedDeclarations নিয়ম বদলে দেয়
isolatedDeclarations সেই কাপলিং ভেঙে দেয়। যখন এই ফ্ল্যাগটি এনাবল করা হয়, কম্পাইলার অন্য কোনো ফাইলকে না জিজ্ঞেস করেই একটি সোর্স ফাইলের জন্য .d.ts ফাইল তৈরি করতে রাজি হয়। এটি করার জন্য একটি সহজ চুক্তি বা কন্ট্রাক্ট প্রয়োজন: প্রতিটি এক্সপোর্টেড সিম্বলকে (exported symbol) যে স্থানে এটি ডিক্লেয়ার করা হয়েছে, সেখানেই একটি স্পষ্ট এবং দৃশ্যমান টাইপ অ্যানোটেশন (type annotation) থাকতে হবে।
যদি কম্পাইলার সোর্সের মধ্যেই সম্পূর্ণ টাইপটি দেখতে পায়, তবে তাকে ইনফারেন্স (inference) করার প্রয়োজন হয় না। তাকে ইমপোর্টগুলো অনুসরণ করতে হয় না। অন্য কোনো ফাইলের User আইডেন্টিফায়ারটি একটি ইন্টারফেস, টাইপ অ্যালিয়াস নাকি ক্লাস, তা জানারও প্রয়োজন হয় না। এটি কেবল আপনি যা লিখেছেন ঠিক সেটিই এমিট করে।
এর মানে হলো ফাইল A এবং ফাইল B একই সাথে তাদের ডিক্লারেশন তৈরি করতে পারে। একটি বিল্ড অর্কেস্ট্রেটর প্রতিটি ফাইলকে আলাদা আলাদা থ্রেডে পাঠিয়ে দিতে পারে। দ্রুত ট্রান্সপিলারগুলো (transpilers), যা আগে পূর্ণাঙ্গ টাইপ চেকার না থাকায় .d.ts জেনারেশন বাদ দিয়ে দিত, তারা এখন ডিক্লারেশন ফাইল তৈরি করতে পারবে, কারণ কাজটি এখন সম্পূর্ণ সিনট্যাকটিক (syntactic) হয়ে দাঁড়িয়েছে।
ট্রেডঅফ: লিখে রাখুন
গতি বিনামূল্যে আসে না। আপনি যা এক্সপোর্ট করছেন তার জন্য টাইপ ইনফারেন্সের ওপর নির্ভর করা বন্ধ করতে হবে। প্রতিটি পাবলিক ফাংশন, ক্লাস, ভেরিয়েবল এবং কনস্ট্যান্টের টাইপ স্পষ্টভাবে উল্লেখ করতে হবে। যদি TypeScript-কে একটি রিটার্ন স্টেটমেন্ট দেখে বা একটি জেনেরিক আর্গুমেন্ট সমাধান করে টাইপটি গণনা করতে হয়, তবে isolatedDeclarations এরর (error) দেখাবে।
বাস্তবে এটি দেখতে কেমন হয় তা নিচে দেওয়া হলো। ফ্ল্যাগটি ছাড়া আপনি হয়তো এভাবে লিখতেন:
export function fetchUser(id: number) {
return fetch(`/users/${id}`).then(r => r.json());
}
TypeScript fetch, তারপর Promise.prototype.then, এবং তারপর r.json() রিটার্ন করা অ্যানোনিমাস ফাংশনটি পরীক্ষা করে রিটার্ন টাইপ ইনফার করে। একটি .d.ts এমিট করার জন্য কম্পাইলারকে এই সমস্ত অ্যানালাইসিস করতে হয়।
isolatedDeclarations এনাবল করা থাকলে, আপনাকে এক্সপোর্টটি অ্যানোটেশন করতে হবে:
interface User {
id: number;
email: string;
}
export function fetchUser(id: number): Promise<User> {
return fetch(`/users/${id}`).then(r => r.json());
}
এখন কম্পাইলার সাথে সাথে Promise<User> দেখতে পায়। এটি ডিক্লারেশনটি এমিট করে এবং পরবর্তী কাজে চলে যায়।
এই নিয়মটি ব্যাপকভাবে প্রযোজ্য। এক্সপোর্টেড অ্যারেগুলোর ক্ষেত্রে এলিমেন্ট থেকে টাইপ ইনফার করার পরিবর্তে স্পষ্ট টাইপ প্রয়োজন। এক্সপোর্টেড অবজেক্টগুলোর ক্ষেত্রে যদি তাদের গঠন কনজিউমারদের জন্য গুরুত্বপূর্ণ হয়, তবে স্পষ্ট টাইপ অ্যানোটেশন প্রয়োজন। জেনেরিক ফাংশনগুলোর ক্ষেত্রে তাদের রিটার্ন টাইপ এবং কনস্ট্রেইন্টগুলো (constraints) ডিক্লারেশন সাইটেই দৃশ্যমান থাকতে হবে। আপনি কোনো জটিল ম্যাপড টাইপের (mapped type) ফলাফল এক্সপোর্ট করতে পারবেন না যদি না আপনি সেটিকে একটি পূর্ণাঙ্গ নামযুক্ত টাইপ অ্যালিয়াস (named type alias) প্রদান করেন।
এর সুবিধা হলো আপনার পাবলিক এপিআই (API) নিজেই নিজেকে ডকুমেন্ট করে (self-documenting)। কনজিউমার এবং কম্পাইলার—কাউকেই আর ইমপ্লিমেন্টেশন ডিটেইলস থেকে আপনার উদ্দেশ্য রিভার্স-ইঞ্জিনিয়ার করতে হবে না। টাইপগুলো এখন একটি সুনিশ্চিত চুক্তি বা কন্ট্রাক্ট।
সময় কোথায় ব্যয় হয়
একটি বড় কোডবেসে এর প্রভাব তাৎক্ষণিক। বিল্ড টাইম যা কয়েক মিনিট পর্যন্ত স্থায়ী হতো তা কয়েক সেকেন্ডে নেমে আসতে পারে, কারণ ডিক্লারেশন এমিশন আর বিল্ড প্রসেসের সবচেয়ে বড় বাধা হয়ে থাকে না। প্রতিটি ফাইল স্বাধীনভাবে এমিট হয়, তাই এই প্রক্রিয়াটি আপনার ইমপোর্ট গ্রাফের গভীরতার ওপর নয়, বরং আপনার কাছে থাকা কোরের সংখ্যার ওপর ভিত্তি করে স্কেল করে।
এটি আপনি কোন টুল ব্যবহার করতে পারবেন তাও পরিবর্তন করে দেয়। esbuild এবং swc-এর মতো ট্রান্সপাইলারগুলো ইতিমধ্যে TypeScript-কে JavaScript-এ রূপান্তর করতে অত্যন্ত দ্রুত, কিন্তু অনেক টিম এখনও শুধুমাত্র .d.ts ফাইল তৈরির জন্য আলাদাভাবে tsc চালায়। isolatedDeclarations-এর মাধ্যমে, এই দ্রুত টুলগুলো উভয় কাজই সামলাতে পারে। ডিক্লারেশন (declarations) তৈরি করার জন্য তাদের TypeScript-এর সম্পূর্ণ টাইপ সিস্টেমটি পুনরায় তৈরি করার প্রয়োজন নেই; তাদের শুধু সিনট্যাক্স পার্স (parse) করতে হয় এবং আপনার দেওয়া এক্সপ্লিসিট (explicit) টাইপগুলো কপি করতে হয়। এটি বিকল্প টুলচেইন ব্যবহার করে এন্ড-টু-এন্ড TypeScript বিল্ডকে অনেক বেশি কার্যকর করে তোলে।
ডিস্ট্রিবিউটেড (Distributed) এবং ইনক্রিমেন্টাল (incremental) বিল্ডও আরও সহজ হয়ে যায়। কন্টিনিউয়াস ইন্টিগ্রেশনে (continuous integration), একটি রিমোট ক্যাশ বা একটি শার্ডেড বিল্ড (sharded build) কোনো প্যাকেজের সম্পূর্ণ ট্রানজিটিভ ডিপেন্ডেন্সি গ্রাফ (transitive dependency graph) আগে থেকে ডাউনলোড না করেই ডিক্লারেশনগুলো প্রদান করতে পারে। যদি সোর্সে টাইপগুলো এক্সপ্লিসিট থাকে, তবে বিল্ড শার্ডের প্রয়োজনীয় সবকিছুই থাকে।
যা অপরিবর্তিত থাকে
এই সীমাবদ্ধতাটি শুধুমাত্র এক্সপোর্টগুলোর (exports) ক্ষেত্রে প্রযোজ্য। একটি মডিউলের ভেতরে সবকিছু স্বাভাবিকভাবেই চলতে থাকে। লোকাল ভেরিয়েবল, প্রাইভেট ক্লাস মেম্বার এবং আন-এক্সপোর্টেড হেল্পার ফাংশনগুলো এখনও সম্পূর্ণ টাইপ ইনফারেন্সের (type inference) ওপর নির্ভর করতে পারে। TypeScript কোনো অভিযোগ ছাড়াই একটি লুপ ভেরিয়েবল বা ক্লোজার প্যারামিটারের টাইপ ইনফার করতে পারবে।
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) প্রয়োজন ছিল। অভ্যন্তরীণ কার্যপদ্ধতিটি নমনীয় এবং প্রকাশভঙ্গি সম্পন্ন থাকে। এটি কোড লেখার বোঝা সহনীয় রাখে। আপনি সব জায়গায় পুরোপুরি এক্সপ্লিসিট স্টাইলে চলে যাচ্ছেন না; আপনি কেবল প্রতিটি মডিউলের সীমানায় চুক্তিটিকে (contract) আনুষ্ঠানিক রূপ দিচ্ছেন।
এটি কি আপনার কোডবেসের জন্য সঠিক?
isolatedDeclarations গ্রহণ করার ফলে আপনার কাজের সময় ব্যবহারের ধরণ বদলে যায়। আপনি যখন একটি এক্সপোর্ট লিখবেন তখন সামান্য কিছু অতিরিক্ত কি-স্ট্রোক (keystrokes) দেবেন, এবং বিনিময়ে প্রতিটি বিল্ডের ক্ষেত্রে বাড়তি সময় বা ঝামেলা দিতে হবে না। লাইব্রেরি লেখকদের জন্য এটি প্রায়শই একটি সহজ সিদ্ধান্ত। পাবলিক API-গুলোতে সম্ভবত এমনিতেও অ্যানোটেশন থাকা উচিত। একটি ক্লোজড মনোরিপোতে (monorepo) কাজ করা অ্যাপ্লিকেশন ডেভেলপারদের জন্য, প্রাথমিক এই খরচটি অপ্রয়োজনীয় আনুষ্ঠানিকতা বলে মনে হতে পারে। কিন্তু আপনার টিম যদি বিল্ড টাইমকে কফি ব্রেক দিয়ে পরিমাপ করে, তবে এই বিনিময়টি দ্রুত আকর্ষণীয় হয়ে উঠবে।
আপনি এটি ধাপে ধাপে গ্রহণ করতে পারেন। ফ্ল্যাগটি (flag) চালু করুন, কম্পাইলার চালান এবং এক্সপোর্টেড সিম্বলগুলোর (exported symbols) ওপর এটি যে ত্রুটিগুলো দেখায় তা ঠিক করুন। এরর মেসেজগুলো আপনাকে ঠিক বলে দেবে কোন পাবলিক-ফেসিং টাইপগুলো ইমপ্লিসিট (implicit)। সেগুলো ঠিক করুন, ইন্টারনাল বিষয়গুলো যেমন আছে তেমনই রাখুন এবং আপনার ডিক্লারেশন ধাপটি কীভাবে দ্রুততর হচ্ছে তা দেখুন।
একটি কথা মনে রাখা জরুরি: এই ফ্ল্যাগটি TypeScript-এর টাইপ চেকারকে নিজে থেকে দ্রুত করে না। আপনি যদি আপনার এডিটরে দ্রুত ফিডব্যাক বা দ্রুততর tsc --noEmit রান চান, তবে আপনার এখনও প্রজেক্ট রেফারেন্স (project references), আরও কঠোর ফাইল ইনক্লুশন বা অন্যান্য আর্কিটেকচারাল ফিক্সের প্রয়োজন হবে। isolatedDeclarations বিশেষভাবে .d.ts ফাইল তৈরির (emission) লক্ষ্য রাখে। এটি একটি বিল্ড অপ্টিমাইজেশন, টাইপ-চেকিং অপ্টিমাইজেশন নয়।
আসল সারসংক্ষেপ
isolatedDeclarations আপনাকে আপনার পাবলিক টাইপগুলোকে ফার্স্ট-ক্লাস আর্টিফ্যাক্ট (first-class artifacts) হিসেবে বিবেচনা করতে বলে। কম্পাইলারকে সেগুলো অনুমান করতে দেবেন না। সেগুলো লিখে ফেলুন। একবার আপনি এটি করলে, কম্পাইলারকে প্রতিবার ডিক্লারেশন ফাইল তৈরি করার জন্য আপনার সম্পূর্ণ ডিপেন্ডেন্সি গ্রাফের মধ্য দিয়ে খুঁটিয়ে খুঁটিয়ে খুঁজতে হবে না। এটি প্যারালালে (parallel) কাজ করে, esbuild এবং swc-এর মতো টুলগুলো সম্পূর্ণ TypeScript ওয়ার্কফ্লো সামলাতে পারে এবং আপনার মনোরিপো বিল্ডের ধীরগতি বন্ধ হয়ে যায়।
খরচটি বিল্ড টাইম থেকে কোড লেখার সময়ে (authoring time) স্থানান্তরিত হয়। বেশিরভাগ ক্রমবর্ধমান টিমের জন্য, এটি একটি লাভজনক বিনিময়।
