TypeScript திட்டங்கள் வளர்கின்றன. கோப்புகள் பெருகுகின்றன. சார்புகள் (Dependencies) சிக்கலடைகின்றன. இறுதியில், உங்கள் பில்ட் (build) செயல்முறை ஒரு தடையைச் சந்திக்கிறது; இது உங்கள் தர்க்கத்தின் (logic) சிக்கலோடு தொடர்புடையது அல்ல, மாறாக ஒரு அறிவிப்பு கோப்பை (declaration file) எழுதுவதற்கு முன்பாகவே கம்பைலர் (compiler) முழு உலகத்தையும் படிக்க வேண்டிய கட்டாயத்தோடு தொடர்புடையது.

TypeScript 6.0 இந்தச் சிக்கலை isolatedDeclarations மூலம் தீர்க்கிறது. இந்த அம்சம் .d.ts கோப்புகள் எவ்வாறு உருவாக்கப்படுகின்றன என்பதை மறுபரிசீலனை செய்கிறது. அறிவிப்பு வெளியீட்டை (declaration emission) முழுமையான வகை-சரிபார்ப்பு (type-checking) செயல்முறையுடன் இணைப்பதற்குப் பதிலாக, ஒவ்வொரு மூலக் கோப்பையும் (source file) தனித்தனியாகப் பார்ப்பதன் மூலம் அந்த கோப்புகளை வெளியிட கம்பைலரை இது அனுமதிக்கிறது. இதன் விளைவாக, உங்கள் சார்பு வரைபடத்தின் (dependency graph) வழியாக ஒவ்வொன்றாக நகர்ந்து செல்வதற்குப் பதிலாக, ஆயிரக்கணக்கான கோப்புகளில் இணையாக (parallel) இயங்கக்கூடிய ஒரு பில்ட் செயல்முறை கிடைக்கிறது.

உண்மையான தடையின் காரணம்

தற்போது, அறிவிப்பு கோப்புகளை உருவாக்குவது ஒரு தொடர்ச்சியான (serial) செயல்பாடாகும். நீங்கள் --declaration என்பதை இயக்கி கம்பைலரைத் தொடங்கும்போது, ஒரு குறிப்பிட்ட மாட்யூல் (module) தொடும் ஒவ்வொரு வகையையும் (type) முழுமையாகப் புரிந்துகொள்ளும் வரை TypeScript ஒரு .d.ts கோப்பை வெளியிட முடியாது. utils.ts ஆனது types.ts-லிருந்து வகைகளைப் இறக்குமதி செய்கிறது என்றும், types.ts ஆனது api.ts-லிருந்து ஏதோ ஒன்றை எடுக்கிறது என்றும் இருந்தால், utils.ts எதை ஏற்றுமதி (export) செய்கிறது என்பதை விவரிப்பதற்கு முன்பாக கம்பைலர் அந்தச் சங்கிலித் தொடரைத் தீர்க்க வேண்டும்.

ஒரு பெரிய மோனோரெப்போவில் (monorepo), இந்தத் தொடர் விளைவு மிகக் கடுமையானது. உங்கள் இறக்குமதி வரைபடத்தின் (import graph) மூலத்திற்கு அருகில் உள்ள ஒரு தனி கோப்பு, நூற்றுக்கணக்கான கீழ்நிலை (downstream) கோப்புகளுக்கான அறிவிப்பு வெளியீட்டைத் தடுக்கக்கூடும். உங்கள் CPU-வில் எட்டு கோர்கள் (cores) இருக்கலாம், ஆனால் TypeScript ஒவ்வொரு இன்டர்ஃபேஸின் (interface) அமைப்பையும் பேக்கேஜ் எல்லைகளுக்கு அப்பால் சிரமத்துடன் மீண்டும் கட்டமைக்கும்போது, அவற்றில் ஏழு கோர்களும் பயன்படுத்தப்படாமல் காலியாக இருக்கும். கம்பைலர் தேவையான வேலையைச் செய்கிறது, ஆனால் வகை-சரிபார்ப்பு மற்றும் அறிவிப்பு வெளியீடு ஆகியவற்றிற்கு இடையிலான பிணைப்பு (coupling) காரணமாக, நீங்கள் பொதுவான மேற்பரப்பு வகைகளை (public surface types) மட்டும் சேமிக்க விரும்பினாலும், கோல்களுக்கு இடையிலான முழுமையான பகுப்பாய்விற்கான விலையை நீங்கள் செலுத்த வேண்டியுள்ளது.

IsolatedDeclarations விதிகளை எவ்வாறு மாற்றுகிறது

isolatedDeclarations அந்தப் பிணைப்பை உடைக்கிறது. இந்த ஃபிளாக் (flag) செயல்படுத்தப்படும்போது, கம்பைலர் வேறு எந்தக் கோப்பையும் கேட்காமல், ஒரு மூலக் கோப்பிற்கான .d.ts கோப்பை வெளியிட ஒப்புக்கொள்கிறது. இதைச் செய்ய ஒரு எளிய ஒப்பந்தத்தை இது கோருகிறது: ஏற்றுமதி செய்யப்படும் ஒவ்வொரு குறியீடும் (symbol), அது அறிவிக்கப்படும் இடத்திலேயே ஒரு தெளிவான, கண்ணுக்குத் தெரியும் வகை குறிப்பைக் (type annotation) கொண்டிருக்க வேண்டும்.

மூலக் கோப்பிலேயே முழுமையான வகை எழுதப்பட்டிருப்பதை கம்பைலரால் பார்க்க முடிந்தால், அது வகை ஊகத்தை (type inference) செய்ய வேண்டிய அவசியமில்லை. அது இறக்குமதிகளைத் தேடிச் செல்ல வேண்டிய அவசியமில்லை. மற்றொரு கோப்பில் உள்ள User என்ற அடையாளங்காட்டி (identifier) ஒரு இன்டர்ஃபேஸா, டைப் ஏலியஸா (type alias) அல்லது கிளாஸா (class) என்பதைத் தெரிந்துகொள்ள வேண்டிய அவசியமில்லை. நீங்கள் எழுதியதை அப்படியே அது வெளியிடுகிறது.

இதன் பொருள் கோப்பு A மற்றும் கோப்பு B ஆகியவை அவற்றின் அறிவிப்புகளை ஒரே நேரத்தில் உருவாக்க முடியும் என்பதாகும். ஒரு பில்ட் ஒருங்கிணைப்பாளர் (build orchestrator) ஒவ்வொரு கோப்பையும் ஒரு தனித் திரெட்டிற்கு (thread) வழங்க முடியும். முழுமையான வகை-சரிபார்ப்பாளர் இல்லாத காரணத்தால் முன்பு .d.ts உருவாக்கத்தைத் தவிர்த்த வேகமான டிரான்ஸ்பைலர்கள் (transpilers) கூட, இப்போது அறிவிப்பு கோப்புகளை உருவாக்க முடியும், ஏனெனில் இந்த வேலை முற்றிலும் தொடரியல் சார்ந்ததாக (syntactic) மாறுகிறது.

சவால்கள்: அதைத் தெளிவாக எழுதுங்கள்

வேகம் இலவசமாகக் கிடைப்பதில்லை. நீங்கள் ஏற்றுமதி செய்யும் எதற்கும் வகை ஊகத்தை (type inference) நம்பியிருப்பதை நிறுத்த வேண்டும். ஒவ்வொரு பொதுவான செயல்பாடு (function), கிளாஸ், மாறி (variable) மற்றும் மாறிலிக்கும் (constant) அதன் வகை தெளிவாகக் குறிப்பிடப்பட வேண்டும். ஒரு ரிட்டர்ன் ஸ்டேட்மென்ட்டை (return statement) பார்ப்பதன் மூலமோ அல்லது ஒரு ஜெனரிக் ஆர்குமென்ட்டை (generic argument) தீர்ப்பதன் மூலமோ TypeScript வகையைக் கணக்கிட வேண்டியிருந்தால், isolatedDeclarations பிழையைக் (error) காட்டும்.

நடைமுறையில் இது எப்படி இருக்கும் என்பதை இங்கே காணலாம். இந்த ஃபிளாக் இல்லாமல், நீங்கள் இவ்வாறு எழுதலாம்:

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

TypeScript ஆனது fetch, பின்னர் Promise.prototype.then, பின்னர் r.json()-ஐத் திருப்பித் தரும் அநாமதேய செயல்பாட்டை (anonymous function) ஆய்வு செய்வதன் மூலம் அதன் ரிட்டர்ன் வகையை ஊகிக்கிறது. ஒரு .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> என்பதைக் காண்கிறது. அது அறிவிப்பை வெளியிட்டுவிட்டு அடுத்த வேலையைச் செய்கிறது.

இந்த விதி பரவலாகப் பொருந்தும். ஏற்றுமதி செய்யப்பட்ட அரேக்கைகளுக்கு (arrays), அவற்றின் உறுப்புகளிலிருந்து ஊகிக்கப்படுவதைத் தவிர்த்து, தெளிவான வகைகள் தேவை. ஏற்றுமதி செய்யப்பட்ட ஆப்ஜெக்ட்களின் (objects) அமைப்பு பயனர்களுக்குத் தேவைப்பட்டால், அவற்றுக்குத் தெளிவான வகை குறிப்புகள் தேவை. ஜெனரிக் செயல்பாடுகளுக்கு (generic functions), அவற்றின் ரிட்டர்ன் வகைகள் மற்றும் கட்டுப்பாடுகள் (constraints) அறிவிக்கப்படும் இடத்திலேயே தெரிய வேண்டும். ஒரு சிக்கலான மேப்பட் டைப்பின் (mapped type) முடிவை, முழுமையாக எழுதப்பட்ட ஒரு பெயரிடப்பட்ட டைப் ஏலியஸ் (named type alias) இல்லாமல் நீங்கள் ஏற்றுமதி செய்ய முடியாது.

இதன் நன்மை என்னவென்றால், உங்கள் பொதுவான API தானாகவே ஆவணப்படுத்தப்பட்டதாக (self-documenting) மாறும். பயனர்களும் கம்பைலரும் இனி உங்கள் செயலாக்க விவரங்களிலிருந்து (implementation details) உங்கள் நோக்கத்தை தலைகீழ் பொறியியல் (reverse-engineer) செய்ய வேண்டியதில்லை. வகைகள் என்பது ஒரு திட்டமிட்ட ஒப்பந்தமாகும்.

நேரம் எங்கே மிச்சமாகிறது

ஒரு பெரிய கோப்புத் தொகுப்பில் (codebase), இதன் தாக்கம் உடனடியாகத் தெரியும். அறிவிப்பு வெளியீடு என்பது நீண்ட நேரத்தை எடுத்துக்கொள்ளும் காரணியாக (long pole in the tent) இல்லாமல் போவதால், நிமிடக்கணக்கில் நீடிக்கும் பில்ட் நேரங்கள் வினாடிகளாகக் குறையலாம். ஒவ்வொரு கோப்பும் தனித்தனியாக வெளியாவதால், இந்தச் செயல்முறை உங்கள் இறக்குமதி வரைபடத்தின் ஆழத்தைப் பொறுத்து அல்லாமல், உங்களிடம் உள்ள கோர்களின் (cores) எண்ணிக்கையைப் பொறுத்துச் செயல்படும்.

இது நீங்கள் பயன்படுத்தக்கூடிய கருவிகளையும் மாற்றுகிறது. esbuild மற்றும் swc போன்ற Transpilers ஏற்கனவே TypeScript-ஐ JavaScript-ஆக மாற்றுவதில் மின்னல் வேகத்தில் உள்ளன, ஆனால் பல குழுக்கள் .d.ts கோப்புகளை உருவாக்குவதற்காக மட்டும் இன்னும் தனியாக tsc-ஐ இயக்குகின்றன. isolatedDeclarations-ன் மூலம், அந்த வேகமான கருவிகளால் இரண்டு வேலைகளையும் கையாள முடியும். அறிவிப்புகளை (declarations) உருவாக்க அவை TypeScript-ன் முழு வகை அமைப்பையும் (type system) நகலெடுக்க வேண்டிய அவசியமில்லை; நீங்கள் வழங்கிய தெளிவான வகைகளை (explicit types) பகுப்பாய்வு செய்து நகலெடுக்கினால் போதுமானது. இது மாற்று கருவித் தொடர்களைக் (toolchains) கொண்டு முழுமையான TypeScript builds-களைச் செய்வது மிகவும் சாத்தியமானதாக மாற்றுகிறது.

விநியோகிக்கப்பட்ட (Distributed) மற்றும் படிப்படியான (incremental) builds-களும் எளிதாகின்றன. Continuous integration-ல், ஒரு remote cache அல்லது sharded build, ஒரு தொகுப்பிற்கான (package) முழுமையான transitive dependency graph-ஐ முதலில் பதிவிறக்கம் செய்யாமலேயே அறிவிப்புகளை வெளியிட முடியும். மூலக் குறியீட்டில் (source) வகைகள் தெளிவாக இருந்தால், build shard-க்குத் தேவையான அனைத்தும் அங்கேயே இருக்கும்.

எது மாறாமல் இருக்கும்

இந்தத் தடையானது exports-களுக்கு மட்டுமே பொருந்தும். ஒரு module-க்குள், அனைத்தும் இயல்பாகவே தொடரும். உள்ளூர் மாறிகள் (Local variables), தனிப்பட்ட வகுப்பு உறுப்பினர்கள் (private class members) மற்றும் export செய்யப்படாத உதவியாளர் செயல்பாடுகள் (unexported helper functions) இன்னும் முழுமையான வகை அனுமானத்தை (type inference) நம்பியிருக்கலாம். TypeScript ஒரு loop மாறி அல்லது 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);
}

Export செய்யப்பட்ட function signature-க்கு மட்டுமே annotation தேவைப்படுகிறது. உட்புறக் கட்டமைப்பு (internal machinery) நெகிழ்வாகவும் வெளிப்படையாகவும் இருக்கும். இது குறியீடு எழுதும் சுமையைத் தாங்கக்கூடியதாக வைக்கிறது. நீங்கள் எல்லா இடங்களிலும் முழுமையான explicit பாணிக்கு மாறவில்லை; ஒவ்வொரு module-ன் எல்லையிலும் ஒப்பந்தத்தை முறைப்படுத்துகிறீர்கள் (formalizing the contract) அவ்வளவுதான்.

இது உங்கள் codebase-க்கு சரியானதா?

isolatedDeclarations-ஐ ஏற்றுக்கொள்வது நீங்கள் நேரத்தைச் செலவிடும் முறையை மாற்றுகிறது. ஒரு export-ஐ எழுதும்போது நீங்கள் சில கூடுதல் தட்டச்சுக்களைச் செய்கிறீர்கள், அதற்குப் பதிலாக ஒவ்வொரு build-க்கும் நீங்கள் கூடுதல் நேரத்தைச் செலவிட வேண்டிய அவசியமில்லை. Library உருவாக்குநர்களுக்கு, இது பெரும்பாலும் எளிதான தேர்வாகும். Public APIs எப்படியும் annotated ஆக இருக்க வேண்டும். ஒரு மூடிய monorepo-க்குள் பணிபுரியும் application உருவாக்குநர்களுக்கு, ஆரம்பக்கட்டச் செலவு தேவையற்ற ஒன்றாகத் தோன்றலாம். ஆனால் உங்கள் குழு build நேரத்தை காபி இடைவேசைகளைக் கொண்டு அளவிடுகிறீர்கள் என்றால், இந்த மாற்றம் விரைவாகக் கவர்ச்சிகரமானதாக மாறும்.

இதை நீங்கள் படிப்படியாகப் பின்பற்றலாம். flag-ஐச் செயல்படுத்தி, compiler-ஐ இயக்கி, export செய்யப்பட்ட symbols-களில் அது காட்டும் பிழைகளைச் சரிசெய்யவும். எந்தப் பொதுவான (public-facing) வகைகள் மறைமுகமாக (implicit) உள்ளன என்பதைப் பிழைச் செய்திகள் துல்லியமாகத் telling. அவற்றைச் சரிசெய்யவும், உட்புறங்களை அப்படியே விடவும், உங்கள் declaration படிநிலை வேகமடைவதைக் காணவும்.

ஒன்றை நினைவில் கொள்ள வேண்டும்: இந்த flag TypeScript-ன் type checker-ஐ வேகமாக்காது. உங்கள் editor-ல் விரைவான பின்னூட்டத்தையோ அல்லது வேகமான tsc --noEmit இயக்கத்தையோ நீங்கள் விரும்பினால், உங்களுக்கு இன்னும் project references, கடுமையான file inclusion அல்லது பிற கட்டமைப்புத் திருத்தங்கள் தேவைப்படும். isolatedDeclarations குறிப்பாக .d.ts கோப்புகளை வெளியிடுவதை மட்டுமே இலக்காகக் கொண்டுள்ளது. இது ஒரு build optimization, type-checking optimization அல்ல.

உண்மையான பயன்

isolatedDeclarations உங்கள் பொதுவான வகைகளை (public types) முதன்மையான அங்கங்களாகக் கருதச் சொல்கிறது. அவற்றை compiler மூலம் கண்டறியச் செய்வதை நிறுத்திவிட்டு, நீங்களே அவற்றை எழுதுங்கள். நீங்கள் இதைச் செய்தவுடன், ஒவ்வொரு முறையும் declaration file-ஐ உருவாக்க compiler உங்கள் முழு dependency graph-ஐயும் தேட வேண்டிய அவசியமில்லை. இது இணையாக (in parallel) வெளியிடும், esbuild மற்றும் swc போன்ற கருவிகள் முழுமையான TypeScript workflows-களைக் கையாளும், மேலும் உங்கள் monorepo builds மெதுவாக நடப்பது நின்றுவிடும்.

இதன் செலவு build நேரத்திலிருந்து குறியீடு எழுதும் நேரத்திற்கு மாறுகிறது. வளர்ந்து வரும் பெரும்பாலான குழுக்களுக்கு, இது ஒரு பயனுள்ள மாற்றமாகும்.