@tanstack/markdown 0.0.5 → 0.0.7

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
@@ -1,36 +1,52 @@
1
1
  # TanStack Markdown
2
2
 
3
- A tiny deterministic TanStack-flavored Markdown renderer.
3
+ A tiny, fast, deterministic Markdown renderer for blogs and documentation.
4
+
5
+ The current browser bundles are 4.6 KB gzip for the parser, 6.4 KB for HTML rendering, and 6.3 KB for the React adapter. The package has no runtime dependencies and does not bundle syntax highlighting.
4
6
 
5
7
  ```bash
6
8
  pnpm add @tanstack/markdown
7
9
  ```
8
10
 
9
- ## Commands
11
+ ```ts
12
+ import { renderHtml } from '@tanstack/markdown/html'
13
+
14
+ const html = renderHtml('# Fast by default')
15
+ ```
16
+
17
+ ```tsx
18
+ import { Markdown } from '@tanstack/markdown/react'
19
+
20
+ export function Article({ source }: { source: string }) {
21
+ return <Markdown>{source}</Markdown>
22
+ }
23
+ ```
24
+
25
+ Raw HTML is escaped and executable link protocols are removed by default. `allowHtml` and highlighter output are explicit trusted-content boundaries.
26
+
27
+ ## Scope
28
+
29
+ This is deliberately not a complete CommonMark, GFM, MDX, or content-processing implementation. It implements the syntax used by technical blogs and documentation, then spends its complexity budget on deterministic output, safe defaults, React/HTML parity, malformed-input resilience, and small entry points.
30
+
31
+ See the [docs Markdown profile](./docs/profile.md) for the supported contract and deliberate non-goals.
32
+
33
+ ## Verification
10
34
 
11
35
  ```bash
12
- pnpm test
13
- pnpm run typecheck
14
- pnpm run build
15
- pnpm run research
36
+ pnpm run verify
16
37
  ```
17
38
 
18
- ## Entry Points
39
+ To include downstream repositories in the corpus gate:
19
40
 
20
- - `src/parser.ts`: Markdown subset parser to a serializable AST
21
- - `src/html.ts`: HTML string renderer
22
- - `src/react.ts`: React adapter
23
- - `src/extensions/*`: optional docs-site extension functions
24
- - `scripts/measure-size.ts`: bundle size matrix
25
- - `scripts/bench.ts`: CPU benchmark matrix
41
+ ```bash
42
+ MARKDOWN_CORPUS_DIRS=../tanstack.com/src/blog:../tanstack.com/docs pnpm run test:corpus
43
+ ```
26
44
 
27
45
  ## Reports
28
46
 
29
47
  - [Bundle sizes](./reports/sizes.md)
30
48
  - [Benchmarks](./reports/benchmarks.md)
49
+ - [CommonMark compatibility accounting](./reports/conformance.md)
31
50
  - [Audit](./docs/audit.md)
32
51
  - [Spec and API](./docs/spec.md)
33
- - [Goal and coverage map](./docs/goal.md)
34
- - [Bundle strategy](./docs/bundle-strategy.md)
35
52
  - [Extensions](./docs/extensions.md)
36
- - [Recommendation](./docs/recommendation.md)
@@ -1 +1 @@
1
- {"version":3,"file":"html.d.ts","sourceRoot":"","sources":["../src/html.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAyD,UAAU,EAAE,gBAAgB,EAAE,aAAa,EAAE,aAAa,EAAiB,MAAM,YAAY,CAAA;AAG7K,wBAAgB,UAAU,CAAC,KAAK,EAAE,aAAa,EAAE,OAAO,GAAE,aAAkB,GAAG,MAAM,CAGpF;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,GAAE,aAAkB,GAAG,MAAM,CAwChF;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,UAAU,EAAE,OAAO,GAAE,aAAkB,GAAG,MAAM,CA0BlF;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,gBAAgB,EAAE,OAAO,GAAE,aAAkB,GAAG,MAAM,CAE9F"}
1
+ {"version":3,"file":"html.d.ts","sourceRoot":"","sources":["../src/html.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAyD,UAAU,EAAE,gBAAgB,EAAE,aAAa,EAAE,aAAa,EAAiB,MAAM,YAAY,CAAA;AAG7K,wBAAgB,UAAU,CAAC,KAAK,EAAE,aAAa,EAAE,OAAO,GAAE,aAAkB,GAAG,MAAM,CAGpF;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,GAAE,aAAkB,GAAG,MAAM,CA0ChF;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,UAAU,EAAE,OAAO,GAAE,aAAkB,GAAG,MAAM,CA0BlF;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,gBAAgB,EAAE,OAAO,GAAE,aAAkB,GAAG,MAAM,CAE9F"}
package/dist/html.js CHANGED
@@ -28,9 +28,11 @@ export function renderBlock(node, options = {}) {
28
28
  return `<blockquote>\n${node.children.map(child => renderBlock(child, options)).join('\n')}\n</blockquote>`;
29
29
  case 'table': {
30
30
  const header = `<thead><tr>${node.header.map((cell, index) => renderTableCell('th', cell, node.align[index], options)).join('')}</tr></thead>`;
31
- const body = `<tbody>${node.rows
32
- .map(row => `<tr>${row.map((cell, index) => renderTableCell('td', cell, node.align[index], options)).join('')}</tr>`)
33
- .join('')}</tbody>`;
31
+ const body = node.rows.length
32
+ ? `<tbody>${node.rows
33
+ .map(row => `<tr>${row.map((cell, index) => renderTableCell('td', cell, node.align[index], options)).join('')}</tr>`)
34
+ .join('')}</tbody>`
35
+ : '';
34
36
  return `<table>${header}${body}</table>`;
35
37
  }
36
38
  case 'footnotes':
@@ -61,7 +63,7 @@ export function renderInline(node, options = {}) {
61
63
  case 'strike':
62
64
  return `<del>${renderInlines(node.children, options)}</del>`;
63
65
  case 'footnoteReference':
64
- return `<sup><a id="user-content-fnref-${escapeAttr(node.id)}" data-footnote-ref="" aria-describedby="footnote-label" href="#user-content-fn-${escapeAttr(node.id)}">${node.number}</a></sup>`;
66
+ return `<sup><a id="user-content-fnref-${escapeAttr(footnoteReferenceId(node))}" data-footnote-ref="" aria-describedby="footnote-label" href="#user-content-fn-${escapeAttr(node.id)}">${node.number}</a></sup>`;
65
67
  case 'link':
66
68
  return `<a href="${escapeAttr(node.href)}"${node.title ? ` title="${escapeAttr(node.title)}"` : ''}>${renderInlines(node.children, options)}</a>`;
67
69
  case 'image':
@@ -101,8 +103,11 @@ function renderCodeBlock(node, options) {
101
103
  function renderListItemChildren(children, checked, loose, options) {
102
104
  const first = children[0];
103
105
  const task = checked === undefined ? '' : `<input type="checkbox" disabled${checked ? ' checked' : ''}> `;
104
- if ((checked !== undefined || !loose) && first?.type === 'paragraph')
105
- return `${task}${renderInlines(first.children, options)}${children.length > 1 ? `\n${children.slice(1).map(child => renderBlock(child, options)).join('\n')}` : ''}`;
106
+ if (first?.type === 'paragraph') {
107
+ const content = `${task}${renderInlines(first.children, options)}`;
108
+ const rest = children.length > 1 ? `\n${children.slice(1).map(child => renderBlock(child, options)).join('\n')}` : '';
109
+ return `${loose ? `<p>${content}</p>` : content}${rest}`;
110
+ }
106
111
  return `${task}${children.map(child => renderBlock(child, options)).join('\n')}`;
107
112
  }
108
113
  function renderCallout(node, options) {
@@ -115,7 +120,7 @@ function renderFootnotes(items, options) {
115
120
  return `<section data-footnotes="" class="footnotes"><h2 id="footnote-label" class="sr-only">Footnotes${renderHeadingAnchor('footnote-label', options)}</h2>\n<ol>\n${renderedItems}\n</ol>\n</section>`;
116
121
  }
117
122
  function renderFootnoteItem(item, options) {
118
- const backref = ` <a data-footnote-backref="" aria-label="Back to reference ${item.number}" class="data-footnote-backref" href="#user-content-fnref-${escapeAttr(item.id)}">&#8617;</a>`;
123
+ const backref = renderFootnoteBackrefs(item);
119
124
  const lastIndex = item.children.length - 1;
120
125
  if (lastIndex < 0)
121
126
  return `<p>${backref.trimStart()}</p>`;
@@ -128,6 +133,18 @@ function renderFootnoteItem(item, options) {
128
133
  })
129
134
  .join('\n');
130
135
  }
136
+ function renderFootnoteBackrefs(item) {
137
+ let result = '';
138
+ for (let index = 1; index <= (item.referenceCount ?? 1); index++) {
139
+ const referenceId = index === 1 ? item.id : `${item.id}-${index}`;
140
+ const label = index === 1 ? `${item.number}` : `${item.number}-${index}`;
141
+ result += ` <a data-footnote-backref="" aria-label="Back to reference ${label}" class="data-footnote-backref" href="#user-content-fnref-${escapeAttr(referenceId)}">&#8617;</a>`;
142
+ }
143
+ return result;
144
+ }
145
+ function footnoteReferenceId(node) {
146
+ return node.referenceIndex && node.referenceIndex > 1 ? `${node.id}-${node.referenceIndex}` : node.id;
147
+ }
131
148
  function renderComponent(node, options) {
132
149
  const tag = node.tagName ?? 'md-comment-component';
133
150
  const attrs = renderComponentAttrs(node);
package/dist/inline.js CHANGED
@@ -7,7 +7,12 @@ export function parseInline(value, options = {}) {
7
7
  }
8
8
  return result;
9
9
  }
10
- function parseInlineRaw(value, options) {
10
+ const maxInlineDepth = 32;
11
+ const scansPerCharacter = 16;
12
+ function parseInlineRaw(value, options, budget = { scans: Math.max(value.length * scansPerCharacter, 1024), depth: 0 }) {
13
+ if (budget.depth >= maxInlineDepth)
14
+ return value ? [{ type: 'text', value }] : [];
15
+ budget.depth++;
11
16
  const nodes = [];
12
17
  let index = 0;
13
18
  let text = '';
@@ -35,7 +40,7 @@ function parseInlineRaw(value, options) {
35
40
  }
36
41
  if (char === '`') {
37
42
  const tickCount = countRun(value, index, '`');
38
- const close = findClosingRun(value, index + tickCount, '`', tickCount);
43
+ const close = findClosingRun(value, index + tickCount, '`', tickCount, budget);
39
44
  if (close !== -1) {
40
45
  pushText();
41
46
  nodes.push({
@@ -45,15 +50,18 @@ function parseInlineRaw(value, options) {
45
50
  index = close + tickCount;
46
51
  continue;
47
52
  }
53
+ text += value.slice(index, index + tickCount);
54
+ index += tickCount;
55
+ continue;
48
56
  }
49
57
  if (char === '!' && next === '[') {
50
- const parsed = parseLinkish(value, index + 1, options);
58
+ const parsed = parseLinkish(value, index + 1, options, budget);
51
59
  if (parsed) {
52
60
  pushText();
53
61
  nodes.push({
54
62
  type: 'image',
55
63
  src: sanitizeUrl(parsed.href),
56
- alt: textFromMarkdown(parsed.label),
64
+ alt: textFromMarkdown(parsed.label, budget),
57
65
  ...(parsed.title ? { title: parsed.title } : {}),
58
66
  });
59
67
  index = parsed.end;
@@ -61,14 +69,14 @@ function parseInlineRaw(value, options) {
61
69
  }
62
70
  }
63
71
  if (char === '[') {
64
- const footnote = parseFootnoteReference(value, index, options);
72
+ const footnote = parseFootnoteReference(value, index, options, budget);
65
73
  if (footnote) {
66
74
  pushText();
67
75
  nodes.push(footnote.node);
68
76
  index = footnote.end;
69
77
  continue;
70
78
  }
71
- const parsed = parseLinkish(value, index, options);
79
+ const parsed = parseLinkish(value, index, options, budget);
72
80
  if (parsed) {
73
81
  const href = sanitizeUrl(parsed.href);
74
82
  pushText();
@@ -77,23 +85,23 @@ function parseInlineRaw(value, options) {
77
85
  type: 'link',
78
86
  href,
79
87
  ...(parsed.title ? { title: parsed.title } : {}),
80
- children: parseInlineRaw(parsed.label, options),
88
+ children: parseInlineRaw(parsed.label, options, budget),
81
89
  });
82
90
  }
83
91
  else {
84
- nodes.push(...parseInlineRaw(parsed.label, options));
92
+ nodes.push(...parseInlineRaw(parsed.label, options, budget));
85
93
  }
86
94
  index = parsed.end;
87
95
  continue;
88
96
  }
89
97
  }
90
98
  if ((char === '*' && next === '*') || (char === '_' && next === '_')) {
91
- const close = findDelimiter(value, index + 2, char + char);
99
+ const close = findDelimiter(value, index + 2, char + char, budget);
92
100
  if (close !== -1) {
93
101
  pushText();
94
102
  nodes.push({
95
103
  type: 'strong',
96
- children: parseInlineRaw(value.slice(index + 2, close), options),
104
+ children: parseInlineRaw(value.slice(index + 2, close), options, budget),
97
105
  });
98
106
  index = close + 2;
99
107
  continue;
@@ -102,13 +110,19 @@ function parseInlineRaw(value, options) {
102
110
  index += 2;
103
111
  continue;
104
112
  }
113
+ if (char === '~' && next === '~' && value[index + 2] === '~') {
114
+ const run = countRun(value, index, '~');
115
+ text += value.slice(index, index + run);
116
+ index += run;
117
+ continue;
118
+ }
105
119
  if (char === '~' && next === '~') {
106
- const close = findDelimiter(value, index + 2, '~~');
120
+ const close = findDelimiter(value, index + 2, '~~', budget);
107
121
  if (close !== -1) {
108
122
  pushText();
109
123
  nodes.push({
110
124
  type: 'strike',
111
- children: parseInlineRaw(value.slice(index + 2, close), options),
125
+ children: parseInlineRaw(value.slice(index + 2, close), options, budget),
112
126
  });
113
127
  index = close + 2;
114
128
  continue;
@@ -118,31 +132,31 @@ function parseInlineRaw(value, options) {
118
132
  continue;
119
133
  }
120
134
  if (char === '~') {
121
- const close = findSingleTildeDelimiter(value, index + 1);
135
+ const close = findSingleTildeDelimiter(value, index + 1, budget);
122
136
  if (close !== -1) {
123
137
  pushText();
124
138
  nodes.push({
125
139
  type: 'strike',
126
- children: parseInlineRaw(value.slice(index + 1, close), options),
140
+ children: parseInlineRaw(value.slice(index + 1, close), options, budget),
127
141
  });
128
142
  index = close + 1;
129
143
  continue;
130
144
  }
131
145
  }
132
146
  if (char === '*' || char === '_') {
133
- const close = findDelimiter(value, index + 1, char);
147
+ const close = findDelimiter(value, index + 1, char, budget);
134
148
  if (close !== -1 && !isIntrawordUnderscore(value, index, close, char)) {
135
149
  pushText();
136
150
  nodes.push({
137
151
  type: 'emphasis',
138
- children: parseInlineRaw(value.slice(index + 1, close), options),
152
+ children: parseInlineRaw(value.slice(index + 1, close), options, budget),
139
153
  });
140
154
  index = close + 1;
141
155
  continue;
142
156
  }
143
157
  }
144
158
  if (char === '<' && options.allowHtml) {
145
- const close = value.indexOf('>', index + 1);
159
+ const close = findCharacter(value, index + 1, '>', budget);
146
160
  if (close !== -1) {
147
161
  pushText();
148
162
  nodes.push({ type: 'inlineHtml', value: value.slice(index, close + 1) });
@@ -154,12 +168,13 @@ function parseInlineRaw(value, options) {
154
168
  index++;
155
169
  }
156
170
  pushText();
171
+ budget.depth--;
157
172
  return nodes;
158
173
  }
159
- function parseFootnoteReference(value, open, options) {
174
+ function parseFootnoteReference(value, open, options, budget) {
160
175
  if (value[open + 1] !== '^')
161
176
  return undefined;
162
- const close = value.indexOf(']', open + 2);
177
+ const close = findCharacter(value, open + 2, ']', budget);
163
178
  if (close === -1)
164
179
  return undefined;
165
180
  const label = value.slice(open + 2, close);
@@ -172,22 +187,26 @@ function parseFootnoteReference(value, open, options) {
172
187
  options.footnoteOrder.push(key);
173
188
  orderIndex = options.footnoteOrder.length - 1;
174
189
  }
190
+ const referenceIndex = (options.footnoteCounts?.[key] ?? 0) + 1;
191
+ if (options.footnoteCounts)
192
+ options.footnoteCounts[key] = referenceIndex;
175
193
  return {
176
194
  node: {
177
195
  type: 'footnoteReference',
178
- id: footnoteId(definition.label),
196
+ id: definition.id ?? (footnoteId(definition.label) || 'footnote'),
179
197
  number: orderIndex + 1,
198
+ ...(referenceIndex > 1 ? { referenceIndex } : {}),
180
199
  },
181
200
  end: close + 1,
182
201
  };
183
202
  }
184
- function parseLinkish(value, open, options) {
185
- const closeBracket = findBalanced(value, open, '[', ']');
203
+ function parseLinkish(value, open, options, budget) {
204
+ const closeBracket = findBalanced(value, open, '[', ']', budget);
186
205
  if (closeBracket === -1)
187
206
  return undefined;
188
207
  const label = value.slice(open + 1, closeBracket);
189
208
  if (value[closeBracket + 1] === '(') {
190
- const closeParen = findBalanced(value, closeBracket + 1, '(', ')');
209
+ const closeParen = findBalanced(value, closeBracket + 1, '(', ')', budget);
191
210
  if (closeParen === -1)
192
211
  return undefined;
193
212
  const destination = value.slice(closeBracket + 2, closeParen).trim();
@@ -202,7 +221,7 @@ function parseLinkish(value, open, options) {
202
221
  };
203
222
  }
204
223
  if (value[closeBracket + 1] === '[') {
205
- const closeReference = findBalanced(value, closeBracket + 1, '[', ']');
224
+ const closeReference = findBalanced(value, closeBracket + 1, '[', ']', budget);
206
225
  if (closeReference === -1)
207
226
  return undefined;
208
227
  const definition = options.references?.[normalizeReferenceLabel(value.slice(closeBracket + 2, closeReference))];
@@ -226,9 +245,11 @@ function parseDestination(value) {
226
245
  ...(match[2] ? { title: match[2] } : {}),
227
246
  };
228
247
  }
229
- function findBalanced(value, openIndex, open, close) {
248
+ function findBalanced(value, openIndex, open, close, budget) {
230
249
  let depth = 0;
231
250
  for (let index = openIndex; index < value.length; index++) {
251
+ if (!takeScan(budget))
252
+ return -1;
232
253
  if (value[index - 1] === '\\')
233
254
  continue;
234
255
  if (value[index] === open)
@@ -241,38 +262,51 @@ function findBalanced(value, openIndex, open, close) {
241
262
  }
242
263
  return -1;
243
264
  }
244
- function countRun(value, index, char) {
265
+ function countRun(value, index, char, budget) {
245
266
  let count = 0;
246
- while (value[index + count] === char)
267
+ while (value[index + count] === char) {
268
+ if (budget && !takeScan(budget))
269
+ break;
247
270
  count++;
271
+ }
248
272
  return count;
249
273
  }
250
- function findClosingRun(value, start, char, count) {
274
+ function findClosingRun(value, start, char, count, budget) {
251
275
  for (let index = start; index < value.length; index++) {
276
+ if (!takeScan(budget))
277
+ return -1;
252
278
  if (value[index] !== char)
253
279
  continue;
254
- if (countRun(value, index, char) >= count)
280
+ if (countRun(value, index, char, budget) >= count)
255
281
  return index;
256
282
  }
257
283
  return -1;
258
284
  }
259
- function findDelimiter(value, start, delimiter) {
285
+ function findDelimiter(value, start, delimiter, budget) {
260
286
  for (let index = start; index < value.length; index++) {
287
+ if (!takeScan(budget))
288
+ return -1;
261
289
  if (value[index - 1] === '\\')
262
290
  continue;
291
+ if (delimiter === '~~' && (value[index - 1] === '~' || value[index + 2] === '~'))
292
+ continue;
263
293
  if (value.startsWith(delimiter, index))
264
294
  return index;
265
295
  }
266
296
  return -1;
267
297
  }
268
- function findSingleTildeDelimiter(value, start) {
298
+ function findSingleTildeDelimiter(value, start, budget) {
269
299
  if (isWhitespace(value[start] ?? ''))
270
300
  return -1;
271
301
  for (let index = start; index < value.length; index++) {
302
+ if (!takeScan(budget))
303
+ return -1;
272
304
  if (value[index - 1] === '\\')
273
305
  continue;
274
306
  if (value[index] !== '~')
275
307
  continue;
308
+ if (value[index - 1] === '~')
309
+ continue;
276
310
  if (value[index + 1] === '~')
277
311
  continue;
278
312
  if (isWhitespace(value[index - 1] ?? ''))
@@ -281,6 +315,21 @@ function findSingleTildeDelimiter(value, start) {
281
315
  }
282
316
  return -1;
283
317
  }
318
+ function findCharacter(value, start, character, budget) {
319
+ for (let index = start; index < value.length; index++) {
320
+ if (!takeScan(budget))
321
+ return -1;
322
+ if (value[index] === character)
323
+ return index;
324
+ }
325
+ return -1;
326
+ }
327
+ function takeScan(budget) {
328
+ if (budget.scans <= 0)
329
+ return false;
330
+ budget.scans--;
331
+ return true;
332
+ }
284
333
  function isIntrawordUnderscore(value, open, close, delimiter) {
285
334
  if (delimiter !== '_')
286
335
  return false;
@@ -289,8 +338,8 @@ function isIntrawordUnderscore(value, open, close, delimiter) {
289
338
  function isWhitespace(value) {
290
339
  return /\s/.test(value);
291
340
  }
292
- function textFromMarkdown(value) {
293
- return parseInlineRaw(value, {}).map(node => (node.type === 'text' || node.type === 'inlineCode' ? node.value : '')).join('');
341
+ function textFromMarkdown(value, budget) {
342
+ return parseInlineRaw(value, {}, budget).map(node => (node.type === 'text' || node.type === 'inlineCode' ? node.value : '')).join('');
294
343
  }
295
344
  function mergeText(nodes) {
296
345
  const result = [];
@@ -1 +1 @@
1
- {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAOV,gBAAgB,EAChB,YAAY,EAGb,MAAM,YAAY,CAAA;AAKnB,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAE,YAAiB,GAAG,gBAAgB,CAwC5F"}
1
+ {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAQV,gBAAgB,EAChB,YAAY,EAGb,MAAM,YAAY,CAAA;AAWnB,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAE,YAAiB,GAAG,gBAAgB,CAyC5F"}
package/dist/parser.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { parseInline } from './inline.js';
2
2
  import { createSlugger, footnoteId, isBlank, normalizeInput, normalizeReferenceLabel, plainText, stripIndent } from './utils.js';
3
+ const maxBlockDepth = 64;
3
4
  export function parseMarkdown(markdown, options = {}) {
4
5
  const normalized = normalizeInput(markdown);
5
6
  const frontmatterEnabled = options.frontmatter !== false;
@@ -15,13 +16,14 @@ export function parseMarkdown(markdown, options = {}) {
15
16
  const definitions = extractDefinitions(lines);
16
17
  lines = definitions.lines;
17
18
  const footnoteOrder = [];
19
+ const footnoteCounts = Object.create(null);
18
20
  const hasReferences = Object.keys(definitions.references).length > 0;
19
21
  const hasFootnotes = Object.keys(definitions.footnotes).length > 0;
20
22
  const parseOptions = hasReferences || hasFootnotes
21
23
  ? {
22
24
  ...options,
23
25
  ...(hasReferences ? { references: definitions.references } : {}),
24
- ...(hasFootnotes ? { footnotes: definitions.footnotes, footnoteOrder } : {}),
26
+ ...(hasFootnotes ? { footnotes: definitions.footnotes, footnoteOrder, footnoteCounts } : {}),
25
27
  }
26
28
  : options;
27
29
  const parser = new BlockParser(lines, parseOptions, createSlugger());
@@ -39,13 +41,21 @@ class BlockParser {
39
41
  lines;
40
42
  options;
41
43
  slugger;
44
+ budget;
42
45
  index = 0;
43
- constructor(lines, options, slugger) {
46
+ constructor(lines, options, slugger, budget = { depth: 0 }) {
44
47
  this.lines = lines;
45
48
  this.options = options;
46
49
  this.slugger = slugger;
50
+ this.budget = budget;
47
51
  }
48
52
  parse() {
53
+ if (this.budget.depth >= maxBlockDepth) {
54
+ const value = this.lines.slice(this.index).join('\n');
55
+ this.index = this.lines.length;
56
+ return value ? [{ type: 'paragraph', children: parseInline(value, this.options) }] : [];
57
+ }
58
+ this.budget.depth++;
49
59
  const nodes = [];
50
60
  while (this.index < this.lines.length) {
51
61
  if (isBlank(this.current())) {
@@ -67,6 +77,7 @@ class BlockParser {
67
77
  this.parseParagraph();
68
78
  nodes.push(node);
69
79
  }
80
+ this.budget.depth--;
70
81
  return nodes;
71
82
  }
72
83
  parseExtensionBlock() {
@@ -77,7 +88,7 @@ class BlockParser {
77
88
  index: this.index,
78
89
  options: this.options,
79
90
  parseInline: value => parseInline(value, this.options),
80
- parseBlocks: value => new BlockParser(normalizeInput(value).split('\n'), this.options, this.slugger).parse(),
91
+ parseBlocks: value => new BlockParser(normalizeInput(value).split('\n'), this.options, this.slugger, this.budget).parse(),
81
92
  consume: lines => {
82
93
  consumed = lines;
83
94
  },
@@ -150,7 +161,7 @@ class BlockParser {
150
161
  }
151
162
  return {
152
163
  type: 'blockquote',
153
- children: new BlockParser(quoted, this.options, this.slugger).parse(),
164
+ children: new BlockParser(quoted, this.options, this.slugger, this.budget).parse(),
154
165
  };
155
166
  }
156
167
  parseList() {
@@ -164,7 +175,7 @@ class BlockParser {
164
175
  let loose = false;
165
176
  while (this.index < this.lines.length) {
166
177
  const marker = listMarker(this.current());
167
- if (!marker || marker.ordered !== ordered || marker.indent !== baseIndent)
178
+ if (!marker || !sameListType(marker, first) || marker.indent !== baseIndent)
168
179
  break;
169
180
  let firstLine = marker.content;
170
181
  const task = firstLine.match(/^\[([ xX])\]\s+(.*)$/);
@@ -176,18 +187,32 @@ class BlockParser {
176
187
  while (this.index < this.lines.length) {
177
188
  const line = this.current();
178
189
  const nextMarker = listMarker(line);
179
- if (nextMarker && nextMarker.indent === baseIndent && nextMarker.ordered === ordered)
190
+ if (nextMarker && nextMarker.indent === baseIndent)
180
191
  break;
181
192
  if (isBlank(line)) {
182
- if (!this.next())
193
+ let nextIndex = this.index;
194
+ while (nextIndex < this.lines.length && isBlank(this.lines[nextIndex]))
195
+ nextIndex++;
196
+ const following = this.lines[nextIndex];
197
+ if (following === undefined)
198
+ break;
199
+ const followingMarker = listMarker(following);
200
+ if (followingMarker?.indent === baseIndent) {
201
+ if (!sameListType(followingMarker, first))
202
+ break;
203
+ loose = true;
204
+ this.index = nextIndex;
205
+ break;
206
+ }
207
+ if (leadingSpaces(following) < marker.contentIndent)
183
208
  break;
184
209
  loose = true;
185
210
  itemLines.push('');
186
- this.index++;
211
+ this.index = nextIndex;
187
212
  continue;
188
213
  }
189
- if (leadingSpaces(line) > baseIndent) {
190
- itemLines.push(stripIndent(line, baseIndent + 2));
214
+ if (leadingSpaces(line) >= marker.contentIndent) {
215
+ itemLines.push(stripIndent(line, marker.contentIndent));
191
216
  this.index++;
192
217
  continue;
193
218
  }
@@ -196,7 +221,7 @@ class BlockParser {
196
221
  itemLines.push(line.trimStart());
197
222
  this.index++;
198
223
  }
199
- const children = new BlockParser(itemLines, this.options, this.slugger).parse();
224
+ const children = new BlockParser(itemLines, this.options, this.slugger, this.budget).parse();
200
225
  const item = { type: 'listItem', children };
201
226
  if (checked !== undefined)
202
227
  item.checked = checked;
@@ -217,13 +242,15 @@ class BlockParser {
217
242
  return undefined;
218
243
  const headerCells = splitTableRow(header);
219
244
  const align = splitTableRow(delimiter).map(parseAlign);
245
+ const columns = headerCells.length;
220
246
  const rows = [];
221
247
  this.index += 2;
222
248
  while (this.index < this.lines.length) {
223
249
  const line = this.current();
224
- if (isBlank(line) || !line.includes('|'))
250
+ if (isBlank(line) || isBlockStart(line, this.next()))
225
251
  break;
226
- rows.push(splitTableRow(line).map(value => cell(value, this.options)));
252
+ const values = splitTableRow(line);
253
+ rows.push(Array.from({ length: columns }, (_, index) => cell(values[index] ?? '', this.options)));
227
254
  this.index++;
228
255
  }
229
256
  return {
@@ -295,8 +322,9 @@ function parseCodeInfo(info) {
295
322
  };
296
323
  }
297
324
  function extractDefinitions(lines) {
298
- const references = {};
299
- const footnotes = {};
325
+ const references = Object.create(null);
326
+ const footnotes = Object.create(null);
327
+ const footnoteIds = new Map();
300
328
  const remaining = [];
301
329
  let fenceMarker;
302
330
  let fenceSize = 0;
@@ -331,7 +359,13 @@ function extractDefinitions(lines) {
331
359
  content.push(continuation.replace(/^(?: {4}|\t)/, ''));
332
360
  index++;
333
361
  }
334
- footnotes[normalizeReferenceLabel(label)] = { label, content: content.join('\n') };
362
+ const key = normalizeReferenceLabel(label);
363
+ if (!footnotes[key]) {
364
+ const baseId = footnoteId(label) || 'footnote';
365
+ const count = (footnoteIds.get(baseId) ?? 0) + 1;
366
+ footnoteIds.set(baseId, count);
367
+ footnotes[key] = { label, content: content.join('\n'), id: count === 1 ? baseId : `${baseId}-${count}` };
368
+ }
335
369
  continue;
336
370
  }
337
371
  const definition = line.match(/^ {0,3}\[([^\]\n]+)\]:[ \t]*(\S+)[ \t]*$/);
@@ -349,15 +383,21 @@ function extractDefinitions(lines) {
349
383
  function createFootnotesBlock(footnotes, footnoteOrder, options) {
350
384
  const items = [];
351
385
  for (let index = 0; index < footnoteOrder.length; index++) {
352
- const definition = footnotes[footnoteOrder[index]];
386
+ const key = footnoteOrder[index];
387
+ const definition = footnotes[key];
353
388
  if (!definition)
354
389
  continue;
355
390
  items.push({
356
- id: footnoteId(definition.label),
391
+ id: definition.id ?? (footnoteId(definition.label) || 'footnote'),
357
392
  number: index + 1,
358
393
  children: new BlockParser(normalizeInput(definition.content).split('\n'), options, createSlugger()).parse(),
359
394
  });
360
395
  }
396
+ for (const item of items) {
397
+ const count = options.footnoteCounts?.[footnoteOrder[item.number - 1]] ?? 1;
398
+ if (count > 1)
399
+ item.referenceCount = count;
400
+ }
361
401
  return { type: 'footnotes', items };
362
402
  }
363
403
  function parseLineRanges(value) {
@@ -374,7 +414,7 @@ function parseLineRanges(value) {
374
414
  return [...lines].sort((a, b) => a - b);
375
415
  }
376
416
  function listMarker(line) {
377
- const match = line.match(/^(\s{0,8})([-+*]|\d{1,9}[.)])\s+(.*)$/);
417
+ const match = line.match(/^(\s{0,8})([-+*]|\d{1,9}[.)])(?:([ \t]+)(.*))?$/);
378
418
  if (!match)
379
419
  return undefined;
380
420
  const marker = match[2];
@@ -383,9 +423,14 @@ function listMarker(line) {
383
423
  ordered,
384
424
  ...(ordered ? { number: Number.parseInt(marker, 10) } : {}),
385
425
  indent: match[1].length,
386
- content: match[3],
426
+ marker: ordered ? marker.at(-1) : marker,
427
+ contentIndent: match[1].length + marker.length + (match[3]?.length ?? 1),
428
+ content: match[4] ?? '',
387
429
  };
388
430
  }
431
+ function sameListType(left, right) {
432
+ return left.ordered === right.ordered && left.marker === right.marker;
433
+ }
389
434
  function leadingSpaces(line) {
390
435
  return line.match(/^ */)?.[0].length ?? 0;
391
436
  }
@@ -401,7 +446,7 @@ function looksLikeTableHeader(header, delimiter) {
401
446
  if (!header.includes('|'))
402
447
  return false;
403
448
  const cells = splitTableRow(delimiter);
404
- return cells.length > 0 && cells.every(cell => /^:?-{2,}:?$/.test(cell.trim()));
449
+ return cells.length === splitTableRow(header).length && cells.every(cell => /^:?-+:?$/.test(cell.trim()));
405
450
  }
406
451
  function splitTableRow(value) {
407
452
  let row = value.trim();
@@ -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,CA6DhH;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,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"}
package/dist/react.js CHANGED
@@ -22,7 +22,9 @@ export function renderBlockReact(node, options = {}, key) {
22
22
  case 'blockquote':
23
23
  return h(options, 'blockquote', { key }, node.children.map((child, index) => renderBlockReact(child, options, `${key}:${index}`)));
24
24
  case 'table':
25
- return h(options, 'table', { key }, h(options, 'thead', null, h(options, 'tr', null, node.header.map((cell, index) => renderTableCellReact('th', cell, node.align[index], options, index)))), h(options, 'tbody', null, node.rows.map((row, rowIndex) => h(options, 'tr', { key: rowIndex }, row.map((cell, index) => renderTableCellReact('td', cell, node.align[index], options, index))))));
25
+ return h(options, 'table', { key }, h(options, 'thead', null, h(options, 'tr', null, node.header.map((cell, index) => renderTableCellReact('th', cell, node.align[index], options, index)))), node.rows.length
26
+ ? h(options, 'tbody', null, node.rows.map((row, rowIndex) => h(options, 'tr', { key: rowIndex }, row.map((cell, index) => renderTableCellReact('td', cell, node.align[index], options, index)))))
27
+ : null);
26
28
  case 'footnotes':
27
29
  return renderFootnotesReact(node.items, options, key);
28
30
  case 'thematicBreak':
@@ -51,7 +53,7 @@ export function renderInlineReact(node, options = {}, key) {
51
53
  return h(options, 'del', { key }, renderInlines(node.children, options));
52
54
  case 'footnoteReference':
53
55
  return h(options, 'sup', { key }, h(options, 'a', {
54
- id: `user-content-fnref-${node.id}`,
56
+ id: `user-content-fnref-${footnoteReferenceId(node)}`,
55
57
  'data-footnote-ref': '',
56
58
  'aria-describedby': 'footnote-label',
57
59
  href: `#user-content-fn-${node.id}`,
@@ -113,10 +115,10 @@ function renderListItemChildrenReact(children, checked, loose, options, key) {
113
115
  }),
114
116
  ' ',
115
117
  ];
116
- if (checked !== undefined && first?.type === 'paragraph') {
118
+ if (first?.type === 'paragraph') {
119
+ const content = [...task, ...renderInlines(first.children, options)];
117
120
  return [
118
- ...task,
119
- ...renderInlines(first.children, options),
121
+ ...(loose ? [h(options, 'p', { key: `${key}:paragraph` }, content)] : content),
120
122
  ...rest.flatMap((child, childIndex) => renderListChildReact(child, loose, options, `${key}:${childIndex + 1}`)),
121
123
  ];
122
124
  }
@@ -133,24 +135,33 @@ function renderFootnotesReact(items, options, key) {
133
135
  }
134
136
  function renderFootnoteItemReact(item, options) {
135
137
  const lastIndex = item.children.length - 1;
136
- const backref = renderFootnoteBackrefReact(item, options, 'backref');
138
+ const backrefs = renderFootnoteBackrefsReact(item, options);
137
139
  if (lastIndex < 0)
138
- return [h(options, 'p', { key: 'backref-wrapper' }, backref)];
140
+ return [h(options, 'p', { key: 'backref-wrapper' }, backrefs.slice(1))];
139
141
  return item.children.map((child, index) => {
140
142
  if (index === lastIndex && child.type === 'paragraph') {
141
- return h(options, 'p', { key: index }, renderInlines(child.children, options), ' ', backref);
143
+ return h(options, 'p', { key: index }, renderInlines(child.children, options), backrefs);
142
144
  }
143
145
  return renderBlockReact(child, options, `${index}`);
144
146
  });
145
147
  }
146
- function renderFootnoteBackrefReact(item, options, key) {
147
- return h(options, 'a', {
148
- key,
149
- 'data-footnote-backref': '',
150
- 'aria-label': `Back to reference ${item.number}`,
151
- className: 'data-footnote-backref',
152
- href: `#user-content-fnref-${item.id}`,
153
- }, '\u21a9');
148
+ function renderFootnoteBackrefsReact(item, options) {
149
+ const result = [];
150
+ for (let index = 1; index <= (item.referenceCount ?? 1); index++) {
151
+ const referenceId = index === 1 ? item.id : `${item.id}-${index}`;
152
+ const label = index === 1 ? `${item.number}` : `${item.number}-${index}`;
153
+ result.push(' ', h(options, 'a', {
154
+ key: index,
155
+ 'data-footnote-backref': '',
156
+ 'aria-label': `Back to reference ${label}`,
157
+ className: 'data-footnote-backref',
158
+ href: `#user-content-fnref-${referenceId}`,
159
+ }, '\u21a9'));
160
+ }
161
+ return result;
162
+ }
163
+ function footnoteReferenceId(node) {
164
+ return node.referenceIndex && node.referenceIndex > 1 ? `${node.id}-${node.referenceIndex}` : node.id;
154
165
  }
155
166
  function h(options, tag, props, ...children) {
156
167
  const component = typeof tag === 'string' ? options.components?.[tag] ?? tag : tag;
package/dist/types.d.ts CHANGED
@@ -60,6 +60,7 @@ export interface FootnotesNode {
60
60
  export interface FootnoteItemNode {
61
61
  id: string;
62
62
  number: number;
63
+ referenceCount?: number;
63
64
  children: BlockNode[];
64
65
  }
65
66
  export interface ThematicBreakNode {
@@ -108,6 +109,7 @@ export interface FootnoteReferenceNode {
108
109
  type: 'footnoteReference';
109
110
  id: string;
110
111
  number: number;
112
+ referenceIndex?: number;
111
113
  }
112
114
  export interface LinkNode {
113
115
  type: 'link';
@@ -162,6 +164,7 @@ export interface ParseOptions {
162
164
  references?: Record<string, LinkReferenceDefinition>;
163
165
  footnotes?: Record<string, FootnoteDefinition>;
164
166
  footnoteOrder?: string[];
167
+ footnoteCounts?: Record<string, number>;
165
168
  }
166
169
  export interface RenderOptions extends ParseOptions {
167
170
  highlighter?: CodeHighlighter;
@@ -194,5 +197,6 @@ export interface LinkReferenceDefinition {
194
197
  export interface FootnoteDefinition {
195
198
  label: string;
196
199
  content: string;
200
+ id?: string;
197
201
  }
198
202
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,10 +1,9 @@
1
1
  {
2
2
  "name": "@tanstack/markdown",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
4
4
  "type": "module",
5
- "description": "A tiny deterministic TanStack-flavored markdown renderer.",
5
+ "description": "A tiny, fast, deterministic Markdown renderer for blogs and documentation.",
6
6
  "license": "MIT",
7
- "packageManager": "pnpm@11.4.0",
8
7
  "sideEffects": false,
9
8
  "files": [
10
9
  "dist",
@@ -53,14 +52,6 @@
53
52
  "import": "./dist/extensions/tabs.js"
54
53
  }
55
54
  },
56
- "scripts": {
57
- "build": "rm -rf dist && tsc -p tsconfig.build.json",
58
- "typecheck": "tsc --noEmit",
59
- "test": "vitest run",
60
- "bench": "tsx scripts/bench.ts",
61
- "size": "tsx scripts/measure-size.ts",
62
- "research": "pnpm run size && pnpm run bench"
63
- },
64
55
  "publishConfig": {
65
56
  "access": "public"
66
57
  },
@@ -77,6 +68,7 @@
77
68
  "@types/react": "^19.0.0",
78
69
  "@types/react-dom": "^19.0.0",
79
70
  "commonmark": "^0.31.2",
71
+ "commonmark-spec": "0.31.2",
80
72
  "esbuild": "^0.25.0",
81
73
  "markdown-it": "^14.2.0",
82
74
  "markdown-wasm": "^1.2.0",
@@ -91,5 +83,16 @@
91
83
  "typescript": "^5.8.0",
92
84
  "unified": "^11.0.5",
93
85
  "vitest": "^3.2.0"
86
+ },
87
+ "scripts": {
88
+ "build": "rm -rf dist && tsc -p tsconfig.build.json",
89
+ "typecheck": "tsc --noEmit",
90
+ "test": "vitest run",
91
+ "test:corpus": "vitest run tests/corpus.test.tsx",
92
+ "conformance": "tsx scripts/conformance.ts",
93
+ "bench": "tsx scripts/bench.ts",
94
+ "size": "tsx scripts/measure-size.ts",
95
+ "research": "pnpm run conformance && pnpm run size && pnpm run bench",
96
+ "verify": "pnpm test && pnpm run typecheck && pnpm run build && pnpm run research && pnpm pack --dry-run"
94
97
  }
95
- }
98
+ }