9.1 KiB
API Docs Builder
Generates interactive API documentation from TypeScript sources for Video.js 10 components.
Architecture
TypeScript Sources (core/html/react packages)
↓
api-docs-builder (typescript-api-extractor)
↓
JSON files (site/src/content/generated-api-reference/)
↓
<ApiReference /> Astro component
↓
Interactive tables in MDX pages
How It Works
1. Source Discovery
The builder scans packages/core/src/core/ui/ for component directories. For each component (e.g., play-button), it looks for:
- Core file:
play-button-core.ts→ ExtractsPlayButtonProps,PlayButtonState, anddefaultProps - Data attrs file:
play-button-data-attrs.ts→ Extracts data attributes with JSDoc descriptions - HTML element file:
packages/html/src/ui/play-button/play-button-element.ts→ ExtractstagName - Parts index:
packages/react/src/ui/play-button/index.parts.ts→ Detects multi-part components
2. TypeScript Extraction
Uses typescript-api-extractor to parse TypeScript AST and extract:
- Interface properties with types and JSDoc descriptions
- Default values from
static defaultProps = { ... } - Data attributes from
const PlayButtonDataAttrs = { ... } as const - Lit element tag names from
static tagName = 'media-play-button'
3. JSON Output
Generates one JSON file per component at site/src/content/generated-api-reference/{kebab-case-name}.json:
{
"name": "PlayButton",
"props": {
"label": {
"type": "string | ((state: PlayButtonState) => string)",
"description": "Custom label for the button.",
"default": "''"
}
},
"state": {
"paused": {
"type": "boolean",
"description": "Whether playback is paused."
}
},
"dataAttributes": {
"data-paused": {
"description": "Present when the media is paused."
}
},
"platforms": {
"html": {
"tagName": "media-play-button"
}
}
}
4. Astro Components
The <ApiReference /> component:
- Loads the JSON via Astro Content Collections (
getEntry('apiReference', 'play-button')) - For single-part components: renders Props, State, and Data Attributes sections with h3 headings
- For multi-part components: renders each part with a framework-aware h3 heading, part description, and h4 sub-sections
- Renders interactive tables with expandable prop details
Usage
In MDX
Use the unified <ApiReference /> component for both single-part and multi-part components:
import ApiReference from "@/components/docs/api-reference/ApiReference.astro";
<ApiReference component="PlayButton" />
For multi-part components, the same pattern applies — the component automatically renders part headings, descriptions, and sub-sections:
import ApiReference from "@/components/docs/api-reference/ApiReference.astro";
<ApiReference component="Time" />
Part descriptions are extracted from JSDoc on the React component exports (e.g., packages/react/src/ui/time/time-value.tsx).
Building
The builder runs automatically before dev/build via npm scripts:
# Run manually
pnpm api-docs
# Runs automatically on:
pnpm dev # via predev hook
pnpm build # via prebuild hook
Multi-Part Components
Some components are composed of multiple parts (e.g., Time has Value, Group, Separator). The builder auto-discovers these via convention.
Detection
Trigger: Presence of packages/react/src/ui/{name}/index.parts.ts.
Single-part components (PlayButton, MuteButton) don't have this file and are unaffected.
Discovery Algorithm
- Part name discovery: Named (non-type-only) exports are parsed from
index.parts.ts. Each value export becomes a part. - Kebab segment derivation: Source path
./time-group→ strip./time-prefix →group. - HTML element matching: Each part's kebab segment is used to find
{name}-{kebab}-element.tsin the HTML directory (e.g.,time-group-element.ts). - Primary part identification: The part with NO
{name}-{part}-element.tsmatch, whose element is just{name}-element.ts, is the primary part. - Shared resource attribution:
{name}-core.tsand{name}-data-attrs.tsare attributed to the primary part only.
Naming Conventions Required
- Core interfaces must be
{Name}Propsand{Name}State(not{Name}CorePropsetc.) - Part exports in
index.parts.tsmust be value exports (not type-only) - HTML element files must follow
{name}-{part}-element.tsnaming - Element classes must be
{Name}{Part}Element(e.g.,TimeGroupElement)
JSON Output
Multi-part components have empty top-level props/state/dataAttributes. All data lives in the parts record:
{
"name": "Time",
"props": {},
"state": {},
"dataAttributes": {},
"platforms": {},
"parts": {
"value": { "name": "Value", "description": "Displays a formatted time value.", "props": { ... }, ... },
"group": { "name": "Group", "description": "Container for composed time displays.", "props": {}, ... },
"separator": { "name": "Separator", "description": "Divider between time values.", "props": {}, ... }
}
}
Troubleshooting
- Part not appearing in JSON? Check
index.parts.tsexports the part as a value export (not type-only). - Props/state empty for primary part? Verify core interfaces are named
{Name}Props/{Name}State. - HTML tag name missing? Verify element file follows
{name}-{part}-element.tsnaming and hasstatic tagName. - No primary part warning? Ensure the primary part's element file is just
{name}-element.ts(not{name}-{part}-element.ts). - Part description missing? Add a JSDoc comment to the React component export (e.g.,
/** Displays a formatted time value. */aboveexport const Value).
File Structure
site/scripts/api-docs-builder/
├── README.md # This file
└── src/
├── index.ts # Main entry point, orchestrates handlers
├── types.ts # TypeScript interfaces
├── formatter.ts # Type formatting utilities
├── utils.ts # Utility functions (naming helpers)
├── core-handler.ts # Extracts Props/State from core packages
├── data-attrs-handler.ts # Extracts data attributes
├── html-handler.ts # Extracts Lit element info
├── parts-handler.ts # Parses index.parts.ts for multi-part components
└── tests/
├── test-utils.ts
├── core-handler.test.ts
├── data-attrs-handler.test.ts
├── formatter.test.ts
├── html-handler.test.ts
├── parts-handler.test.ts
└── utils.test.ts
site/src/
├── content/generated-api-reference/ # Generated JSON files (gitignored)
│ ├── play-button.json
│ ├── mute-button.json
│ └── time.json
└── components/docs/api-reference/
├── ApiReference.astro # Unified component — renders full API reference from JSON
├── ApiPropsTable.astro # Props table
├── ApiStateTable.astro # State interface table
├── ApiDataAttrsTable.astro # Data attributes table
└── PropRow.astro # Expandable prop row
Adding a New Component
Single-Part Component
- Create the component in
packages/core/src/core/ui/{name}/ - Export
{Name}Propsinterface and{Name}Stateinterface - Optionally create
{name}-data-attrs.tswith data attribute definitions - Create the HTML element in
packages/html/src/ui/{name}/withstatic tagName - Run
pnpm api-docsto generate JSON - Use
<ApiReference component="{Name}" />in MDX
Multi-Part Component
- Follow the single-part steps above for the primary part's core/data-attrs/element files
- Create
packages/react/src/ui/{name}/index.parts.tsexporting each part - Add JSDoc descriptions to each React component export for part descriptions
- Create HTML element files for each non-primary part at
packages/html/src/ui/{name}/{name}-{part}-element.ts - Run
pnpm api-docsto generate JSON - Use
<ApiReference component="{Name}" />in MDX
Acknowledgements
This builder's architecture and approach were inspired by Base UI's
api-docs-builder, maintained by MUI. Base UI is licensed under the
MIT License (Copyright 2019 Material-UI SAS).
Thank you to the MUI team for the excellent reference implementation.
Key differences from Base UI's builder
- Multi-platform: One JSON per component containing all platform variants (React/HTML)
- Core-first: Props come from core package, not platform-specific components
- Data attributes: Extracted from dedicated
*-data-attrs.tsfiles - HTML elements: Extracts Lit element
static tagName - No prettier: Uses biome for formatting (removed prettier dependency)
Dependencies
typescript-api-extractor: AST parsing for TypeScript typeses-toolkit: Utility functions (kebabCase, etc.)tsx: TypeScript execution
All dependencies are in site/package.json devDependencies.