@tanstack/markdown
Version:
A tiny, fast, deterministic Markdown renderer for blogs and documentation.
367 lines (292 loc) • 10 kB
Markdown
# AST and Options Reference
This reference targets `@tanstack/markdown@0.0.13`. Shared public types are
exported from `@tanstack/markdown`.
## Entry Points
Use the narrowest entry point needed by the application:
```ts
import {
parseInline,
parseMarkdown,
renderBlock,
renderDocument,
renderHtml,
renderInline,
type MarkdownDocument,
type ParseOptions,
type RenderOptions,
} from '@tanstack/markdown'
const options: ParseOptions = {
frontmatter: true,
headingIds: true,
}
const document: MarkdownDocument = parseMarkdown('# API', options)
const inline = parseInline('Use **stable** APIs.')
const blockHtml = document.children.map(node => renderBlock(node)).join('\n')
const inlineHtml = inline.map(node => renderInline(node)).join('')
const documentHtml = renderDocument(document)
const completeHtml = renderHtml(document, {
headingAnchors: true,
} satisfies RenderOptions)
console.log({ blockHtml, inlineHtml, documentHtml, completeHtml })
```
Equivalent narrow imports are:
```ts
import { parseMarkdown } from '@tanstack/markdown/parser'
import {
renderBlock,
renderDocument,
renderHtml,
renderInline,
} from '@tanstack/markdown/html'
const document = parseMarkdown('# Narrow imports')
const html = renderHtml(document)
console.log({
html,
documentHtml: renderDocument(document),
firstBlockHtml: renderBlock(document.children[0]!),
inlineHtml: renderInline({
type: 'text',
value: 'Inline text',
}),
})
```
## Function Signatures
```ts
import type {
BlockNode,
InlineNode,
MarkdownDocument,
MarkdownInput,
ParseOptions,
RenderOptions,
} from '@tanstack/markdown'
declare function parseMarkdown(
markdown: string,
options?: ParseOptions,
): MarkdownDocument
declare function parseInline(
value: string,
options?: ParseOptions,
): InlineNode[]
declare function renderHtml(
input: MarkdownInput,
options?: RenderOptions,
): string
declare function renderDocument(
document: MarkdownDocument,
options?: RenderOptions,
): string
declare function renderBlock(
node: BlockNode,
options?: RenderOptions,
): string
declare function renderInline(
node: InlineNode,
options?: RenderOptions,
): string
```
`MarkdownInput` is `string | MarkdownDocument`. Only complete-document
renderers accept source strings. `renderBlock` and `renderInline` accept one
already-parsed node.
## Document Shape
```ts
import type { BlockNode, MarkdownHeading } from '@tanstack/markdown'
interface MarkdownDocument {
type: 'root'
children: BlockNode[]
frontmatter?: string
headings?: MarkdownHeading[]
}
```
- `frontmatter` contains the raw text between leading `---` delimiters.
- `headings` is optional extension-derived data, not part of core parsing.
- The object is plain and serializable.
- Because the package is pre-1.0, persisted ASTs should be regenerated after
an upgrade that changes node contracts.
## Block Nodes
`BlockNode` is a discriminated union:
| `type` | Interface | Fields beyond `type` |
| --- | --- | --- |
| `heading` | `HeadingNode` | `depth`, optional `id`, optional `framework`, inline `children` |
| `paragraph` | `ParagraphNode` | inline `children` |
| `code` | `CodeBlockNode` | raw `value`; optional `lang`, `meta`, `title`, `file`, `framework`, `highlightLines` |
| `list` | `ListNode` | `ordered`, optional `start`, optional `loose`, `items` |
| `blockquote` | `BlockquoteNode` | block `children` |
| `table` | `TableNode` | per-column `align`, `header`, body `rows` |
| `footnotes` | `FootnotesNode` | `items` |
| `thematicBreak` | `ThematicBreakNode` | no additional fields |
| `html` | `HtmlBlockNode` | raw `value` |
| `callout` | `CalloutNode` | `kind`, `title`, block `children` |
| `component` | `ComponentNode` | `name`, `attributes`, block `children`, optional `tagName`, optional `properties` |
Supporting block shapes:
```ts
import type { BlockNode, InlineNode } from '@tanstack/markdown'
interface ListItemNode {
type: 'listItem'
checked?: boolean
children: BlockNode[]
}
interface TableCellNode {
type: 'tableCell'
children: InlineNode[]
}
interface FootnoteItemNode {
id: string
number: number
referenceCount?: number
children: BlockNode[]
}
```
Important rendering invariants:
- `ListNode.loose: true` preserves paragraph wrappers for every list item.
- `ListItemNode.checked` controls task-list checkbox rendering.
- A non-`1` ordered-list `start` renders the corresponding `<ol start>`.
- `HtmlBlockNode` is created only when parsing with `allowHtml: true`.
- `CalloutNode` and `ComponentNode` are extension-oriented public nodes.
## Inline Nodes
`InlineNode` is a discriminated union:
| `type` | Interface | Fields beyond `type` |
| --- | --- | --- |
| `text` | `TextNode` | `value` |
| `inlineCode` | `CodeSpanNode` | `value` |
| `strong` | `StrongNode` | inline `children` |
| `emphasis` | `EmphasisNode` | inline `children` |
| `strike` | `StrikeNode` | inline `children` |
| `footnoteReference` | `FootnoteReferenceNode` | normalized `id`, display `number`, optional `referenceIndex` |
| `link` | `LinkNode` | sanitized `href`, optional `title`, inline `children` |
| `image` | `ImageNode` | sanitized `src`, text `alt`, optional `title` |
| `break` | `BreakNode` | no additional fields |
| `inlineHtml` | `HtmlInlineNode` | raw `value` |
`HtmlInlineNode` is created only when parsing with `allowHtml: true`.
Repeated footnote references use `referenceIndex` to produce unique source
IDs and back links.
## Parse Options
```ts
import type {
FootnoteDefinition,
LinkReferenceDefinition,
MarkdownExtension,
} from '@tanstack/markdown'
interface ParseOptions {
allowHtml?: boolean
frontmatter?: boolean
headingIds?: boolean | ((text: string, index: number) => string)
extensions?: MarkdownExtension[]
references?: Record<string, LinkReferenceDefinition>
footnotes?: Record<string, FootnoteDefinition>
footnoteOrder?: string[]
footnoteCounts?: Record<string, number>
}
```
| Option | Default | Contract |
| --- | --- | --- |
| `allowHtml` | `false` | Recognize raw block and inline HTML nodes. Rendering also requires this option to emit their raw values. |
| `frontmatter` | `true` | Extract one leading `---` block into `document.frontmatter`. The library does not parse YAML. |
| `headingIds` | `true` | Generate duplicate-safe IDs, disable IDs with `false`, or return an ID from `(text, normalizedLineIndex)`. |
| `extensions` | `[]` | Run block parsers and inline/document transforms in array order. |
| `references` | internal | Carry normalized reference definitions through nested parsing. |
| `footnotes` | internal | Carry normalized footnote definitions through nested parsing. |
| `footnoteOrder` | internal | Track first-reference order through nested parsing. |
| `footnoteCounts` | internal | Track repeated references for unique IDs and back links. |
Ordinary complete-document calls should leave the four parser-state fields
unset. They are public for extension-driven nested parsing.
The custom heading ID callback receives a zero-based line index after input
normalization and possible frontmatter/definition extraction. Treat it as a
stable parser index, not a source-location API.
## Render Options
`RenderOptions` extends every `ParseOptions` field:
```ts
import type { ParseOptions } from '@tanstack/markdown'
interface RenderOptions extends ParseOptions {
highlighter?: CodeHighlighter
codeLineNumbers?: boolean
headingAnchors?: boolean | HeadingAnchorOptions
}
interface HeadingAnchorOptions {
content?: string
className?: string
ariaHidden?: boolean
tabIndex?: number
}
interface CodeHighlightOptions {
highlightLines?: number[]
lineNumbers?: boolean
}
interface CodeHighlighter {
(
code: string,
lang?: string,
options?: CodeHighlightOptions,
): string
}
```
| Option | Default | Contract |
| --- | --- | --- |
| `highlighter` | none | Return trusted HTML for the contents of the emitted `<code>` element. |
| `codeLineNumbers` | unset | Forward the preference to the highlighter as `lineNumbers`. |
| `headingAnchors` | `false` | Append a default or configured anchor to headings that already have IDs. |
For a source-string input, `renderHtml(source, options)` parses and renders
with the same options. For a `MarkdownDocument` input, parser options cannot
change existing AST structure. Renderer options and extension `renderHtml`
hooks still apply.
## Derived Data Shapes
```ts
import type { BlockNode } from '@tanstack/markdown'
interface MarkdownHeading {
id: string
text: string
level: number
framework?: string
}
interface LinkReferenceDefinition {
href: string
title?: string
}
interface FootnoteDefinition {
label: string
content: string
id?: string
}
const heading: MarkdownHeading = {
id: 'install',
text: 'Install',
level: 2,
}
const reference: LinkReferenceDefinition = {
href: '/install',
title: 'Installation',
}
const footnote: FootnoteDefinition = {
label: 'support',
content: 'Supported in maintained browsers.',
}
const blocks: BlockNode[] = []
console.log({ heading, reference, footnote, blocks })
```
Core parsing does not populate `document.headings`; the heading collection
extension does. Link and footnote definition maps are parser state and are
normally extracted automatically by `parseMarkdown`.
## Narrowing the AST
Use each node's `type` discriminant:
```ts
import {
parseMarkdown,
type HeadingNode,
type MarkdownDocument,
} from '@tanstack/markdown'
function collectHeadings(document: MarkdownDocument): HeadingNode[] {
return document.children.filter(
(node): node is HeadingNode => node.type === 'heading',
)
}
const document = parseMarkdown(`# Guide
## Install
Content`)
const headings = collectHeadings(document)
console.log(headings.map(heading => ({
depth: heading.depth,
id: heading.id,
})))
```
The same `MarkdownDocument` can be serialized, transformed as plain data, or
rendered through HTML, React, and Octane adapters.