How the help() Function Parses JSDoc to Provide Runtime Documentation in ego-lite
help() deserializes pre-computed JSDoc data that was extracted at build time—rather than parsing source files at runtime—to deliver on-demand documentation for ego-lite's helper functions.
The help() function in ego-lite (a lightweight browser automation framework developed by citrolabs) gives agents instant access to formatted documentation for any public helper. This article breaks down exactly how JSDoc comments travel from source code to runtime, following the complete pipeline implemented in citrolabs/ego-lite.
Build-Time JSDoc Extraction
The heavy lifting happens before your code ever runs. The build script scripts/extract-help-docs.mjs parses the bundled JavaScript output and transforms JSDoc blocks into structured JSON.
AST Parsing with Acorn
The extraction script uses Acorn to walk the TypeScript-compiled bundle:
// scripts/extract-help-docs.mjs
import { parse } from 'acorn';
const ast = parse(bundleCode, {
ecmaVersion: 'latest',
sourceType: 'module',
onComment: (isBlock, text, start, end) => {
// Collect all block comments for JSDoc processing
}
});
Each function declaration is matched with its preceding block comment. The parseJSDoc function then splits these comments into structured fields: description, @param entries, @returns information, and modifiers for optional, rest, or default parameters.
Output Structure
Every extracted helper becomes a plain object with this shape:
interface HelperDoc {
name: string;
signature: string;
description: string;
params: Array<{
name: string;
type: string;
description: string;
optional?: boolean;
rest?: boolean;
defaultValue?: string;
}>;
returns?: { type: string; description: string };
async?: boolean;
}
Embedding Documentation into the Runtime
Once extracted, the documentation must survive the bundling process. This happens in scripts/build.mjs through a two-step serialization technique.
Double Stringification Technique
The embedHelpDocs function serializes the docs array twice:
// scripts/build.mjs
const docs = extractHelpDocs(bundlePath);
const embeddedString = JSON.stringify(JSON.stringify(docs));
This double-wrapped JSON is injected into src/help-runtime.ts by replacing the placeholder __EGO_EMBEDDED_HELP_DOCS__. The result is a static string literal baked directly into the distributed code—no filesystem reads, no network requests at runtime.
Runtime Deserialization and Caching
The runtime side in src/help-runtime.ts is intentionally minimal. It only needs to parse the embedded string and serve the results.
Safe Parsing with Fallback
// src/help-runtime.ts
function parseEmbeddedDocs(): HelperDoc[] {
try {
const raw = '__EGO_EMBEDDED_HELP_DOCS__'; // replaced at build
return JSON.parse(JSON.parse(raw));
} catch {
// Placeholder not replaced (e.g., raw TS import)
return [];
}
}
Lazy-Loaded Map Cache
getDocsMap() builds a Map<string, HelperDoc> only on first access, then caches it for subsequent calls:
let docsMap: Map<string, HelperDoc> | undefined;
export function getDocsMap(): Map<string, HelperDoc> {
if (!docsMap) {
docsMap = new Map(parseEmbeddedDocs().map(d => [d.name, d]));
}
return docsMap;
}
The help() API: Three Call Patterns
The exported help() function supports flexible usage patterns depending on how many helper names you provide.
List All Available Helpers
Without arguments, help() returns every documented helper that exists in your helpers object:
import { help, formatHelp } from "ego-browser";
// List all available helpers
const allDocs = help(helpers) as HelperDoc[];
console.log(allDocs.map(d => d.name).join(", "));
// Output: click, type, nav, scroll, wait, ...
Single Helper Lookup
Pass one name to get its full documentation object:
const navDoc = help(helpers, "nav") as HelperDoc;
console.log(formatHelp(navDoc));
Example output:
Navigate to a URL.
@param url string — Destination URL
@param timeout? number — Max wait time (default: 30)
@returns void
nav(url, timeout?) → void
Batch Lookup
Multiple names return an array, with unknown helpers replaced by placeholder objects:
const docs = help(helpers, "click", "type", "unknown") as HelperDoc[];
docs.forEach(d => console.log(formatHelp(d)));
// "unknown" outputs: "Unknown helper: unknown"
Formatting Human-Readable Output
The formatHelp() function assembles the final display string by concatenating:
- Description (first paragraph of JSDoc)
- @param lines with optional/rest/default annotations
- @returns line (if present)
- Function signature generated from parsed metadata
This formatting happens at runtime—cheap string operations on already-parsed data.
Key Files in the Pipeline
| File | Purpose |
|---|---|
scripts/extract-help-docs.mjs |
Parses bundled JS with Acorn, extracts JSDoc, builds HelperDoc objects |
scripts/build.mjs |
Calls embedHelpDocs, double-serializes docs, injects into help-runtime.ts |
src/help-runtime.ts |
Defines parseEmbeddedDocs, getDocsMap, help(), and formatHelp() |
According to the citrolabs/ego-lite source code, this three-file architecture cleanly separates build-time computation from runtime serving, keeping the browser bundle small and help() calls instantaneous.
Summary
- No runtime parsing:
help()never touches filesystem or parses ASTs at runtime - Build-time extraction: Acorn walks the bundled JS in
extract-help-docs.mjsto harvest JSDoc - Double serialization:
JSON.stringify(JSON.stringify(docs))safely embeds data as a string literal - Lazy caching:
getDocsMap()constructs and caches the lookup map on first use - Three API modes: zero args (all), one arg (single), multiple args (batch)
- Human formatting:
formatHelp()renders structured docs into readable output
Frequently Asked Questions
Does help() parse JSDoc comments every time it's called?
No. All JSDoc parsing happens at build time in scripts/extract-help-docs.mjs. At runtime, help() simply calls JSON.parse on the pre-serialized data and looks up entries from a cached Map.
What happens if I call help() in a development environment before building?
The placeholder __EGO_EMBEDDED_HELP_DOCS__ remains unmodified, causing parseEmbeddedDocs() to catch the parse error and return an empty array. No documentation will be available until after a proper build runs.
How does the system handle @param tags with default values or optional markers?
The parseJSDoc function recognizes ? for optional parameters, ... for rest parameters, and = value syntax for defaults. These are stored as optional, rest, and defaultValue properties on each param object, which formatHelp() uses to generate signatures like fnName(required, optional?, ...args).
Can I extend help() to document my own custom helpers?
Yes, provided your helpers follow JSDoc conventions and your build process runs the same extraction pipeline. The extractHelpDocs function processes any function declaration with a preceding block comment, regardless of whether it's a core ego-lite helper or your own extension.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →