Skip to content

feat(typedoc): add a TypeDoc plugin writing doc-kit Markdown - #1125

Open
ovflowd wants to merge 16 commits into
mainfrom
feat/typedoc
Open

ovflowd wants to merge 16 commits into
mainfrom
feat/typedoc

Conversation

@ovflowd

@ovflowd ovflowd commented Oct 1, 2026 •

Copy link
Copy Markdown
Member

Description

This PR adds @doc-kit/typedoc, a TypeDoc plugin that writes the API reference of a TypeScript project as doc-kit Markdown, so TypeScript projects get doc-kit's signatures, typed lists, stability indices and source links straight from their sources.

It registers a doc-kit output (outputs: [{ name: 'doc-kit', path: 'docs/api' }]) that writes:

  • One page per export, with signatures in doc-kit's syntax (`build(options[, extra])` heading + typed list), {Type} annotations, > Stability: from @deprecated / @experimental, source_link, and @example / @see / @throws. Custom block tags render as **Tag:** content.
  • type-map.json for doc-kit's typeMap, and pages.json (every page with its kind, URL and @category) for sites to build their navigation from.

A few docKit* options cover what bigger APIs need. The main one is docKitMemberPages: members of chosen types (e.g. a bundler's options) get a page of their own, and the type's page, the types extending it and the parameters typed with it list them as linked one-liners instead of repeating their docs. The others handle event emitters (Event: entries from an event-map type), receivers (this.resolve() instead of pluginContext.resolve()), signatures taken from another type, import paths and legacy member anchors. They're all documented in the package's README.

Some notes:

  • It only writes files whose contents changed, so typedoc --watch works nicely with node --watch on doc-kit.
  • It checks TypeDoc's discriminants (reflection.variant, type.type) instead of instanceof, so it still works when the plugin resolves a different TypeDoc copy than the host (linked packages, strict package managers).

This is part of a proof of concept of migrating rolldown.rs from VitePress to doc-kit. There it replaces ~1,100 lines of Rolldown-specific TypeDoc → doc-kit conversion with this plugin and a small typedoc.config.mjs.

Validation

  • Unit tests for the Markdown helpers, and an end-to-end test running TypeDoc with the plugin over a fixture (functions, an options interface with member pages, an extending interface, a class with events, @deprecated, @experimental, @default, a custom tag) and snapshotting every generated file.
  • Generating Rolldown's whole API reference (230 pages) produces the same pages as the custom script it replaces, apart from two deliberate changes: custom tags render as plain text (**Kind:** async parallel), and member lists are titled "Properties".

Related Issues

Part of the Rolldown docs migration PoC: rolldown/rolldown#11072. It's the base of a stack: #1125 (TypeDoc plugin) → #1126 (Graphviz diagrams) → #1127 (llms-full), each targeting the one below, with this one targeting main. The changes don't depend on each other, the stack just keeps them reviewable one at a time while the Rolldown PoC builds from the top branch.

Check List

  • I have read the Contributing Guidelines and made commit messages that follow the guideline.
  • I have run node --run test and all tests passed.
  • I have check code formatting with node --run format:check & node --run lint.
  • I've covered new added functionality with unit tests if necessary.

@vercel

vercel Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
api-docs-tooling Ready Ready Preview Oct 3, 2026 2:23pm UTC

Request Review

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

🚀 Deploying Preview to Cloudflare 🚀

Preview Deployments by commit

Status Deployment URL Commit Updated (UTC) See this deployment's details
  • Build: Failed ❌

View logs ↗
ea26c1e 2026-10-03T14:23:59.659Z View logs ↗
  • Build: Failed ❌

View logs ↗
01f985c 2026-10-03T14:14:18.055Z View logs ↗
  • Build: Failed ❌

View logs ↗
e9479b6 2026-10-03T13:25:59.774Z View logs ↗
  • Build: Failed ❌

View logs ↗
f0abf2c 2026-10-03T13:14:31.753Z View logs ↗
  • Build: Failed ❌

View logs ↗
03f0300 2026-10-01T14:25:18.190Z View logs ↗
  • Build: Failed ❌

View logs ↗
933f8f5 2026-10-01T13:26:51.469Z View logs ↗

@codecov

codecov Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.96200% with 85 lines in your changes missing coverage. Please review.
✅ Project coverage is 92.92%. Comparing base (acc1e4a) to head (ea26c1e).

Files with missing lines Patch % Lines
packages/typedoc/src/render/members.mjs 93.36% 15 Missing and 1 partial ⚠️
packages/typedoc/src/utils/reflections.mjs 95.81% 7 Missing and 4 partials ⚠️
packages/typedoc/src/render/lists.mjs 95.28% 8 Missing and 1 partial ⚠️
packages/typedoc/src/render/types.mjs 89.88% 7 Missing and 2 partials ⚠️
packages/typedoc/src/render/comments.mjs 89.61% 5 Missing and 3 partials ⚠️
packages/typedoc/src/render/entries.mjs 97.28% 8 Missing ⚠️
packages/typedoc/src/render/pages.mjs 96.49% 7 Missing and 1 partial ⚠️
packages/typedoc/src/utils/router.mjs 95.91% 7 Missing and 1 partial ⚠️
packages/typedoc/src/generate.mjs 94.50% 5 Missing ⚠️
packages/typedoc/src/index.mjs 95.94% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1125      +/-   ##
==========================================
+ Coverage   92.64%   92.92%   +0.28%     
==========================================
  Files         244      260      +16     
  Lines       23114    25219    +2105     
  Branches     2263     2573     +310     
==========================================
+ Hits        21413    23434    +2021     
- Misses       1692     1763      +71     
- Partials        9       22      +13     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions

github-actions Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

api-links Generator

Output: 1 file differs

apilinks.json
Expected values to be strictly deep-equal:
+ actual - expected
... Skipped lines

  {
    'Agent.defaultMaxSockets': 'https://github.com/{repository}/blob/HEAD/lib/_http_agent.js#L300',
    'Buffer.alloc': 'https://github.com/{repository}/blob/HEAD/lib/buffer.js#L454',
    'Buffer.allocUnsafe': 'https://github.com/{repository}/blob/HEAD/lib/buffer.js#L473',
    'Buffer.allocUnsafeSlow': 'https://github.com/{repository}/blob/HEAD/lib/buffer.js#L496',
...
    'agent.addRequest': 'https://github.com/{repository}/blob/HEAD/lib/_http_agent.js#L365',
+   'agent.createConnection': 'https://github.com/{repository}/blob/HEAD/lib/https.js#L354',
-   'agent.createConnection': 'https://github.com/{repository}/blob/HEAD/lib/_http_agent.js#L304',
    'agent.createSocket': 'https://github.com/{repository}/blob/HEAD/lib/_http_agent.js#L446',
    'agent.destroy': 'https://github.com/{repository}/blob/HEAD/lib/_http_agent.js#L679',
+   'agent.getName': 'https://github.com/{repository}/blob/HEAD/lib/https.js#L537',
+   'agent.keepSocketAlive': 'https://github.com/{repository}/blob/HEAD/lib/https.js#L506',
-   'agent.getName': 'https://github.com/{repository}/blob/HEAD/lib/_http_agent.js#L334',
-   'agent.keepSocketAlive': 'https://github.com/{repository}/blob/HEAD/lib/_http_agent.js#L635',
    'agent.removeSocket': 'https://github.com/{repository}/blob/HEAD/lib/_http_agent.js#L574',
    'agent.reuseSocket': 'https://github.com/{repository}/blob/HEAD/lib/_http_agent.js#L671',
    'assert.assert': 'https://github.com/{repository}/blob/HEAD/lib/assert.js#L185',
    'asyncResource.asyncId': 'https://github.com/{repository}/blob/HEAD/lib/async_hooks.js#L243',
    'asyncResource.bind': 'https://github.com/{repository}/blob/HEAD/lib/async_hooks.js#L275',
...
    'server.address': 'https://github.com/{repository}/blob/HEAD/lib/net.js#L2776',
+   'server.close': 'https://github.com/{repository}/blob/HEAD/lib/net.js#L2909',
+   'server.closeAllConnections': 'https://github.com/{repository}/blob/HEAD/lib/https.js#L125',
+   'server.closeIdleConnections': 'https://github.com/{repository}/blob/HEAD/lib/https.js#L127',
-   'server.close': 'https://github.com/{repository}/blob/HEAD/lib/_http_server.js#L697',
-   'server.closeAllConnections': 'https://github.com/{repository}/blob/HEAD/lib/_http_server.js#L707',
-   'server.closeIdleConnections': 'https://github.com/{repository}/blob/HEAD/lib/_http_server.js#L719',
    'server.getConnections': 'https://github.com/{repository}/blob/HEAD/lib/net.js#L2871',
    'server.listen': 'https://github.com/{repository}/blob/HEAD/lib/net.js#L2566',
    'server.ref': 'https://github.com/{repository}/blob/HEAD/lib/net.js#L3023',
+   'server.setTimeout': 'https://github.com/{repository}/blob/HEAD/lib/https.js#L129',
-   'server.setTimeout': 'https://github.com/{repository}/blob/HEAD/lib/_http_server.js#L735',
    'server.unref': 'https://github.com/{repository}/blob/HEAD/lib/net.js#L3032',
+   'server[SymbolAsyncDispose]': 'https://github.com/{repository}/blob/HEAD/lib/net.js#L2950',
-   'server[SymbolAsyncDispose]': 'https://github.com/{repository}/blob/HEAD/lib/_http_server.js#L703',
    'server[SymbolAsyncIterator]': 'https://github.com/{repository}/blob/HEAD/lib/net.js#L2957',
    'server[kDeserialize]': 'https://github.com/{repository}/blob/HEAD/lib/net.js#L2487',
    'server[kTransferList]': 'https://github.com/{repository}/blob/HEAD/lib/net.js#L2459',
    'server[kTransfer]': 'https://github.com/{repository}/blob/HEAD/lib/net.js#L2464',
+   'server[undefined]': 'https://github.com/{repository}/blob/HEAD/lib/net.js#L2987',
-   'server[undefined]': 'https://github.com/{repository}/blob/HEAD/lib/_http_server.js#L742',
    'serverresponse._finish': 'https://github.com/{repository}/blob/HEAD/lib/_http_server.js#L262',
    'serverresponse._implicitHeader': 'https://github.com/{repository}/blob/HEAD/lib/_http_server.js#L419',
    'serverresponse.assignSocket': 'https://github.com/{repository}/blob/HEAD/lib/_http_server.js#L312',
    'serverresponse.detachSocket': 'https://github.com/{repository}/blob/HEAD/lib/_http_server.js#L323',
    'serverresponse.statusCode': 'https://github.com/{repository}/blob/HEAD/lib/_http_server.js#L285',

Performance estimate (single CI run)

  • Generation time: 29.6% slower (1.35 s → 1.75 s)
  • Peak memory: 1.2% lower (426.75 MB → 421.55 MB)

json Generator

Performance estimate (single CI run)

  • Generation time: 38.5% slower (6.78 s → 9.39 s)
  • Peak memory: 9.8% lower (1.74 GB → 1.57 GB)

legacy-html Generator

Performance estimate (single CI run)

  • Generation time: 8.2% faster (43.60 s → 40.02 s)
  • Peak memory: 2.8% higher (2.29 GB → 2.35 GB)

legacy-json Generator

Performance estimate (single CI run)

  • Generation time: 0.7% slower (8.62 s → 8.68 s)
  • Peak memory: 6.7% lower (1.64 GB → 1.53 GB)

llms-txt Generator

Performance estimate (single CI run)

  • Generation time: 5.6% slower (7.13 s → 7.53 s)
  • Peak memory: 19.2% higher (1.53 GB → 1.82 GB)

orama-db Generator

Output size: 1 file changed · net -153.00 B

File size details
File Main PR Change
orama-db.json 9.55 MB 9.55 MB -153.00 B (-0.0%)

Performance estimate (single CI run)

  • Generation time: 0.6% slower (8.10 s → 8.15 s)
  • Peak memory: 13.8% higher (1.53 GB → 1.74 GB)

web Generator

Performance estimate (single CI run)

  • Generation time: 4.3% slower (60.75 s → 63.34 s)
  • Peak memory: 12.1% higher (3.27 GB → 3.66 GB)

@avivkeller avivkeller left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a first pass, and I didn't look at all the files.

From trying to change webpack-doc-kit to use this library, note that:

  • collectDeclarations only reads the direct children of the project, or of its entry-point modules. webpack exports 28 nested namespaces (optimize, container, javascript, cli, util, sources, etc).
  • Enums and namespaces are not in PAGE_KINDS (Intentional?)
  • Accessors render as {unknown} type
  • Call signatures of interfaces are dropped.
  • signaturesOf ignores named function types.
  • StatsObject (webpack type) gives an invalid toutput ype
  • docKitMemberAnchors shouldn't exist, IMO, it should always be true, since things like {@link Compiler.run} break without it
  • Type-map URLs assume doc-kit's input root is the site root.
  • Source links are relative to the host's Git root. IMO it should be relative to a provided value

As an aside, export= isn't properly handled, but I'd argue that's an issue for a project using the poor syntax over a issue here.

Comment thread packages/typedoc/package.json Outdated
Comment thread packages/typedoc/package.json Outdated
Comment thread packages/typedoc/package.json Outdated
Comment thread packages/typedoc/README.md Outdated

The output directory receives:

- A page per exported function, class, interface, type alias and variable: `Function.build.md`, `Interface.BuildOptions.md`, …

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the format we want? [Type].[Name].md?

Not functions/build.md?

Comment thread packages/typedoc/README.md Outdated
Comment thread packages/typedoc/src/utils/files.mjs Outdated
Comment thread packages/typedoc/src/utils/files.mjs Outdated
Comment thread packages/typedoc/src/utils/markdown.mjs Outdated
Comment thread packages/typedoc/src/types.d.ts Outdated
Comment thread packages/typedoc/src/types.d.ts
@avivkeller

avivkeller commented Oct 2, 2026 •

Copy link
Copy Markdown
Member

diff.patch

I didn't want to push directly to your branch (although I will if you give me the OK), but here's how I would resolve most of these concerns

ovflowd added a commit to ovflowd/rolldown that referenced this pull request Oct 3, 2026
The plugin now builds on TypeDoc's router and options (nodejs/doc-kit#1125). The config uses `docKit`, TypeDoc's `basePath` for source links, and `docKitUrlAdapter` to keep rolldown.rs' URLs (`Interface.Plugin`, `InputOptions.input`), so every existing link and redirect still works. Plugin hooks get the signatures of `FunctionPluginHooks` from a small TypeDoc plugin, as `Plugin` types them as `ObjectHook<…>`, and the sidebar reads the new page list.

Assisted-by: Claude Opus 5.5 <noreply@anthropic.com>
@ovflowd

ovflowd commented Oct 3, 2026

Copy link
Copy Markdown
Member Author

@avivkeller heads-up: I brought back documenting a type on the page of the one member page using it (01f985c), which got dropped in your patch. E.g. rolldown's treeshake?: boolean | TreeshakingOptions now documents TreeshakingOptions' members right on the InputOptions.treeshake page (inputOptions.treeshake.annotations), and the type's own page just links there.

Rolldown has links into those (InputOptions.treeshake#annotations) that broke without it, and imo it's nicer to read an option and its sub-options in one place. Lmk if you feel it should work differently :)

@ovflowd
ovflowd requested a review from avivkeller October 3, 2026 14:18
ovflowd and others added 8 commits October 3, 2026 16:20
A TypeDoc plugin with a `doc-kit` output: one page per export, with doc-kit signatures, typed lists, stability indices and source links, plus a type map and a page list. Members of chosen types (a bundler's options, say) can have pages of their own, listed and linked wherever the type appears.

Assisted-by: Claude Opus 5.5 <noreply@anthropic.com>
Assisted-by: Claude Opus 5.5 <noreply@anthropic.com>
Assisted-by: Claude Opus 5.5 <noreply@anthropic.com>
Applies the changes suggested in review:

- pages follow TypeDoc's kind router (functions/build.md), with relative
  .md links and doc-kit's anchors, and cover modules, namespaces, enums
  and accessors
- a docKit output shortcut; docKitTypeMap and docKitPageList set or leave
  out those files
- events come from classes extending EventEmitter<Events>, entry point
  names from @module, and source links from TypeDoc's basePath;
  docKitSiteUrl, docKitEvents, docKitSignatureSources, docKitImportPaths
  and docKitMemberAnchors are gone
- files are written with doc-kit's helpers, and the plugin no longer
  prunes its output directory
- typedoc is a dependency
Past maxTypeConversionDepth, TypeDoc writes the rest of a type as `...`
(`Promise<...>`), which is no TypeScript, so doc-kit can't parse the
annotation. String literal types holding `...`, such as webpack's
`"..."`, are kept.

Assisted-by: Claude Opus 5.5 <noreply@anthropic.com>
A method of an object type has signatures but no type, so its item read
`{unknown}`. It now reads as its function type,
`(n?: NodeSyntax) => number`, with the description of its signature.

Assisted-by: Claude Opus 5.5 <noreply@anthropic.com>
A variable typed with a function type alias (`const identity: Transform`)
is documented as a function, as one typed with a callable interface is.

Assisted-by: Claude Opus 5.5 <noreply@anthropic.com>
An emitter that types its listeners instead of extending EventEmitter,
`on<E extends keyof Events>(event: E, listener: (...args: Events[E]) => void)`,
gets its events from the map its listener indexes. TypeDoc resolves the
`keyof Events` constraint to the event names, so the listener is what
still names the map.

Assisted-by: Claude Opus 5.5 <noreply@anthropic.com>
Assisted-by: Claude Opus 5.5 <noreply@anthropic.com>
Besides doc-kit's anchor of its heading, a member gets one of its name
alone (`#resolveid`), so links to it survive changes of its signature,
and links into a reference written with TypeDoc's anchors keep working.

Assisted-by: Claude Opus 5.5 <noreply@anthropic.com>
With `@mergeModuleWith <project>`, the exports of the main entry point
are the project's own, and their URLs have no module prefix
(`functions/build.md`). The exports of the other entry points are still
noted as exported from them.

Assisted-by: Claude Opus 5.5 <noreply@anthropic.com>
A re-export linked to an anchor its module's page doesn't have, and
functions were listed without a summary, as their comment is their
signature's.

Assisted-by: Claude Opus 5.5 <noreply@anthropic.com>
A function given each page's default URL (`interfaces/Plugin`) and its
reflection returns the URL to use, so a site moving to doc-kit keeps its
URLs (`Interface.Plugin`), or picks a layout of its own. Links, the type
map and the page list follow it.

Assisted-by: Claude Opus 5.5 <noreply@anthropic.com>
Assisted-by: Claude Opus 5.5 <noreply@anthropic.com>
A type with members that only one member with a page of its own uses, as
`treeshake?: boolean | TreeshakeOptions` uses `TreeshakeOptions`, is
documented on that member's page, as `buildOptions.treeshake.annotations`
and so on. The type's own page links there, and so do links to it and the
type map; the page list leaves it out. The plugin did this before, and
links into such options (`InputOptions.treeshake#annotations`) rely on it.

Assisted-by: Claude Opus 5.5 <noreply@anthropic.com>
Oxfmt sorts imports since #1120, with parent imports before sibling ones.

Assisted-by: Claude Opus 5.5 <noreply@anthropic.com>
@@ -0,0 +1,71 @@
# `@doc-kit/typedoc`

A [TypeDoc](https://typedoc.org) plugin writing the API reference of a TypeScript project as [doc-kit Markdown](../../docs/specification.md): a page per module, namespace and export, with doc-kit's signatures, typed lists, stability indices and source links, which doc-kit then builds into a site.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
A [TypeDoc](https://typedoc.org) plugin writing the API reference of a TypeScript project as [doc-kit Markdown](../../docs/specification.md): a page per module, namespace and export, with doc-kit's signatures, typed lists, stability indices and source links, which doc-kit then builds into a site.
An **experimental** [TypeDoc](https://typedoc.org) plugin writing the API reference of a TypeScript project as [doc-kit Markdown](../../docs/specification.md): a page per module, namespace and export, with doc-kit's signatures, typed lists, stability indices and source links, which doc-kit then builds into a site.

@ovflowd
ovflowd requested a review from avivkeller October 3, 2026 23:04

This branch was successfully deployed

1 active deployment
Preview – api-docs-tooling — ea26c1e3 Deployed Oct 3, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants