@tanstack/markdown 0.0.10 → 0.0.12
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 +4 -0
- 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/html.js +1 -1
- package/dist/octane.js +1 -1
- package/dist/react.d.ts +6 -1
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +1 -1
- package/package.json +30 -20
- package/skills/custom-extensions/SKILL.md +1 -1
- package/skills/docs-features/SKILL.md +10 -17
- package/skills/octane-rendering/SKILL.md +1 -1
- package/skills/production-pipelines/SKILL.md +9 -17
- package/skills/react-rendering/SKILL.md +20 -17
- package/skills/render-markdown/SKILL.md +1 -1
- package/skills/render-markdown/references/ast-and-options.md +1 -1
package/README.md
CHANGED
|
@@ -10,6 +10,7 @@ A tiny, fast, deterministic Markdown parser and renderer for blogs and documenta
|
|
|
10
10
|
- serializable AST
|
|
11
11
|
- safe defaults for raw HTML and executable URLs
|
|
12
12
|
- optional docs extensions and external syntax highlighting
|
|
13
|
+
- optional AI streaming profile
|
|
13
14
|
|
|
14
15
|
```bash
|
|
15
16
|
pnpm add @tanstack/markdown
|
|
@@ -41,6 +42,8 @@ export function Article({ source }: { source: string }) @{
|
|
|
41
42
|
|
|
42
43
|
TanStack Markdown targets controlled technical content. It supports the Markdown used by blogs and docs, then spends its remaining complexity budget on deterministic output, renderer parity, malformed-input resilience, and small entry points. It is intentionally not a complete CommonMark, GFM, MDX, or general content-processing implementation.
|
|
43
44
|
|
|
45
|
+
For syntax highlighting, use the tested [TanStack Highlight adapter](./docs/guides/syntax-highlighting.md#tanstack-highlight-adapter). It registers only the languages you choose and returns escaped token markup inside Markdown-owned code containers.
|
|
46
|
+
|
|
44
47
|
## Documentation
|
|
45
48
|
|
|
46
49
|
- [Overview](./docs/overview.md)
|
|
@@ -50,6 +53,7 @@ TanStack Markdown targets controlled technical content. It supports the Markdown
|
|
|
50
53
|
- [Syntax Profile](./docs/core-concepts/syntax-profile.md)
|
|
51
54
|
- [React Guide](./docs/guides/react.md)
|
|
52
55
|
- [Octane Guide](./docs/guides/octane.md)
|
|
56
|
+
- [AI Streaming](./docs/guides/ai-streaming.md)
|
|
53
57
|
- [Syntax Highlighting](./docs/guides/syntax-highlighting.md)
|
|
54
58
|
- [Extensions](./docs/guides/extensions.md)
|
|
55
59
|
- [API Reference](./docs/reference/index.md)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"streaming.d.ts","sourceRoot":"","sources":["../../src/extensions/streaming.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAA2B,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAE7E,wBAAgB,0BAA0B,IAAI,iBAAiB,CAQ9D"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export function streamingMarkdownExtension() {
|
|
2
|
+
return {
|
|
3
|
+
name: 'streaming',
|
|
4
|
+
transformDocument(document) {
|
|
5
|
+
const children = trimTrailingPlaceholder(document.children);
|
|
6
|
+
return children === document.children ? document : { ...document, children };
|
|
7
|
+
},
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
function trimTrailingPlaceholder(children) {
|
|
11
|
+
const last = children.at(-1);
|
|
12
|
+
if (!last)
|
|
13
|
+
return children;
|
|
14
|
+
const trimmed = trimBlock(last);
|
|
15
|
+
if (trimmed === last)
|
|
16
|
+
return children;
|
|
17
|
+
if (!trimmed)
|
|
18
|
+
return children.slice(0, -1);
|
|
19
|
+
return [...children.slice(0, -1), trimmed];
|
|
20
|
+
}
|
|
21
|
+
function trimBlock(node) {
|
|
22
|
+
if (node.type === 'heading' && node.children.length === 0)
|
|
23
|
+
return undefined;
|
|
24
|
+
if (node.type === 'blockquote') {
|
|
25
|
+
const children = trimTrailingPlaceholder(node.children);
|
|
26
|
+
if (children.length === 0)
|
|
27
|
+
return undefined;
|
|
28
|
+
return children === node.children ? node : { ...node, children };
|
|
29
|
+
}
|
|
30
|
+
if (node.type !== 'list')
|
|
31
|
+
return node;
|
|
32
|
+
const last = node.items.at(-1);
|
|
33
|
+
if (!last)
|
|
34
|
+
return undefined;
|
|
35
|
+
const item = trimListItem(last);
|
|
36
|
+
if (item === last)
|
|
37
|
+
return node;
|
|
38
|
+
const items = item ? [...node.items.slice(0, -1), item] : node.items.slice(0, -1);
|
|
39
|
+
return items.length === 0 ? undefined : { ...node, items };
|
|
40
|
+
}
|
|
41
|
+
function trimListItem(item) {
|
|
42
|
+
const children = trimTrailingPlaceholder(item.children);
|
|
43
|
+
if (children.length === 0)
|
|
44
|
+
return undefined;
|
|
45
|
+
return children === item.children ? item : { ...item, children };
|
|
46
|
+
}
|
package/dist/html.js
CHANGED
|
@@ -83,7 +83,7 @@ function renderInlines(nodes, options) {
|
|
|
83
83
|
function renderCodeBlock(node, options) {
|
|
84
84
|
const lang = node.lang ?? 'plaintext';
|
|
85
85
|
const preAttrs = [
|
|
86
|
-
|
|
86
|
+
`class="tm-code${options.codeLineNumbers ? ' tm-code--line-numbers' : ''}"`,
|
|
87
87
|
`data-lang="${escapeAttr(lang)}"`,
|
|
88
88
|
node.title ? `data-code-title="${escapeAttr(node.title)}"` : '',
|
|
89
89
|
node.file ? `data-filename="${escapeAttr(node.file)}"` : '',
|
package/dist/octane.js
CHANGED
|
@@ -88,7 +88,7 @@ function renderCodeBlockOctane(node, options, key) {
|
|
|
88
88
|
}
|
|
89
89
|
: undefined;
|
|
90
90
|
const pre = h(options, 'pre', {
|
|
91
|
-
className: 'tm-code'
|
|
91
|
+
className: `tm-code${options.codeLineNumbers ? ' tm-code--line-numbers' : ''}`,
|
|
92
92
|
'data-lang': lang,
|
|
93
93
|
...(node.title ? { 'data-code-title': node.title } : {}),
|
|
94
94
|
...(node.file ? { 'data-filename': node.file } : {}),
|
package/dist/react.d.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
import type { ComponentType, ReactElement, ReactNode } from 'react';
|
|
1
|
+
import type { ComponentPropsWithoutRef, ComponentType, JSX, ReactElement, ReactNode } from 'react';
|
|
2
2
|
import type { BlockNode, InlineNode, MarkdownInput, RenderOptions } from './types.js';
|
|
3
|
+
type IntrinsicElementName = keyof JSX.IntrinsicElements;
|
|
3
4
|
type ComponentMap = Partial<Record<string, string | ComponentType<any>>>;
|
|
5
|
+
export type MarkdownComponentProps<TagName extends IntrinsicElementName> = ComponentPropsWithoutRef<TagName>;
|
|
6
|
+
export type MarkdownComponents = Partial<{
|
|
7
|
+
[TagName in IntrinsicElementName]: IntrinsicElementName | ComponentType<MarkdownComponentProps<TagName>>;
|
|
8
|
+
}> & Record<string, string | ComponentType<any> | undefined>;
|
|
4
9
|
export interface MarkdownReactOptions extends RenderOptions {
|
|
5
10
|
components?: ComponentMap;
|
|
6
11
|
}
|
package/dist/react.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,wBAAwB,EAAE,aAAa,EAAE,GAAG,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAElG,OAAO,KAAK,EAAE,SAAS,EAAmC,UAAU,EAAE,aAAa,EAAE,aAAa,EAAiB,MAAM,YAAY,CAAA;AAErI,KAAK,oBAAoB,GAAG,MAAM,GAAG,CAAC,iBAAiB,CAAA;AACvD,KAAK,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;AAExE,MAAM,MAAM,sBAAsB,CAAC,OAAO,SAAS,oBAAoB,IAAI,wBAAwB,CAAC,OAAO,CAAC,CAAA;AAE5G,MAAM,MAAM,kBAAkB,GAAG,OAAO,CAAC;KACtC,OAAO,IAAI,oBAAoB,GAAG,oBAAoB,GAAG,aAAa,CAAC,sBAAsB,CAAC,OAAO,CAAC,CAAC;CACzG,CAAC,GACA,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC,CAAA;AAEzD,MAAM,WAAW,oBAAqB,SAAQ,aAAa;IACzD,UAAU,CAAC,EAAE,YAAY,CAAA;CAC1B;AAED,MAAM,WAAW,aAAc,SAAQ,oBAAoB;IACzD,QAAQ,EAAE,aAAa,CAAA;CACxB;AAED,wBAAgB,QAAQ,CAAC,EAAE,QAAQ,EAAE,GAAG,OAAO,EAAE,EAAE,aAAa,GAAG,YAAY,CAE9E;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,aAAa,EAAE,OAAO,GAAE,oBAAyB,GAAG,SAAS,CAGvG;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,GAAE,oBAAyB,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,YAAY,CA+DhH;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,UAAU,EAAE,OAAO,GAAE,oBAAyB,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAwC/G"}
|
package/dist/react.js
CHANGED
|
@@ -91,7 +91,7 @@ function renderCodeBlockReact(node, options, key) {
|
|
|
91
91
|
}
|
|
92
92
|
: undefined;
|
|
93
93
|
const pre = h(options, 'pre', {
|
|
94
|
-
className: 'tm-code'
|
|
94
|
+
className: `tm-code${options.codeLineNumbers ? ' tm-code--line-numbers' : ''}`,
|
|
95
95
|
'data-lang': lang,
|
|
96
96
|
...(node.title ? { 'data-code-title': node.title } : {}),
|
|
97
97
|
...(node.file ? { 'data-filename': node.file } : {}),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/markdown",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.12",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A tiny, fast, deterministic Markdown renderer for blogs and documentation.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"bugs": {
|
|
13
13
|
"url": "https://github.com/TanStack/markdown/issues"
|
|
14
14
|
},
|
|
15
|
+
"packageManager": "pnpm@11.4.0",
|
|
15
16
|
"sideEffects": false,
|
|
16
17
|
"files": [
|
|
17
18
|
"dist",
|
|
@@ -61,11 +62,36 @@
|
|
|
61
62
|
"types": "./dist/extensions/headings.d.ts",
|
|
62
63
|
"import": "./dist/extensions/headings.js"
|
|
63
64
|
},
|
|
65
|
+
"./extensions/streaming": {
|
|
66
|
+
"types": "./dist/extensions/streaming.d.ts",
|
|
67
|
+
"import": "./dist/extensions/streaming.js"
|
|
68
|
+
},
|
|
64
69
|
"./extensions/tabs": {
|
|
65
70
|
"types": "./dist/extensions/tabs.d.ts",
|
|
66
71
|
"import": "./dist/extensions/tabs.js"
|
|
67
72
|
}
|
|
68
73
|
},
|
|
74
|
+
"scripts": {
|
|
75
|
+
"build": "rm -rf dist && tsc -p tsconfig.build.json",
|
|
76
|
+
"typecheck": "tsc --noEmit",
|
|
77
|
+
"test": "vitest run",
|
|
78
|
+
"test:corpus": "vitest run tests/corpus.test.tsx",
|
|
79
|
+
"corpus:audit": "tsx scripts/audit-corpus.ts",
|
|
80
|
+
"corpus:audit:tanstack": "tsx scripts/audit-tanstack-corpus.ts",
|
|
81
|
+
"corpus:audit:external": "tsx scripts/audit-external-corpus.ts",
|
|
82
|
+
"docs:verify": "node scripts/verify-docs.mjs",
|
|
83
|
+
"test:skills": "intent validate skills --check && pnpm run skills:check-version",
|
|
84
|
+
"skills:check-version": "node scripts/sync-skill-version.mjs --check",
|
|
85
|
+
"skills:sync-version": "node scripts/sync-skill-version.mjs",
|
|
86
|
+
"skills:stale": "intent stale",
|
|
87
|
+
"conformance": "tsx scripts/conformance.ts",
|
|
88
|
+
"bench": "tsx scripts/bench.ts",
|
|
89
|
+
"size": "tsx scripts/measure-size.ts",
|
|
90
|
+
"research": "pnpm run conformance && pnpm run size && pnpm run bench",
|
|
91
|
+
"verify": "pnpm test && pnpm run typecheck && pnpm run build && pnpm run docs:verify && pnpm run test:skills && pnpm run research && pnpm pack --dry-run",
|
|
92
|
+
"prepublishOnly": "pnpm run verify",
|
|
93
|
+
"version": "pnpm run skills:sync-version"
|
|
94
|
+
},
|
|
69
95
|
"publishConfig": {
|
|
70
96
|
"access": "public"
|
|
71
97
|
},
|
|
@@ -82,6 +108,7 @@
|
|
|
82
108
|
}
|
|
83
109
|
},
|
|
84
110
|
"devDependencies": {
|
|
111
|
+
"@tanstack/highlight": "0.0.6",
|
|
85
112
|
"@tanstack/intent": "^0.3.6",
|
|
86
113
|
"@types/node": "^24.0.0",
|
|
87
114
|
"@types/react": "^19.0.0",
|
|
@@ -106,22 +133,5 @@
|
|
|
106
133
|
},
|
|
107
134
|
"keywords": [
|
|
108
135
|
"tanstack-intent"
|
|
109
|
-
]
|
|
110
|
-
|
|
111
|
-
"build": "rm -rf dist && tsc -p tsconfig.build.json",
|
|
112
|
-
"typecheck": "tsc --noEmit",
|
|
113
|
-
"test": "vitest run",
|
|
114
|
-
"test:corpus": "vitest run tests/corpus.test.tsx",
|
|
115
|
-
"corpus:audit": "tsx scripts/audit-corpus.ts",
|
|
116
|
-
"corpus:audit:tanstack": "tsx scripts/audit-tanstack-corpus.ts",
|
|
117
|
-
"corpus:audit:external": "tsx scripts/audit-external-corpus.ts",
|
|
118
|
-
"docs:verify": "node scripts/verify-docs.mjs",
|
|
119
|
-
"test:skills": "intent validate skills --check",
|
|
120
|
-
"skills:stale": "intent stale",
|
|
121
|
-
"conformance": "tsx scripts/conformance.ts",
|
|
122
|
-
"bench": "tsx scripts/bench.ts",
|
|
123
|
-
"size": "tsx scripts/measure-size.ts",
|
|
124
|
-
"research": "pnpm run conformance && pnpm run size && pnpm run bench",
|
|
125
|
-
"verify": "pnpm test && pnpm run typecheck && pnpm run build && pnpm run docs:verify && pnpm run test:skills && pnpm run research && pnpm pack --dry-run"
|
|
126
|
-
}
|
|
127
|
-
}
|
|
136
|
+
]
|
|
137
|
+
}
|
|
@@ -9,7 +9,7 @@ description: >
|
|
|
9
9
|
metadata:
|
|
10
10
|
type: core
|
|
11
11
|
library: '@tanstack/markdown'
|
|
12
|
-
library_version: '0.0.
|
|
12
|
+
library_version: '0.0.12'
|
|
13
13
|
requires:
|
|
14
14
|
- 'render-markdown'
|
|
15
15
|
sources:
|
|
@@ -392,11 +392,10 @@ export const html = renderHtml(source)
|
|
|
392
392
|
Correct:
|
|
393
393
|
|
|
394
394
|
````ts
|
|
395
|
-
import {
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
} from '@tanstack/highlight'
|
|
395
|
+
import { createHighlighter } from '@tanstack/highlight/core'
|
|
396
|
+
import { plaintext } from '@tanstack/highlight/languages/plaintext'
|
|
397
|
+
import { ts } from '@tanstack/highlight/languages/ts'
|
|
398
|
+
import { createTanStackMarkdownHighlighter } from '@tanstack/highlight/markdown'
|
|
400
399
|
import { renderHtml } from '@tanstack/markdown/html'
|
|
401
400
|
|
|
402
401
|
const source = `\`\`\`ts file="app.ts" {2}
|
|
@@ -404,18 +403,12 @@ const one = 1
|
|
|
404
403
|
const two = 2
|
|
405
404
|
\`\`\``
|
|
406
405
|
|
|
406
|
+
const highlighter = createHighlighter({
|
|
407
|
+
languages: [plaintext, ts],
|
|
408
|
+
})
|
|
409
|
+
|
|
407
410
|
export const html = renderHtml(source, {
|
|
408
|
-
highlighter(
|
|
409
|
-
const result = tokenize(code, { lang })
|
|
410
|
-
return renderNodesToHtml(
|
|
411
|
-
renderTokens(result.tokens, {
|
|
412
|
-
decorations: options?.highlightLines?.map((line) => ({
|
|
413
|
-
lines: line,
|
|
414
|
-
className: 'is-highlighted',
|
|
415
|
-
})),
|
|
416
|
-
}),
|
|
417
|
-
)
|
|
418
|
-
},
|
|
411
|
+
highlighter: createTanStackMarkdownHighlighter(highlighter),
|
|
419
412
|
})
|
|
420
413
|
````
|
|
421
414
|
|
|
@@ -8,7 +8,7 @@ description: >
|
|
|
8
8
|
metadata:
|
|
9
9
|
type: lifecycle
|
|
10
10
|
library: '@tanstack/markdown'
|
|
11
|
-
library_version: '0.0.
|
|
11
|
+
library_version: '0.0.12'
|
|
12
12
|
requires:
|
|
13
13
|
- 'render-markdown'
|
|
14
14
|
sources:
|
|
@@ -58,23 +58,15 @@ Fix: Separate trusted and untrusted entry points, keep trusted callbacks disable
|
|
|
58
58
|
### Check: Return trusted code contents only
|
|
59
59
|
|
|
60
60
|
Expected:
|
|
61
|
-
|
|
62
61
|
```ts
|
|
63
62
|
import { renderHtml } from '@tanstack/markdown/html'
|
|
64
|
-
import {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
export const html = renderHtml(source, {
|
|
73
|
-
highlighter(code, lang, options) {
|
|
74
|
-
const result = tokenize(code, { lang: lang ?? 'plaintext' })
|
|
75
|
-
const decorations = options?.highlightLines?.map((lines) => ({ lines, className: 'is-highlighted' }))
|
|
76
|
-
return renderNodesToHtml(renderTokens(result.tokens, { lineNumbers: options?.lineNumbers, decorations }))
|
|
77
|
-
},
|
|
63
|
+
import { createHighlighter } from '@tanstack/highlight/core'
|
|
64
|
+
import { plaintext } from '@tanstack/highlight/languages/plaintext'
|
|
65
|
+
import { ts } from '@tanstack/highlight/languages/ts'
|
|
66
|
+
import { createTanStackMarkdownHighlighter } from '@tanstack/highlight/markdown'
|
|
67
|
+
const highlighter = createHighlighter({ languages: [plaintext, ts] })
|
|
68
|
+
export const html = renderHtml('```ts {1}\nconst answer = 42\n```', {
|
|
69
|
+
highlighter: createTanStackMarkdownHighlighter(highlighter),
|
|
78
70
|
})
|
|
79
71
|
```
|
|
80
72
|
|
|
@@ -135,7 +127,7 @@ import { parseMarkdown } from '@tanstack/markdown/parser'
|
|
|
135
127
|
const cache = new Map<string, MarkdownDocument>()
|
|
136
128
|
|
|
137
129
|
export function renderCachedArticle(key: string, source: string): string {
|
|
138
|
-
const cacheKey = `markdown-0.0.
|
|
130
|
+
const cacheKey = `markdown-0.0.12:${key}`
|
|
139
131
|
let document = cache.get(cacheKey)
|
|
140
132
|
if (!document) {
|
|
141
133
|
document = parseMarkdown(source, {
|
|
@@ -9,7 +9,7 @@ metadata:
|
|
|
9
9
|
type: framework
|
|
10
10
|
library: '@tanstack/markdown'
|
|
11
11
|
framework: 'react'
|
|
12
|
-
library_version: '0.0.
|
|
12
|
+
library_version: '0.0.12'
|
|
13
13
|
requires:
|
|
14
14
|
- 'render-markdown'
|
|
15
15
|
sources:
|
|
@@ -48,31 +48,34 @@ export function Article({ source }: { source: string }) {
|
|
|
48
48
|
### Replace emitted links with an application component
|
|
49
49
|
|
|
50
50
|
```tsx
|
|
51
|
-
import
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
51
|
+
import {
|
|
52
|
+
Markdown,
|
|
53
|
+
type MarkdownComponents,
|
|
54
|
+
} from '@tanstack/markdown/react'
|
|
55
|
+
|
|
56
|
+
const components = {
|
|
57
|
+
a(props) {
|
|
58
|
+
const external = props.href?.startsWith('http') ?? false
|
|
59
|
+
return (
|
|
60
|
+
<a
|
|
61
|
+
{...props}
|
|
62
|
+
rel={external ? 'nofollow noopener noreferrer' : props.rel}
|
|
63
|
+
target={external ? '_blank' : props.target}
|
|
64
|
+
/>
|
|
65
|
+
)
|
|
66
|
+
},
|
|
67
|
+
} satisfies MarkdownComponents
|
|
65
68
|
|
|
66
69
|
export function Article({ source }: { source: string }) {
|
|
67
70
|
return (
|
|
68
71
|
<article>
|
|
69
|
-
<Markdown components={
|
|
72
|
+
<Markdown components={components}>{source}</Markdown>
|
|
70
73
|
</article>
|
|
71
74
|
)
|
|
72
75
|
}
|
|
73
76
|
```
|
|
74
77
|
|
|
75
|
-
|
|
78
|
+
Known intrinsic keys infer their matching React props. Arbitrary extension component tag names remain supported.
|
|
76
79
|
|
|
77
80
|
### Parse once before repeated React rendering
|
|
78
81
|
|