惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

U
Unit 42
罗磊的独立博客
博客园 - 聂微东
T
The Blog of Author Tim Ferriss
博客园 - 司徒正美
Stack Overflow Blog
Stack Overflow Blog
F
Fortinet All Blogs
A
About on SuperTechFans
腾讯CDC
Apple Machine Learning Research
Apple Machine Learning Research
B
Blog RSS Feed
IT之家
IT之家
V
Visual Studio Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
宝玉的分享
宝玉的分享
C
Check Point Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Vercel News
Vercel News
爱范儿
爱范儿
Microsoft Security Blog
Microsoft Security Blog
月光博客
月光博客
T
Tailwind CSS Blog
The Cloudflare Blog
Hugging Face - Blog
Hugging Face - Blog

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
Beyond Missing Exports: Building an Early Garbage Collect...
Mohamed Shams El-Deen · 2026-06-18 · via DEV Community

Introduction
Great documentation relies on an unbroken chain of references. As I'm responsible for the API Markdown Generation Phase, one of my tasks was to import internal types and interfaces that were missing from the generated documentation because they weren't explicitly exported.

The initial thought was simple: drop in typedoc-plugin-missing-exports and let it resolve the missing links. However, integrating a plugin into a massive codebase is rarely a plug-and-play operation. It turned into an architectural challenge involving Abstract Syntax Tree (AST) manipulation, memory optimization, and avoiding recursive explosions.

The Complexity of Resolution: 3 Experiments
To understand how TypeDoc handled these missing exports, I isolated the plugin's behavior through three distinct experiments:

Experiment 1: The Shallow Pass

Running the plugin with its default configuration recovered ~135 types. The downside? It isolated them in an <internal> module. Our documentation tool, nodejs/doc-kit, created markdown files for them and dumped them into an <internal> folder. We still needed to categorize them manually. Furthermore, TypeDoc didn’t traverse them deeply, leaving the documentation structurally inaccurate. It also included generic Node/JavaScript environment types, which we don't want since we aren't building Node/JS docs.

Experiment 2: Filtering the Noise (The Illusion of 60 Types)

By enabling excludeExternals: true, It stripped out the generic Node/JavaScript environment types. On the surface, this seemed successful, reducing the rendered payload in the docs to ~60 Webpack-specific types.
However, this was an illusion. In memory, the plugin was still extracting ~600 types and trapping them in the <internal> namespace. TypeDoc only rendered 60 of them because of its rendering and visibility rules, ignoring the deeper and nested dependencies. The rest were just eating up memory.

Experiment 3: The Recursive Explosion

To fix the shallow pass and map the types back to their original subsystems, I used placeInternalsInOwningModule: true. This broke the types out of the <internal>, but resulted in a recursive chain. The plugin began extracting nested internal interfaces, iterating continuously until it rendered ~630 inline types. The output was completely flooded with noise, making the developer experience terrible.

The Code Review: Rethinking the Architecture
I initially submitted a Pull Request based on Experiment 2, accepting the <internal> folder as necessary to avoid the recursive explosion of Experiment 3. But during the code review, the maintainers pointed out a fundamental flaw: keeping types in an <internal> module broke the logical grouping of Webpack's subsystems.

We needed the types in their respective modules, but we couldn't afford the ~600 noise types. We needed a custom intervention.

The Solution: Early Garbage Collection via AST Hooks
Instead of allowing TypeDoc to build an enormous AST and then trying to filter it during the routing phase (which wastes memory and CPU cycles), I implemented what I call "Early Garbage Collection" (similar to the V8 engine's Garbage Collector 😅).

By hooking directly into Converter.EVENT_RESOLVE_END, I could intercept the AST right after the initial resolution but before TypeDoc began assigning categories, building URLs, or allocating significant memory for the final output.

The logic was clean and focused on Separation of Concerns:

  1. Locate the <internal> module in the AST (which secretly held all ~600 raw types).
  2. Iterate through its child nodes.
  3. Evaluate each node using a custom categoryForReflection utility.
  4. Destroy the noise: Use project.removeReflection(child) to immediately sever the references of over 300+ unneeded nodes, allowing Node.js to garbage collect them and free up memory.
  5. Lift the essentials: Merge the remaining ~300 crucial types directly into the root scope using project.mergeReflections.

By doing this, I rescued ~240 vital types that were previously hidden in Experiment 2's shallow pass, bringing the total of correctly routed, fully visible types to ~300 all without the recursive noise of Experiment 3!

The Implementation:

app.converter.on(Converter.EVENT_RESOLVE_END, context => {
  const project = context.project;
  const internalModule = project.children?.find(c => c.name === '<internal>');

  if (internalModule) {
    const importantTypes = [];

    internalModule.children.forEach(child => {
      // Evaluate before routing to prevent memory waste
      if (categoryForReflection(child)) {
        importantTypes.push(child);
      } else {
        // Early Garbage Collection
        project.removeReflection(child);
      }
    });

    internalModule.children = importantTypes;
    project.mergeReflections(internalModule, project); // Lift to root scope
  }
});

What I Learned
Stepping back from the code, here are the core engineering lessons from this PR:

  • Handle ASTs Early: "Garbage Collection" isn't just a V8 background task. Actively dropping unneeded AST nodes prevents massive downstream memory overhead.
  • Hooks over Hacks: Understanding compiler lifecycles (like EVENT_RESOLVE_END) allows for clean interventions rather than messy downstream workarounds.
  • Reviews Shape Architecture: Maintainer feedback shifted my focus from simply "closing the issue" to building a structurally sound solution that fits the system.
  • Completeness ≠ Usability: Extracting 600+ types is just noise. Good engineering is finding the sweet spot between capacity and actual developer experience.

Conclusion
What started as a simple plugin integration evolved into a lesson on compiler hooks and AST lifecycle management. By shifting the filtering logic earlier in the execution pipeline, we prevented memory overhead, exposed the true required types, avoided a recursive cascade of useless noise, and delivered clean, deeply-linked documentation for the Webpack community.