@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 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,3 @@
1
+ import type { MarkdownExtension } from '../types.js';
2
+ export declare function streamingMarkdownExtension(): MarkdownExtension;
3
+ //# sourceMappingURL=streaming.d.ts.map
@@ -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
- 'class="tm-code"',
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
  }
@@ -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;AAEnE,OAAO,KAAK,EAAE,SAAS,EAAmC,UAAU,EAAE,aAAa,EAAE,aAAa,EAAiB,MAAM,YAAY,CAAA;AAErI,KAAK,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;AAExE,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"}
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.10",
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
- "scripts": {
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
+ }
@@ -7,7 +7,7 @@ description: >
7
7
  metadata:
8
8
  type: core
9
9
  library: '@tanstack/markdown'
10
- library_version: '0.0.10'
10
+ library_version: '0.0.12'
11
11
  requires:
12
12
  - 'render-markdown'
13
13
  sources:
@@ -9,7 +9,7 @@ description: >
9
9
  metadata:
10
10
  type: core
11
11
  library: '@tanstack/markdown'
12
- library_version: '0.0.10'
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
- renderNodesToHtml,
397
- renderTokens,
398
- tokenize,
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(code, lang, options) {
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
 
@@ -9,7 +9,7 @@ metadata:
9
9
  type: framework
10
10
  library: '@tanstack/markdown'
11
11
  framework: 'octane'
12
- library_version: '0.0.10'
12
+ library_version: '0.0.12'
13
13
  requires:
14
14
  - 'render-markdown'
15
15
  sources:
@@ -8,7 +8,7 @@ description: >
8
8
  metadata:
9
9
  type: lifecycle
10
10
  library: '@tanstack/markdown'
11
- library_version: '0.0.10'
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
- renderNodesToHtml,
66
- renderTokens,
67
- tokenize,
68
- } from '@tanstack/highlight'
69
-
70
- const source = '```ts {1}\nconst answer = 42\n```'
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.10:${key}`
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.10'
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 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
- }
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={{ a: ArticleLink }}>{source}</Markdown>
72
+ <Markdown components={components}>{source}</Markdown>
70
73
  </article>
71
74
  )
72
75
  }
73
76
  ```
74
77
 
75
- `components` is keyed by emitted intrinsic or custom-element tag names.
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
 
@@ -9,7 +9,7 @@ description: >
9
9
  metadata:
10
10
  type: core
11
11
  library: '@tanstack/markdown'
12
- library_version: '0.0.10'
12
+ library_version: '0.0.12'
13
13
  sources:
14
14
  - 'TanStack/markdown:docs/quick-start.md'
15
15
  - 'TanStack/markdown:docs/core-concepts/document-model.md'
@@ -1,6 +1,6 @@
1
1
  # AST and Options Reference
2
2
 
3
- This reference targets `@tanstack/markdown@0.0.10`. Shared public types are
3
+ This reference targets `@tanstack/markdown@0.0.12`. Shared public types are
4
4
  exported from `@tanstack/markdown`.
5
5
 
6
6
  ## Entry Points