@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.
@@ -0,0 +1,493 @@
1
+ ---
2
+ name: 'production-pipelines'
3
+ description: >
4
+ Audit and ship a production Markdown pipeline with explicit trust boundaries,
5
+ external syntax highlighting, parse-ahead caching, compatibility checks,
6
+ deterministic output, and bundle budgets. Load before deploying blogs, docs,
7
+ or untrusted-content rendering.
8
+ metadata:
9
+ type: lifecycle
10
+ library: '@tanstack/markdown'
11
+ library_version: '0.0.10'
12
+ requires:
13
+ - 'render-markdown'
14
+ sources:
15
+ - 'TanStack/markdown:docs/core-concepts/security.md'
16
+ - 'TanStack/markdown:docs/core-concepts/document-model.md'
17
+ - 'TanStack/markdown:docs/core-concepts/syntax-profile.md'
18
+ - 'TanStack/markdown:docs/guides/react.md'
19
+ - 'TanStack/markdown:docs/guides/syntax-highlighting.md'
20
+ - 'TanStack/markdown:docs/guides/performance.md'
21
+ - 'TanStack/markdown:docs/guides/testing.md'
22
+ - 'TanStack/markdown:docs/comparison.md'
23
+ - 'TanStack/markdown:src/utils.ts'
24
+ - 'TanStack/markdown:tests/security.test.tsx'
25
+ - 'TanStack/markdown:tests/bundle-size.test.ts'
26
+ ---
27
+
28
+ This skill builds on `render-markdown`. Read it first for the supported syntax, AST, parser options, and renderer contracts.
29
+
30
+ # TanStack Markdown — Production Pipeline Checklist
31
+
32
+ Run every section before deploying a blog, documentation site, or user-content renderer.
33
+
34
+ ## Trust Boundary Checks
35
+
36
+ ### Check: Classify every Markdown source
37
+
38
+ Expected:
39
+
40
+ ```ts
41
+ import { renderHtml } from '@tanstack/markdown/html'
42
+
43
+ export function renderUntrustedMarkdown(
44
+ source: string,
45
+ sanitize: (html: string) => string,
46
+ ): string {
47
+ const rendered = renderHtml(source)
48
+ return sanitize(rendered)
49
+ }
50
+ ```
51
+
52
+ Fail condition: Untrusted input reaches `allowHtml`, an unaudited extension `renderHtml` hook, or an unaudited highlighter.
53
+
54
+ Fix: Separate trusted and untrusted entry points, keep trusted callbacks disabled for untrusted content, and enforce application link, image, and final-HTML policy.
55
+
56
+ ## Highlighting Checks
57
+
58
+ ### Check: Return trusted code contents only
59
+
60
+ Expected:
61
+
62
+ ```ts
63
+ 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
+ },
78
+ })
79
+ ```
80
+
81
+ Fail condition: The callback returns a complete `<pre><code>` tree, does not escape source code, or comes from an unreviewed transform.
82
+
83
+ Fix: Return only escaped markup for the renderer-owned `<code>` contents, and run highlighting during ingestion, build, or server rendering.
84
+
85
+ ## Compatibility Checks
86
+
87
+ ### Check: Validate the actual content corpus
88
+
89
+ Expected:
90
+
91
+ ```bash
92
+ MARKDOWN_CORPUS_DIRS=../site/src/blog:../site/docs pnpm run test:corpus
93
+ pnpm run corpus:audit:tanstack
94
+ pnpm run corpus:audit:external
95
+ ```
96
+
97
+ Fail condition: Adoption relies only on CommonMark examples or a comparison table instead of the site's Markdown.
98
+
99
+ Fix: Add downstream content directories, preserve practical regressions with focused fixtures, and require renderer and bundle accounting for new syntax.
100
+
101
+ ### Check: Verify deterministic output
102
+
103
+ Expected:
104
+
105
+ ```ts
106
+ import { renderHtml } from '@tanstack/markdown/html'
107
+ import { parseMarkdown } from '@tanstack/markdown/parser'
108
+
109
+ const source = '# Deterministic\n\n- one\n- two'
110
+ const firstDocument = JSON.stringify(parseMarkdown(source))
111
+ const secondDocument = JSON.stringify(parseMarkdown(source))
112
+ const firstHtml = renderHtml(source)
113
+ const secondHtml = renderHtml(source)
114
+
115
+ if (firstDocument !== secondDocument || firstHtml !== secondHtml) {
116
+ throw new Error('Markdown output is nondeterministic')
117
+ }
118
+ ```
119
+
120
+ Fail condition: Identical source and options produce different serialized AST or HTML.
121
+
122
+ Fix: Remove time, randomness, environment state, and unstable ordering from extensions and render callbacks.
123
+
124
+ ## Performance and Cache Checks
125
+
126
+ ### Check: Parse once with final options
127
+
128
+ Expected:
129
+
130
+ ```ts
131
+ import type { MarkdownDocument } from '@tanstack/markdown'
132
+ import { renderHtml } from '@tanstack/markdown/html'
133
+ import { parseMarkdown } from '@tanstack/markdown/parser'
134
+
135
+ const cache = new Map<string, MarkdownDocument>()
136
+
137
+ export function renderCachedArticle(key: string, source: string): string {
138
+ const cacheKey = `markdown-0.0.10:${key}`
139
+ let document = cache.get(cacheKey)
140
+ if (!document) {
141
+ document = parseMarkdown(source, {
142
+ frontmatter: true,
143
+ headingIds: true,
144
+ })
145
+ cache.set(cacheKey, document)
146
+ }
147
+ return renderHtml(document)
148
+ }
149
+ ```
150
+
151
+ Fail condition: Stable content is reparsed per request, or parser options/extensions change after the AST is cached.
152
+
153
+ Fix: Build the AST with final parse options, version persisted cache keys, invalidate stored ASTs when node contracts change, and use only narrow entry points.
154
+
155
+ ### Check: Enforce bundle budgets
156
+
157
+ Expected:
158
+
159
+ ```bash
160
+ pnpm run size
161
+ pnpm test -- tests/bundle-size.test.ts
162
+ ```
163
+
164
+ Fail condition: Any measured entry exceeds its checked gzip budget or starts bundling a highlighter.
165
+
166
+ Fix: Inspect the bundle diff and justify any syntax or dependency cost before adjusting a budget.
167
+
168
+ ## Release Checks
169
+
170
+ ### Check: Run the complete package gate
171
+
172
+ Expected:
173
+
174
+ ```bash
175
+ pnpm run verify
176
+ ```
177
+
178
+ Fail condition: Tests, typechecking, build, docs validation, conformance accounting, sizes, benchmarks, or the npm dry run fail.
179
+
180
+ Fix: Resolve every gate before publishing, including HTML/React/Octane parity; audit raw HTML, highlighter output, HTML hooks, and component replacements separately.
181
+
182
+ ## Common Production Mistakes
183
+
184
+ ### CRITICAL Enabling HTML for untrusted Markdown
185
+
186
+ Wrong:
187
+
188
+ ```ts
189
+ import { renderHtml } from '@tanstack/markdown/html'
190
+
191
+ export function renderComment(source: string): string {
192
+ return renderHtml(source, { allowHtml: true })
193
+ }
194
+ ```
195
+
196
+ Correct:
197
+
198
+ ```ts
199
+ import { renderHtml } from '@tanstack/markdown/html'
200
+
201
+ export function renderComment(source: string): string {
202
+ return renderHtml(source)
203
+ }
204
+ ```
205
+
206
+ `allowHtml` emits raw nodes and is not a sanitization step.
207
+
208
+ Source: `docs/core-concepts/security.md`
209
+
210
+ ### HIGH Returning highlighter containers
211
+
212
+ Wrong:
213
+
214
+ ```ts
215
+ import { renderHtml } from '@tanstack/markdown/html'
216
+
217
+ function escapeCode(code: string): string {
218
+ return code.replace(/[&<>]/g, (character) => ({
219
+ '&': '&amp;',
220
+ '<': '&lt;',
221
+ '>': '&gt;',
222
+ })[character] ?? character)
223
+ }
224
+
225
+ console.log(renderHtml('```ts\nconst x = 1\n```', {
226
+ highlighter: (code) => `<pre><code>${escapeCode(code)}</code></pre>`,
227
+ }))
228
+ ```
229
+
230
+ Correct:
231
+
232
+ ```ts
233
+ import { renderHtml } from '@tanstack/markdown/html'
234
+
235
+ function escapeCode(code: string): string {
236
+ return code.replace(/[&<>]/g, (character) => ({
237
+ '&': '&amp;',
238
+ '<': '&lt;',
239
+ '>': '&gt;',
240
+ })[character] ?? character)
241
+ }
242
+
243
+ console.log(renderHtml('```ts\nconst x = 1\n```', {
244
+ highlighter: (code) => `<span class="token">${escapeCode(code)}</span>`,
245
+ }))
246
+ ```
247
+
248
+ The renderer owns `<pre><code>`; the callback supplies only the code element's trusted contents.
249
+
250
+ Source: `docs/guides/syntax-highlighting.md`
251
+
252
+ ### CRITICAL Trusting arbitrary highlighter output
253
+
254
+ Wrong:
255
+
256
+ ```ts
257
+ import { renderHtml } from '@tanstack/markdown/html'
258
+
259
+ const source = '```html\n<img src=x onerror=alert(1)>\n```'
260
+ console.log(renderHtml(source, { highlighter: (code) => code }))
261
+ ```
262
+
263
+ Correct:
264
+
265
+ ```ts
266
+ import { renderHtml } from '@tanstack/markdown/html'
267
+
268
+ function escapeCode(code: string): string {
269
+ return code.replace(/[&<>]/g, (character) => ({
270
+ '&': '&amp;',
271
+ '<': '&lt;',
272
+ '>': '&gt;',
273
+ })[character] ?? character)
274
+ }
275
+
276
+ const source = '```html\n<img src=x onerror=alert(1)>\n```'
277
+ console.log(renderHtml(source, { highlighter: escapeCode }))
278
+ ```
279
+
280
+ Highlighter output is inserted without further escaping in every renderer.
281
+
282
+ Source: `docs/core-concepts/security.md`
283
+
284
+ ### MEDIUM Bundling highlighting into static clients
285
+
286
+ Wrong:
287
+
288
+ ```tsx
289
+ import { Markdown } from '@tanstack/markdown/react'
290
+ import { tokenize } from '@tanstack/highlight'
291
+
292
+ export function Article({ source }: { source: string }) {
293
+ return <Markdown highlighter={(code) => String(tokenize(code))}>{source}</Markdown>
294
+ }
295
+ ```
296
+
297
+ Correct:
298
+
299
+ ```ts
300
+ import { renderHtml } from '@tanstack/markdown/html'
301
+ import type { CodeHighlighter } from '@tanstack/markdown'
302
+
303
+ export function renderStaticArticle(
304
+ source: string,
305
+ highlighter: CodeHighlighter,
306
+ ): string {
307
+ return renderHtml(source, { highlighter })
308
+ }
309
+ ```
310
+
311
+ Tokenizer runtimes, grammars, and themes can outweigh Markdown parsing and should remain build-time or server-side for static content.
312
+
313
+ Source: `docs/guides/performance.md`
314
+
315
+ ### CRITICAL Treating defaults as a sanitizer
316
+
317
+ Wrong:
318
+
319
+ ```ts
320
+ import { renderHtml } from '@tanstack/markdown/html'
321
+
322
+ export function renderForEveryPolicy(source: string): string {
323
+ return renderHtml(source)
324
+ }
325
+ ```
326
+
327
+ Correct:
328
+
329
+ ```ts
330
+ import { renderHtml } from '@tanstack/markdown/html'
331
+
332
+ export function renderWithPolicy(
333
+ source: string,
334
+ sanitize: (html: string) => string,
335
+ ): string {
336
+ return sanitize(renderHtml(source))
337
+ }
338
+ ```
339
+
340
+ Core escaping and protocol filtering do not enforce application-specific outbound-link, image, or final-HTML policy.
341
+
342
+ Source: `docs/core-concepts/security.md`
343
+
344
+ ### HIGH Assuming complete CommonMark behavior
345
+
346
+ Wrong:
347
+
348
+ ```ts
349
+ import { renderHtml } from '@tanstack/markdown/html'
350
+
351
+ export function renderArbitraryMarkdown(source: string): string {
352
+ return renderHtml(source)
353
+ }
354
+ ```
355
+
356
+ Correct:
357
+
358
+ ```ts
359
+ import { renderHtml } from '@tanstack/markdown/html'
360
+
361
+ export function renderControlledDocs(source: string): string {
362
+ return renderHtml(source)
363
+ }
364
+ ```
365
+
366
+ The package implements a documented docs/blog profile, not complete CommonMark, GFM, MDX, or arbitrary plugin behavior.
367
+
368
+ Source: `docs/core-concepts/syntax-profile.md`
369
+
370
+ ### MEDIUM Reparsing unchanged content
371
+
372
+ Wrong:
373
+
374
+ ```ts
375
+ import { renderHtml } from '@tanstack/markdown/html'
376
+
377
+ export function renderRequest(source: string): string {
378
+ return renderHtml(source)
379
+ }
380
+ ```
381
+
382
+ Correct:
383
+
384
+ ```ts
385
+ import type { MarkdownDocument } from '@tanstack/markdown'
386
+ import { renderHtml } from '@tanstack/markdown/html'
387
+ import { parseMarkdown } from '@tanstack/markdown/parser'
388
+
389
+ export function compile(source: string): MarkdownDocument {
390
+ return parseMarkdown(source)
391
+ }
392
+
393
+ export function renderRequest(document: MarkdownDocument): string {
394
+ return renderHtml(document)
395
+ }
396
+ ```
397
+
398
+ String render inputs parse the complete document, while a cached `MarkdownDocument` skips that work.
399
+
400
+ Source: `docs/core-concepts/document-model.md`
401
+
402
+ ### HIGH Injecting HTML into React
403
+
404
+ Wrong:
405
+
406
+ ```tsx
407
+ import { renderHtml } from '@tanstack/markdown/html'
408
+
409
+ export function Article({ source }: { source: string }) {
410
+ const html = renderHtml(source)
411
+ return <article dangerouslySetInnerHTML={{ __html: html }} />
412
+ }
413
+ ```
414
+
415
+ Correct:
416
+
417
+ ```tsx
418
+ import { Markdown } from '@tanstack/markdown/react'
419
+
420
+ export function Article({ source }: { source: string }) {
421
+ return <article><Markdown>{source}</Markdown></article>
422
+ }
423
+ ```
424
+
425
+ The HTML string adds a trusted insertion boundary and bypasses React component replacement.
426
+
427
+ Source: `docs/guides/react.md`
428
+
429
+ ### MEDIUM Expecting fence metadata to highlight
430
+
431
+ Wrong:
432
+
433
+ ```ts
434
+ import { renderHtml } from '@tanstack/markdown/html'
435
+
436
+ const source = '```ts {1}\nconst answer = 42\n```'
437
+ console.log(renderHtml(source))
438
+ ```
439
+
440
+ Correct:
441
+
442
+ ```ts
443
+ import { renderHtml } from '@tanstack/markdown/html'
444
+
445
+ function escapeCode(code: string): string {
446
+ return code.replace(/[&<>]/g, (character) => ({
447
+ '&': '&amp;',
448
+ '<': '&lt;',
449
+ '>': '&gt;',
450
+ })[character] ?? character)
451
+ }
452
+
453
+ const source = '```ts {1}\nconst answer = 42\n```'
454
+ console.log(renderHtml(source, { highlighter: escapeCode }))
455
+ ```
456
+
457
+ Fence metadata enters the AST, but token markup requires an external highlighter.
458
+
459
+ Source: `docs/core-concepts/syntax-profile.md`
460
+
461
+ ## Tensions
462
+
463
+ ### Compatibility breadth versus bundle budget
464
+
465
+ Do not maximize conformance by default. Require target-corpus evidence, renderer coverage, and measured bundle cost before adding syntax.
466
+
467
+ ### Rich trusted output versus untrusted-content safety
468
+
469
+ Do not enable raw HTML, extension HTML, or highlighter markup globally to solve presentation needs. Scope each trusted callback to controlled content.
470
+
471
+ ### Parse-ahead performance versus option timing
472
+
473
+ Build cached ASTs with final parser options and document transforms. Keep required HTML render hooks active when rendering those cached documents.
474
+
475
+ ## Pre-Deploy Summary
476
+
477
+ - [ ] Every content source is classified as trusted or untrusted.
478
+ - [ ] Raw HTML, extension HTML, and highlighter output have explicit owners.
479
+ - [ ] Application link, image, and final-sanitization policies are enforced.
480
+ - [ ] The downstream corpus passes deterministic AST and renderer checks.
481
+ - [ ] Unsupported syntax is documented rather than silently assumed.
482
+ - [ ] Stable content is parsed once with final options and versioned cache keys.
483
+ - [ ] Highlighting runs at build time or on the server where possible.
484
+ - [ ] Narrow entry points and individual extensions are used.
485
+ - [ ] Bundle budgets and HTML/React/Octane parity tests pass.
486
+ - [ ] `pnpm run verify` passes before release.
487
+
488
+ ## Related Skills
489
+
490
+ - `render-markdown` for the supported profile, AST, parser options, and core HTML rendering.
491
+ - `docs-features` for docs metadata and code-fence behavior.
492
+ - `custom-extensions` for parser hooks and trusted HTML extension boundaries.
493
+ - `react-rendering` and `octane-rendering` for framework component policy and SSR parity.