diff --git a/reports/sizes.json b/reports/sizes.json index 3518990..98cff4c 100644 --- a/reports/sizes.json +++ b/reports/sizes.json @@ -1,40 +1,40 @@ { - "generatedAt": "2026-07-26T03:30:47.396Z", + "generatedAt": "2026-07-29T02:36:13.985Z", "results": [ { "group": "tanstack", "name": "parser only", - "minBytes": 14017, - "gzipBytes": 4942, - "brotliBytes": 4549 + "minBytes": 14043, + "gzipBytes": 4970, + "brotliBytes": 4571 }, { "group": "tanstack", "name": "html renderer no highlighter", - "minBytes": 19095, - "gzipBytes": 6765, - "brotliBytes": 6191 + "minBytes": 19120, + "gzipBytes": 6781, + "brotliBytes": 6203 }, { "group": "tanstack", "name": "html renderer with external highlighter stub", - "minBytes": 19131, - "gzipBytes": 6786, - "brotliBytes": 6210 + "minBytes": 19156, + "gzipBytes": 6802, + "brotliBytes": 6223 }, { "group": "tanstack", "name": "react adapter", - "minBytes": 19060, - "gzipBytes": 6681, - "brotliBytes": 6141 + "minBytes": 19085, + "gzipBytes": 6704, + "brotliBytes": 6166 }, { "group": "tanstack", "name": "octane adapter", - "minBytes": 19063, - "gzipBytes": 6680, - "brotliBytes": 6144 + "minBytes": 19088, + "gzipBytes": 6701, + "brotliBytes": 6169 }, { "group": "tanstack", @@ -60,9 +60,9 @@ { "group": "tanstack", "name": "react adapter with streaming extension", - "minBytes": 19751, - "gzipBytes": 6862, - "brotliBytes": 6295 + "minBytes": 19776, + "gzipBytes": 6886, + "brotliBytes": 6331 }, { "group": "tanstack", diff --git a/reports/sizes.md b/reports/sizes.md index 548f8d7..b39756c 100644 --- a/reports/sizes.md +++ b/reports/sizes.md @@ -1,20 +1,20 @@ # Bundle Size Results -Generated: 2026-07-26T03:30:47.397Z +Generated: 2026-07-29T02:36:13.986Z Bundles are ESM, browser-targeted, minified with esbuild, then gzip and brotli compressed. Framework runtimes are externalized for the React and Octane adapters. | Group | Entry | Min bytes | Gzip bytes | Brotli bytes | | :--- | :--- | ---: | ---: | ---: | -| tanstack | parser only | 14017 | 4942 | 4549 | -| tanstack | html renderer no highlighter | 19095 | 6765 | 6191 | -| tanstack | html renderer with external highlighter stub | 19131 | 6786 | 6210 | -| tanstack | react adapter | 19060 | 6681 | 6141 | -| tanstack | octane adapter | 19063 | 6680 | 6144 | +| tanstack | parser only | 14043 | 4970 | 4571 | +| tanstack | html renderer no highlighter | 19120 | 6781 | 6203 | +| tanstack | html renderer with external highlighter stub | 19156 | 6802 | 6223 | +| tanstack | react adapter | 19085 | 6704 | 6166 | +| tanstack | octane adapter | 19088 | 6701 | 6169 | | tanstack | docs extension preset | 6792 | 2398 | 2188 | | tanstack | callouts extension | 556 | 372 | 329 | | tanstack | streaming extension | 699 | 311 | 253 | -| tanstack | react adapter with streaming extension | 19751 | 6862 | 6295 | +| tanstack | react adapter with streaming extension | 19776 | 6886 | 6331 | | tanstack | tabs transforms | 3398 | 1255 | 1103 | | markdown | marked | 41415 | 12548 | 11509 | | markdown | markdown-it | 148242 | 52655 | 44023 | diff --git a/src/inline.ts b/src/inline.ts index f3aa5e7..5be64e1 100644 --- a/src/inline.ts +++ b/src/inline.ts @@ -361,6 +361,13 @@ function findDelimiter(value: string, start: number, delimiter: string, budget: for (let index = start; index < value.length; index++) { if (!takeScan(budget)) return -1 if (value[index - 1] === '\\') continue + if (!delimiter[1] && value[index + 1] === delimiter) { + const close = findDelimiter(value, index + 2, delimiter + delimiter, budget) + if (close > index + 2) { + index = close + 1 + continue + } + } if (delimiter === '~~' && (value[index - 1] === '~' || value[index + 2] === '~')) continue if (delimiter[0] === '_' && !canUseUnderscore(value, index, delimiter.length, false)) continue if (value.startsWith(delimiter, index)) return index @@ -369,7 +376,7 @@ function findDelimiter(value: string, start: number, delimiter: string, budget: } function findSingleTildeDelimiter(value: string, start: number, budget: InlineParseBudget): number { - if (isWhitespace(value[start] ?? '') || /\d/.test(value[start] ?? '')) return -1 + if (isDelimiterWhitespace(value[start]) || /\d/.test(value[start]!)) return -1 for (let index = start; index < value.length; index++) { if (!takeScan(budget)) return -1 @@ -377,7 +384,7 @@ function findSingleTildeDelimiter(value: string, start: number, budget: InlinePa if (value[index] !== '~') continue if (value[index - 1] === '~') continue if (value[index + 1] === '~') continue - if (isWhitespace(value[index - 1] ?? '')) return -1 + if (isDelimiterWhitespace(value[index - 1])) return -1 return index } @@ -414,10 +421,6 @@ function isPunctuation(value: string | undefined): boolean { return value !== undefined && /[^\p{L}\p{N}\s]/u.test(value) } -function isWhitespace(value: string): boolean { - return /\s/.test(value) -} - function textFromMarkdown(value: string, budget: InlineParseBudget): string { return parseInlineRaw(value, {}, budget).map(node => (node.type === 'text' || node.type === 'inlineCode' ? node.value : '')).join('') } diff --git a/tests/bundle-size.test.ts b/tests/bundle-size.test.ts index 5d15c82..fdf162d 100644 --- a/tests/bundle-size.test.ts +++ b/tests/bundle-size.test.ts @@ -16,10 +16,10 @@ describe('bundle budgets', () => { ) expect(parser.gzipBytes).toBeLessThan(4_975) - expect(html.gzipBytes).toBeLessThan(6_775) - expect(react.gzipBytes).toBeLessThan(6_700) - expect(octane.gzipBytes).toBeLessThan(6_700) - expect(pluggable.gzipBytes).toBeLessThan(6_800) + expect(html.gzipBytes).toBeLessThan(6_785) + expect(react.gzipBytes).toBeLessThan(6_707) + expect(octane.gzipBytes).toBeLessThan(6_705) + expect(pluggable.gzipBytes).toBeLessThan(6_805) expect(streaming.gzipBytes).toBeLessThan(400) expect(reactStreaming.gzipBytes).toBeLessThan(7_000) expect(html.code).not.toContain('external-line') diff --git a/tests/markdown.test.tsx b/tests/markdown.test.tsx index 0cca9f0..ba52b73 100644 --- a/tests/markdown.test.tsx +++ b/tests/markdown.test.tsx @@ -17,6 +17,21 @@ describe('TanStack Markdown', () => { expect(renderToStaticMarkup({source})).toBe(expected) }) + it('nests strong inside emphasis', () => { + const cases = [ + ['*a **b** c*', '

a b c

'], + ['*see **the docs** here*', '

see the docs here

'], + ['_italic __bold__ tail_', '

italic bold tail

'], + ['*outer **inner***', '

outer inner

'], + ['*a **b\\** c*', '

a b* c*

'], + ] + + for (const [source, expected] of cases) { + expect(renderHtml(source!)).toBe(expected) + expect(renderToStaticMarkup({source!})).toBe(expected) + } + }) + it('preserves escaped type brackets around linked API types when HTML is enabled', () => { const source = '`Partial`\\<[HotkeyOptions](/options)\\>' const expected = '

Partial<HotkeyOptions>

'