𝗕𝗲𝘆𝗼𝗻𝗱 𝗠𝗶𝘀𝘀𝗶𝗻𝗴 𝗘𝘅𝗽𝗼𝗿𝘁𝘀: 𝗕𝘂𝗶𝗹𝗱𝗶𝗻𝗴 𝗮𝗻 𝗘𝗮𝗿𝗹𝘆 𝗚𝗮𝗿𝗯𝗮𝗴𝗲 𝗖𝗼𝗹𝗹𝗲𝗰𝘁𝗼𝗿

I tried to use a standard plugin to fix missing links in Webpack documentation. It failed.

Integrating a plugin into a large codebase is not a simple task. It became an architectural challenge. I had to manage Abstract Syntax Tree (AST) manipulation, memory use, and recursive loops.

The Problem with Standard Plugins

I ran three experiments to find a solution.

Experiment 1: The plugin recovered 135 types. However, it put them in an internal module. Our tool created a separate folder for them. We had to sort them manually. The documentation was also structurally wrong.

Experiment 2: I turned on a setting to hide external types. This worked for some things. It reduced the payload to 60 Webpack types. But TypeDoc ignored nested dependencies. It left many types hidden while they still used memory.

Experiment 3: I tried to map types back to their original modules. This caused a recursive loop. The plugin kept extracting nested interfaces until it created 630 types. The documentation was full of noise. It ruined the user experience.

The Solution: Early Garbage Collection

I needed the types in their correct modules without the extra noise. I stopped trying to fix the output and started fixing the process.

I used a hook called EVENT_RESOLVE_END. This let me intercept the AST right after resolution. I did this before TypeDoc assigned categories or used heavy memory.

My logic followed three steps:

The result: I saved 240 vital types. They are now visible and correctly routed. I avoided the recursive noise of the third experiment.

Lessons Learned

• Handle ASTs early. Removing unneeded nodes prevents memory waste later. • Use hooks instead of hacks. Understanding the compiler lifecycle allows for clean work. • Feedback improves architecture. Maintainer reviews helped me build a better system. • More data is not better. Good engineering means finding the balance between data and usability.

Missing Exports से परे: Webpack के TypeDoc AST के लिए एक अर्ली गार्बेज कलेक्टर बनाना

बड़े पैमाने के TypeScript कोडबेस के साथ काम करते समय, एक आम समस्या अनयूज्ड (unused) एक्सपोर्ट्स का जमा होना है। ये अनयूज्ड एक्सपोर्ट्स न केवल बंडल साइज को बढ़ाते हैं, बल्कि कोडबेस में भ्रम भी पैदा करते हैं। हालांकि tree shaking जैसे टूल्स इस समस्या को हल करने में मदद करते हैं, लेकिन वे अक्सर रनटाइम या बंडलिंग स्टेज पर काम करते हैं।

इस लेख में, हम यह जानेंगे कि कैसे हम Webpack के बिल्ड प्रोसेस के दौरान ही इन अनयूज्ड एक्सपोर्ट्स को पहचानने के लिए TypeDoc AST का उपयोग करके एक "अर्ली गार्बेज कलेक्टर" (early garbage collector) बना सकते हैं।

समस्या: अनयूज्ड एक्सपोर्ट्स का बोझ

जब हम TypeScript में मॉड्यूल लिखते हैं, तो हम अक्सर कई चीज़ें एक्सपोर्ट करते हैं:

export const usefulFunction = () => { ... };
export const unusedFunction = () => { ... }; // यह कभी इस्तेमाल नहीं होता
export type UsefulType = { ... };
export type UnusedType = { ... }; // यह भी बेकार है

भले ही tree shaking अंततः इन्हें हटा दे, लेकिन बिल्ड पाइपलाइन के शुरुआती चरणों में ये एक्सपोर्ट्स मौजूद रहते हैं, जिससे टाइप-चेकिंग और डॉक्यूमेंटेशन जनरेशन (जैसे TypeDoc) की प्रक्रिया धीमी हो जाती है।

TypeDoc AST का उपयोग क्यों करें?

TypeDoc केवल डॉक्यूमेंटेशन जनरेटर नहीं है; यह आपके कोड का एक समृद्ध (rich) एब्स्ट्रैक्ट सिंटैक्स ट्री (AST) प्रदान करता है। TypeDoc का AST हमें कोड के स्ट्रक्चर, सिम्बल्स (symbols), और उनके बीच के संबंधों को समझने में मदद करता है।

एक गार्बेज कलेक्टर बनाने के लिए, हमें निम्नलिखित की आवश्यकता है:

  1. सिम्बल ग्राफ (Symbol Graph): सभी एक्सपोर्ट्स और उनके बीच के संबंधों का एक मैप।
  2. रूट्स (Roots): वे एंट्री पॉइंट्स जहाँ से कोड का उपयोग शुरू होता है।
  3. रीचैबिलिटी एनालिसिस (Reachability Analysis): यह पता लगाना कि कौन से सिम्बल्स रूट्स से जुड़े हैं।

गार्बेज कलेक्टर का आर्किटेक्चर

हमारा गार्बेज कलेक्टर तीन मुख्य चरणों में काम करेगा: Mark, Trace, और Sweep

1. ग्राफ निर्माण (Building the Graph)

सबसे पहले, हमें TypeDoc के Project ऑब्जेक्ट का उपयोग करके सभी सिम्बल्स को पार्स करना होगा और एक डिपेंडेंसी ग्राफ बनाना होगा।

// काल्पनिक कोड उदाहरण
const graph = new SymbolGraph();
project.getEntries().forEach(entry => {
  graph.addNode(entry);
});

2. मार्क और ट्रेस (Mark and Trace)

एक बार ग्राफ तैयार हो जाने के बाद, हम "रूट्स" (जैसे कि मुख्य एंट्री फाइलें) से ट्रैवर्सल शुरू करेंगे। हम उन सभी सिम्बल्स को "मार्क" करेंगे जो इन रूट्स से किसी भी तरह से जुड़े हुए हैं।

function markReachable(node, visited) {
  if (visited.has(node)) return;
  visited.add(node);
  
  for (const neighbor of node.getReferences()) {
    markReachable(neighbor, visited);
  }
}

3. स्वीप (Sweep)

अंत में, हम उन सभी सिम्बल्स को हटा देंगे (sweep) जिन्हें "मार्क" नहीं किया गया था। ये वे सिम्बल्स हैं जो अनयूज्ड हैं।

चुनौतियाँ और समाधान

साइक्लिक डिपेंडेंसी (Cyclic Dependencies)

ग्राफ में साइक्लिक डिपेंडेंसी (A -> B -> A) एक बड़ी चुनौती हो सकती है। यदि हम सही ढंग से visited सेट का उपयोग नहीं करते हैं, तो हमारा ट्रैवर्सल अनंत लूप (infinite loop) में फंस सकता है।

टाइप-ओनली एक्सपोर्ट्स (Type-only Exports)

TypeScript में, कुछ एक्सपोर्ट्स केवल टाइप-लेवल पर होते हैं। हमें यह सुनिश्चित करना होगा कि हमारा गार्बेज कलेक्टर केवल रनटाइम कोड ही नहीं, बल्कि टाइप डेफिनिशन को भी सही ढंग से हैंडल करे।

निष्कर्ष

एक अर्ली गार्बेज कलेक्टर बनाकर, हम बिल्ड पाइपलाइन को अधिक कुशल बना सकते हैं। TypeDoc के AST का उपयोग करके, हम न केवल अनयूज्ड कोड को हटा सकते हैं, बल्कि टाइप-चेकिंग और डॉक्यूमेंटेशन की प्रक्रिया को भी तेज कर सकते हैं।

यह दृष्टिकोण बड़े पैमाने के एंटरप्राइज एप्लिकेशन के लिए अत्यंत उपयोगी है जहाँ कोडबेस लगातार बढ़ता रहता है।