@tanstack/markdown 0.0.9 → 0.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -4
- package/dist/extensions/streaming.d.ts +3 -0
- package/dist/extensions/streaming.d.ts.map +1 -0
- package/dist/extensions/streaming.js +46 -0
- package/dist/inline.js +52 -11
- package/dist/parser.js +16 -3
- package/package.json +26 -3
- package/skills/custom-extensions/SKILL.md +444 -0
- package/skills/docs-features/SKILL.md +436 -0
- package/skills/docs-features/references/docs-metadata.md +320 -0
- package/skills/octane-rendering/SKILL.md +351 -0
- package/skills/production-pipelines/SKILL.md +493 -0
- package/skills/react-rendering/SKILL.md +309 -0
- package/skills/render-markdown/SKILL.md +335 -0
- package/skills/render-markdown/references/ast-and-options.md +366 -0
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: 'react-rendering'
|
|
3
|
+
description: >
|
|
4
|
+
Render Markdown source or a MarkdownDocument with @tanstack/markdown/react
|
|
5
|
+
using Markdown, renderMarkdownReact, component replacements, and React static
|
|
6
|
+
SSR. Load for React article components, emitted-tag mappings, pre-parsed
|
|
7
|
+
documents, custom elements, or renderer parity.
|
|
8
|
+
metadata:
|
|
9
|
+
type: framework
|
|
10
|
+
library: '@tanstack/markdown'
|
|
11
|
+
framework: 'react'
|
|
12
|
+
library_version: '0.0.10'
|
|
13
|
+
requires:
|
|
14
|
+
- 'render-markdown'
|
|
15
|
+
sources:
|
|
16
|
+
- 'TanStack/markdown:docs/guides/react.md'
|
|
17
|
+
- 'TanStack/markdown:docs/reference/react.md'
|
|
18
|
+
- 'TanStack/markdown:docs/guides/extensions.md'
|
|
19
|
+
- 'TanStack/markdown:docs/core-concepts/security.md'
|
|
20
|
+
- 'TanStack/markdown:src/react.ts'
|
|
21
|
+
- 'TanStack/markdown:tests/ssr-react.test.tsx'
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
This skill builds on [render-markdown](../render-markdown/SKILL.md). Read it first for the syntax profile, parser options, AST, and core trust boundaries.
|
|
25
|
+
|
|
26
|
+
# React Rendering
|
|
27
|
+
|
|
28
|
+
## Setup
|
|
29
|
+
|
|
30
|
+
Render source directly and keep the surrounding semantic element in the application:
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
import { Markdown } from '@tanstack/markdown/react'
|
|
34
|
+
|
|
35
|
+
export function Article({ source }: { source: string }) {
|
|
36
|
+
return (
|
|
37
|
+
<article>
|
|
38
|
+
<Markdown>{source}</Markdown>
|
|
39
|
+
</article>
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`Markdown` returns a React fragment. The React adapter requires React 18 or newer.
|
|
45
|
+
|
|
46
|
+
## Hooks and Components
|
|
47
|
+
|
|
48
|
+
### Replace emitted links with an application component
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
import type { ComponentProps } from 'react'
|
|
52
|
+
import { Markdown } from '@tanstack/markdown/react'
|
|
53
|
+
|
|
54
|
+
function ArticleLink(props: ComponentProps<'a'>) {
|
|
55
|
+
const external = props.href?.startsWith('http') ?? false
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<a
|
|
59
|
+
{...props}
|
|
60
|
+
rel={external ? 'noreferrer' : props.rel}
|
|
61
|
+
target={external ? '_blank' : props.target}
|
|
62
|
+
/>
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function Article({ source }: { source: string }) {
|
|
67
|
+
return (
|
|
68
|
+
<article>
|
|
69
|
+
<Markdown components={{ a: ArticleLink }}>{source}</Markdown>
|
|
70
|
+
</article>
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
`components` is keyed by emitted intrinsic or custom-element tag names.
|
|
76
|
+
|
|
77
|
+
### Parse once before repeated React rendering
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
import { parseMarkdown } from '@tanstack/markdown/parser'
|
|
81
|
+
import { Markdown } from '@tanstack/markdown/react'
|
|
82
|
+
|
|
83
|
+
const document = parseMarkdown('# Cached article\n\nRendered from an AST.')
|
|
84
|
+
|
|
85
|
+
export function Article() {
|
|
86
|
+
return (
|
|
87
|
+
<article>
|
|
88
|
+
<Markdown>{document}</Markdown>
|
|
89
|
+
</article>
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Apply parser options and document transforms when creating the document; renderer options cannot retroactively change its structure.
|
|
95
|
+
|
|
96
|
+
### Render static React markup on the server
|
|
97
|
+
|
|
98
|
+
```tsx
|
|
99
|
+
import { renderToStaticMarkup } from 'react-dom/server'
|
|
100
|
+
import { Markdown } from '@tanstack/markdown/react'
|
|
101
|
+
|
|
102
|
+
const source = '# Server-rendered article'
|
|
103
|
+
|
|
104
|
+
export const html = renderToStaticMarkup(
|
|
105
|
+
<article>
|
|
106
|
+
<Markdown>{source}</Markdown>
|
|
107
|
+
</article>,
|
|
108
|
+
)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Core static output is tested for structural equivalence with `renderHtml()`.
|
|
112
|
+
|
|
113
|
+
### Compose through the lower-level node API
|
|
114
|
+
|
|
115
|
+
```tsx
|
|
116
|
+
import type { ReactNode } from 'react'
|
|
117
|
+
import { parseMarkdown } from '@tanstack/markdown/parser'
|
|
118
|
+
import { renderMarkdownReact } from '@tanstack/markdown/react'
|
|
119
|
+
|
|
120
|
+
const document = parseMarkdown('# Parsed once')
|
|
121
|
+
|
|
122
|
+
export function ArticleBody(): ReactNode {
|
|
123
|
+
return renderMarkdownReact(document)
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Use `renderBlockReact` or `renderInlineReact` only when custom tree composition needs individual public AST nodes.
|
|
128
|
+
|
|
129
|
+
## Common Mistakes
|
|
130
|
+
|
|
131
|
+
### HIGH Injecting HTML instead of React nodes
|
|
132
|
+
|
|
133
|
+
Wrong:
|
|
134
|
+
|
|
135
|
+
```tsx
|
|
136
|
+
import { renderHtml } from '@tanstack/markdown'
|
|
137
|
+
|
|
138
|
+
export function Article({ source }: { source: string }) {
|
|
139
|
+
const html = renderHtml(source)
|
|
140
|
+
return <article dangerouslySetInnerHTML={{ __html: html }} />
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Correct:
|
|
145
|
+
|
|
146
|
+
```tsx
|
|
147
|
+
import { Markdown } from '@tanstack/markdown/react'
|
|
148
|
+
|
|
149
|
+
export function Article({ source }: { source: string }) {
|
|
150
|
+
return (
|
|
151
|
+
<article>
|
|
152
|
+
<Markdown>{source}</Markdown>
|
|
153
|
+
</article>
|
|
154
|
+
)
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
The HTML-string path bypasses React component replacement and creates an unnecessary trusted-HTML boundary.
|
|
159
|
+
|
|
160
|
+
Source: `docs/guides/react.md`
|
|
161
|
+
|
|
162
|
+
### HIGH Mapping AST node names instead of tags
|
|
163
|
+
|
|
164
|
+
Wrong:
|
|
165
|
+
|
|
166
|
+
```tsx
|
|
167
|
+
import type { ComponentProps } from 'react'
|
|
168
|
+
import { Markdown } from '@tanstack/markdown/react'
|
|
169
|
+
|
|
170
|
+
function ArticleLink(props: ComponentProps<'a'>) {
|
|
171
|
+
return <a {...props} data-navigation="article" />
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function Article() {
|
|
175
|
+
return (
|
|
176
|
+
<Markdown components={{ link: ArticleLink }}>
|
|
177
|
+
{'Read the [guide](/guide).'}
|
|
178
|
+
</Markdown>
|
|
179
|
+
)
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Correct:
|
|
184
|
+
|
|
185
|
+
```tsx
|
|
186
|
+
import type { ComponentProps } from 'react'
|
|
187
|
+
import { Markdown } from '@tanstack/markdown/react'
|
|
188
|
+
|
|
189
|
+
function ArticleLink(props: ComponentProps<'a'>) {
|
|
190
|
+
return <a {...props} data-navigation="article" />
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function Article() {
|
|
194
|
+
return (
|
|
195
|
+
<Markdown components={{ a: ArticleLink }}>
|
|
196
|
+
{'Read the [guide](/guide).'}
|
|
197
|
+
</Markdown>
|
|
198
|
+
)
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
The map is keyed by emitted tag names such as `a`, not AST discriminants such as `link`.
|
|
203
|
+
|
|
204
|
+
Source: `docs/guides/react.md`
|
|
205
|
+
|
|
206
|
+
### HIGH Expecting HTML extension hooks in React
|
|
207
|
+
|
|
208
|
+
Wrong:
|
|
209
|
+
|
|
210
|
+
```tsx
|
|
211
|
+
import type { MarkdownExtension } from '@tanstack/markdown'
|
|
212
|
+
import { Markdown } from '@tanstack/markdown/react'
|
|
213
|
+
|
|
214
|
+
const paragraphHtml: MarkdownExtension = {
|
|
215
|
+
name: 'paragraph-html',
|
|
216
|
+
renderHtml(node) {
|
|
217
|
+
return node.type === 'paragraph'
|
|
218
|
+
? '<aside>Rendered only by the HTML renderer</aside>'
|
|
219
|
+
: undefined
|
|
220
|
+
},
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export function Article() {
|
|
224
|
+
return <Markdown extensions={[paragraphHtml]}>Ordinary content</Markdown>
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Correct:
|
|
229
|
+
|
|
230
|
+
```tsx
|
|
231
|
+
import type { MarkdownExtension } from '@tanstack/markdown'
|
|
232
|
+
import type { PropsWithChildren } from 'react'
|
|
233
|
+
import { Markdown } from '@tanstack/markdown/react'
|
|
234
|
+
|
|
235
|
+
const panelExtension: MarkdownExtension = {
|
|
236
|
+
name: 'panel',
|
|
237
|
+
transformDocument(document) {
|
|
238
|
+
return {
|
|
239
|
+
...document,
|
|
240
|
+
children: [
|
|
241
|
+
{
|
|
242
|
+
type: 'component',
|
|
243
|
+
name: 'panel',
|
|
244
|
+
tagName: 'doc-panel',
|
|
245
|
+
attributes: {},
|
|
246
|
+
children: document.children,
|
|
247
|
+
},
|
|
248
|
+
],
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function Panel({ children }: PropsWithChildren) {
|
|
254
|
+
return <aside className="documentation-panel">{children}</aside>
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export function Article() {
|
|
258
|
+
return (
|
|
259
|
+
<Markdown
|
|
260
|
+
components={{ 'doc-panel': Panel }}
|
|
261
|
+
extensions={[panelExtension]}
|
|
262
|
+
>
|
|
263
|
+
Ordinary content
|
|
264
|
+
</Markdown>
|
|
265
|
+
)
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
`MarkdownExtension.renderHtml` is HTML-specific; portable custom output uses `ComponentNode` and an emitted-tag component mapping.
|
|
270
|
+
|
|
271
|
+
Source: `docs/guides/extensions.md`
|
|
272
|
+
|
|
273
|
+
### CRITICAL Enabling raw HTML for untrusted input
|
|
274
|
+
|
|
275
|
+
Wrong:
|
|
276
|
+
|
|
277
|
+
```tsx
|
|
278
|
+
import { Markdown } from '@tanstack/markdown/react'
|
|
279
|
+
|
|
280
|
+
export function UserPost({ source }: { source: string }) {
|
|
281
|
+
return <Markdown allowHtml>{source}</Markdown>
|
|
282
|
+
}
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
Correct:
|
|
286
|
+
|
|
287
|
+
```tsx
|
|
288
|
+
import { Markdown } from '@tanstack/markdown/react'
|
|
289
|
+
|
|
290
|
+
export function UserPost({ source }: { source: string }) {
|
|
291
|
+
return <Markdown>{source}</Markdown>
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
With `allowHtml`, React uses `dangerouslySetInnerHTML`; TanStack Markdown does not sanitize that raw HTML.
|
|
296
|
+
|
|
297
|
+
Source: `docs/core-concepts/security.md`
|
|
298
|
+
|
|
299
|
+
### HIGH Tension: renderer parity versus React customization
|
|
300
|
+
|
|
301
|
+
Core React SSR is tested against `renderHtml()`. React component replacements, raw HTML, highlighter output, and HTML-only extension hooks are application-controlled boundaries outside that parity guarantee.
|
|
302
|
+
|
|
303
|
+
See also: [custom-extensions](../custom-extensions/SKILL.md) for portable `ComponentNode` output.
|
|
304
|
+
|
|
305
|
+
## Cross-References
|
|
306
|
+
|
|
307
|
+
- [render-markdown](../render-markdown/SKILL.md) - shared `MarkdownInput`, parser options, AST behavior, and syntax profile.
|
|
308
|
+
- [custom-extensions](../custom-extensions/SKILL.md) - emit custom tags that React can replace through `components`.
|
|
309
|
+
- [production-pipelines](../production-pipelines/SKILL.md) - audit raw HTML, highlighter output, untrusted content, and SSR boundaries.
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: render-markdown
|
|
3
|
+
description: >
|
|
4
|
+
Parse Markdown with parseMarkdown or parseInline, render HTML with renderHtml,
|
|
5
|
+
renderDocument, renderBlock, or renderInline, configure frontmatter and
|
|
6
|
+
heading IDs, and reuse the serializable MarkdownDocument AST. Load for
|
|
7
|
+
@tanstack/markdown core syntax, parser options, HTML output, references,
|
|
8
|
+
footnotes, lists, tables, or AST work.
|
|
9
|
+
metadata:
|
|
10
|
+
type: core
|
|
11
|
+
library: '@tanstack/markdown'
|
|
12
|
+
library_version: '0.0.10'
|
|
13
|
+
sources:
|
|
14
|
+
- 'TanStack/markdown:docs/quick-start.md'
|
|
15
|
+
- 'TanStack/markdown:docs/core-concepts/document-model.md'
|
|
16
|
+
- 'TanStack/markdown:docs/core-concepts/parsing.md'
|
|
17
|
+
- 'TanStack/markdown:docs/core-concepts/rendering.md'
|
|
18
|
+
- 'TanStack/markdown:docs/core-concepts/syntax-profile.md'
|
|
19
|
+
- 'TanStack/markdown:src/parser.ts'
|
|
20
|
+
- 'TanStack/markdown:src/html.ts'
|
|
21
|
+
- 'TanStack/markdown:src/types.ts'
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
# Render Markdown
|
|
25
|
+
|
|
26
|
+
## Setup
|
|
27
|
+
|
|
28
|
+
Install the framework-neutral package:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pnpm add @tanstack/markdown
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Render supported Markdown to HTML:
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import { renderHtml } from '@tanstack/markdown/html'
|
|
38
|
+
|
|
39
|
+
const source = `# Release notes
|
|
40
|
+
|
|
41
|
+
- Small browser bundle
|
|
42
|
+
- Serializable AST
|
|
43
|
+
- Safe HTML defaults`
|
|
44
|
+
|
|
45
|
+
const html = renderHtml(source)
|
|
46
|
+
|
|
47
|
+
console.log(html)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Use the narrow `@tanstack/markdown/parser` and
|
|
51
|
+
`@tanstack/markdown/html` entry points when the default entry's combined
|
|
52
|
+
exports are unnecessary.
|
|
53
|
+
|
|
54
|
+
## Core Patterns
|
|
55
|
+
|
|
56
|
+
### Parse once and render a reusable document
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
import { renderHtml } from '@tanstack/markdown/html'
|
|
60
|
+
import { parseMarkdown } from '@tanstack/markdown/parser'
|
|
61
|
+
import type { MarkdownDocument } from '@tanstack/markdown'
|
|
62
|
+
|
|
63
|
+
const source = `# Guide
|
|
64
|
+
|
|
65
|
+
Read the [installation notes](/installation).`
|
|
66
|
+
|
|
67
|
+
const document = parseMarkdown(source)
|
|
68
|
+
const serialized = JSON.stringify(document)
|
|
69
|
+
const restored = JSON.parse(serialized) as MarkdownDocument
|
|
70
|
+
const html = renderHtml(restored)
|
|
71
|
+
|
|
72
|
+
console.log(html)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
All complete-document renderers accept a source string or an existing
|
|
76
|
+
`MarkdownDocument`; a document input skips parsing.
|
|
77
|
+
|
|
78
|
+
### Configure frontmatter and heading IDs while parsing
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
import { renderHtml } from '@tanstack/markdown/html'
|
|
82
|
+
import { parseMarkdown } from '@tanstack/markdown/parser'
|
|
83
|
+
|
|
84
|
+
const source = `---
|
|
85
|
+
title: Installation
|
|
86
|
+
published: true
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
# Install
|
|
90
|
+
|
|
91
|
+
## Install`
|
|
92
|
+
|
|
93
|
+
const document = parseMarkdown(source, {
|
|
94
|
+
frontmatter: true,
|
|
95
|
+
headingIds(text, lineIndex) {
|
|
96
|
+
const slug = text.toLowerCase().replaceAll(' ', '-')
|
|
97
|
+
return `docs-${lineIndex}-${slug}`
|
|
98
|
+
},
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
console.log(document.frontmatter)
|
|
102
|
+
console.log(renderHtml(document))
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Frontmatter remains an unparsed string. Heading IDs are generated during
|
|
106
|
+
parsing and are duplicate-safe under the default slugger.
|
|
107
|
+
|
|
108
|
+
### Add visible heading anchors while rendering
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
import { renderHtml } from '@tanstack/markdown/html'
|
|
112
|
+
import { parseMarkdown } from '@tanstack/markdown/parser'
|
|
113
|
+
|
|
114
|
+
const document = parseMarkdown('# API')
|
|
115
|
+
|
|
116
|
+
const html = renderHtml(document, {
|
|
117
|
+
headingAnchors: {
|
|
118
|
+
content: '#',
|
|
119
|
+
className: 'heading-anchor',
|
|
120
|
+
ariaHidden: true,
|
|
121
|
+
tabIndex: -1,
|
|
122
|
+
},
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
console.log(html)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Heading IDs are a parse concern; visible anchor links are a render concern.
|
|
129
|
+
|
|
130
|
+
### Parse inline content only when no block context is needed
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
import { parseInline, renderInline } from '@tanstack/markdown'
|
|
134
|
+
|
|
135
|
+
const nodes = parseInline('Use **stable** APIs and `parseMarkdown`.')
|
|
136
|
+
const html = nodes.map(node => renderInline(node)).join('')
|
|
137
|
+
|
|
138
|
+
console.log(html)
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
`parseInline` handles inline syntax only. Use `parseMarkdown` for headings,
|
|
142
|
+
lists, tables, frontmatter, reference definitions, and footnote definitions.
|
|
143
|
+
|
|
144
|
+
## Behavioral Contracts
|
|
145
|
+
|
|
146
|
+
- Parsing is synchronous, deterministic, and normalizes line endings.
|
|
147
|
+
- Raw block and inline HTML are recognized only with `allowHtml: true`.
|
|
148
|
+
- Link and image URLs have unsafe executable protocols removed.
|
|
149
|
+
- Tight lists place simple content directly under `<li>`; loose lists retain
|
|
150
|
+
paragraph wrappers; task checkboxes remain inline with labels.
|
|
151
|
+
- Reference definitions resolve case-insensitively before block parsing.
|
|
152
|
+
- Footnotes render in first-reference order with collision-safe IDs and
|
|
153
|
+
repeated-reference back links.
|
|
154
|
+
- Code fence metadata is recorded in the AST; highlighting is external.
|
|
155
|
+
- The AST is public but pre-1.0. Regenerate persisted documents when an
|
|
156
|
+
upgrade changes node contracts.
|
|
157
|
+
|
|
158
|
+
## Compatibility and Option Timing
|
|
159
|
+
|
|
160
|
+
TanStack Markdown targets controlled blog and documentation content rather
|
|
161
|
+
than complete CommonMark, GFM, MDX, or arbitrary HTML parsing. Before adding
|
|
162
|
+
syntax, require corpus evidence, regression coverage, renderer parity, and an
|
|
163
|
+
accepted bundle cost. See `production-pipelines/SKILL.md` for compatibility,
|
|
164
|
+
security, highlighting, and size gates.
|
|
165
|
+
|
|
166
|
+
Parsing fixes the document structure. Apply `frontmatter`, `headingIds`,
|
|
167
|
+
`allowHtml`, parser state, and parser/transform extensions before caching the
|
|
168
|
+
AST. Renderer-only options such as `headingAnchors`, `highlighter`, and
|
|
169
|
+
`codeLineNumbers` may be applied when rendering an existing document.
|
|
170
|
+
|
|
171
|
+
## Common Mistakes
|
|
172
|
+
|
|
173
|
+
### HIGH Assuming complete CommonMark or GFM behavior
|
|
174
|
+
|
|
175
|
+
Wrong:
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
import { renderHtml } from '@tanstack/markdown/html'
|
|
179
|
+
|
|
180
|
+
const markdownFromAnywhere = `Heading
|
|
181
|
+
===
|
|
182
|
+
|
|
183
|
+
Visit https://example.com`
|
|
184
|
+
|
|
185
|
+
const html = renderHtml(markdownFromAnywhere)
|
|
186
|
+
|
|
187
|
+
console.log(html)
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Correct:
|
|
191
|
+
|
|
192
|
+
```ts
|
|
193
|
+
import { renderHtml } from '@tanstack/markdown/html'
|
|
194
|
+
|
|
195
|
+
const controlledMarkdown = `# Heading
|
|
196
|
+
|
|
197
|
+
Visit [example](https://example.com)`
|
|
198
|
+
|
|
199
|
+
const html = renderHtml(controlledMarkdown)
|
|
200
|
+
|
|
201
|
+
console.log(html)
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Setext headings and automatic URL linking are outside the supported syntax
|
|
205
|
+
profile, so unsupported input can remain literal or have different structure.
|
|
206
|
+
|
|
207
|
+
Source: `docs/core-concepts/syntax-profile.md`
|
|
208
|
+
|
|
209
|
+
See also: `production-pipelines/SKILL.md` - compatibility breadth must be
|
|
210
|
+
justified against corpus evidence and the bundle budget.
|
|
211
|
+
|
|
212
|
+
### MEDIUM Reparsing unchanged content on every render
|
|
213
|
+
|
|
214
|
+
Wrong:
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
import { renderHtml } from '@tanstack/markdown/html'
|
|
218
|
+
|
|
219
|
+
const source = '# Cached article'
|
|
220
|
+
|
|
221
|
+
function renderArticle() {
|
|
222
|
+
return renderHtml(source)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
console.log(renderArticle())
|
|
226
|
+
console.log(renderArticle())
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Correct:
|
|
230
|
+
|
|
231
|
+
```ts
|
|
232
|
+
import { renderHtml } from '@tanstack/markdown/html'
|
|
233
|
+
import { parseMarkdown } from '@tanstack/markdown/parser'
|
|
234
|
+
|
|
235
|
+
const source = '# Cached article'
|
|
236
|
+
const document = parseMarkdown(source)
|
|
237
|
+
|
|
238
|
+
function renderArticle() {
|
|
239
|
+
return renderHtml(document)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
console.log(renderArticle())
|
|
243
|
+
console.log(renderArticle())
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Complete-document renderers parse string inputs on every call, while a
|
|
247
|
+
`MarkdownDocument` input skips repeated parsing.
|
|
248
|
+
|
|
249
|
+
Source: `docs/core-concepts/document-model.md`
|
|
250
|
+
|
|
251
|
+
See also: `production-pipelines/SKILL.md` - parse-ahead caching must preserve
|
|
252
|
+
the parser option and extension set.
|
|
253
|
+
|
|
254
|
+
### HIGH Applying parser options after parsing
|
|
255
|
+
|
|
256
|
+
Wrong:
|
|
257
|
+
|
|
258
|
+
```ts
|
|
259
|
+
import { renderHtml } from '@tanstack/markdown/html'
|
|
260
|
+
import { parseMarkdown } from '@tanstack/markdown/parser'
|
|
261
|
+
|
|
262
|
+
const document = parseMarkdown('# API')
|
|
263
|
+
const html = renderHtml(document, { headingIds: false })
|
|
264
|
+
|
|
265
|
+
console.log(html)
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
Correct:
|
|
269
|
+
|
|
270
|
+
```ts
|
|
271
|
+
import { renderHtml } from '@tanstack/markdown/html'
|
|
272
|
+
import { parseMarkdown } from '@tanstack/markdown/parser'
|
|
273
|
+
|
|
274
|
+
const document = parseMarkdown('# API', { headingIds: false })
|
|
275
|
+
const html = renderHtml(document)
|
|
276
|
+
|
|
277
|
+
console.log(html)
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
Parse-only options do not retroactively alter a pre-parsed AST.
|
|
281
|
+
|
|
282
|
+
Source: `docs/reference/html.md`
|
|
283
|
+
|
|
284
|
+
See also: `custom-extensions/SKILL.md` - parser and transform extensions must
|
|
285
|
+
also be present while creating a cached document.
|
|
286
|
+
|
|
287
|
+
### MEDIUM Using parseInline for document definitions
|
|
288
|
+
|
|
289
|
+
Wrong:
|
|
290
|
+
|
|
291
|
+
```ts
|
|
292
|
+
import { parseInline, renderInline } from '@tanstack/markdown'
|
|
293
|
+
|
|
294
|
+
const source = `[Guide][guide]
|
|
295
|
+
|
|
296
|
+
[guide]: /guide`
|
|
297
|
+
|
|
298
|
+
const html = parseInline(source).map(node => renderInline(node)).join('')
|
|
299
|
+
|
|
300
|
+
console.log(html)
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
Correct:
|
|
304
|
+
|
|
305
|
+
```ts
|
|
306
|
+
import { renderHtml } from '@tanstack/markdown/html'
|
|
307
|
+
import { parseMarkdown } from '@tanstack/markdown/parser'
|
|
308
|
+
|
|
309
|
+
const source = `[Guide][guide]
|
|
310
|
+
|
|
311
|
+
[guide]: /guide`
|
|
312
|
+
|
|
313
|
+
const document = parseMarkdown(source)
|
|
314
|
+
const html = renderHtml(document)
|
|
315
|
+
|
|
316
|
+
console.log(html)
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
Standalone inline parsing does not extract document-level reference or
|
|
320
|
+
footnote definitions unless internal parser state is supplied explicitly.
|
|
321
|
+
|
|
322
|
+
Source: `docs/reference/default-entry.md`
|
|
323
|
+
|
|
324
|
+
## Related Skills
|
|
325
|
+
|
|
326
|
+
- `production-pipelines/SKILL.md` - trust boundaries, syntax highlighting,
|
|
327
|
+
practical compatibility, caching, tests, performance, and bundle budgets.
|
|
328
|
+
- `react-rendering/SKILL.md` - render the same source or AST as React nodes.
|
|
329
|
+
- `octane-rendering/SKILL.md` - render the same source or AST as Octane nodes.
|
|
330
|
+
- `custom-extensions/SKILL.md` - add deterministic parser and renderer hooks.
|
|
331
|
+
- `docs-features/SKILL.md` - use the first-party documentation extensions.
|
|
332
|
+
|
|
333
|
+
## References
|
|
334
|
+
|
|
335
|
+
- [AST nodes, parser state, and render options](references/ast-and-options.md)
|