svelte-streamdown 3.1.2 → 4.0.0

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
@@ -75,9 +75,9 @@ Full support for
75
75
 
76
76
  ### 💻 Interactive Code Blocks
77
77
 
78
- - Syntax highlighting powered by Shiki
78
+ - Syntax highlighting powered by [@tanstack/highlight](https://github.com/TanStack/highlight) (synchronous, SSR-friendly, ~31KB min / ~11KB gzip for every language)
79
79
  - Copy-to-clipboard functionality
80
- - Support any Shiki themes
80
+ - Support any `@tanstack/highlight` theme, or your own
81
81
 
82
82
  ### 🔢 Mathematical Expressions
83
83
 
@@ -398,7 +398,7 @@ Here is how it works on each content update:
398
398
 
399
399
  During streaming, newly received text almost always only changes the **last** block (and occasionally starts a new one). Every earlier block keeps an identical raw string, so Svelte skips its `lex()` call entirely — this is the equivalent of the memoized `Block` component in the React version. The block-splitting pass itself runs on every update, but it is the cheap pass; the costly inline parsing is what gets reused.
400
400
 
401
- Code highlighting is incremental as well: a code block is only re-highlighted when its text changes, and the Shiki highlighter caches the languages and themes it has already loaded, so a streaming code block does not reload its grammar on every chunk.
401
+ Code highlighting is incremental as well: a code block is only re-highlighted when its text changes. Highlighting itself is synchronous and runs during SSR, so there is no grammar to load, no loading state and no hydration flash.
402
402
 
403
403
  > [!NOTE]
404
404
  > There is intentionally no separate block-level parse cache (e.g. an LRU keyed by block content). For the common append-only streaming case the reactivity-based approach above already avoids redundant work, and a standalone cache would add memory usage and invalidation complexity without a measurable benefit. If you have a workload where this matters, please [open an issue](https://github.com/beynar/svelte-streamdown/issues) with a repro — we're happy to revisit.
@@ -524,7 +524,7 @@ Streamdown is optimized for minimal bundle size by making heavy components **opt
524
524
  <script>
525
525
  import { Streamdown } from 'svelte-streamdown';
526
526
  // Import only the components you need
527
- import Code from 'svelte-streamdown/code'; // Shiki syntax highlighting
527
+ import Code from 'svelte-streamdown/code'; // syntax highlighting
528
528
  import Mermaid from 'svelte-streamdown/mermaid'; // Mermaid diagrams
529
529
  import Math from 'svelte-streamdown/math'; // KaTeX math rendering
530
530
  </script>
@@ -534,11 +534,11 @@ Streamdown is optimized for minimal bundle size by making heavy components **opt
534
534
 
535
535
  ### Component Dependencies
536
536
 
537
- | Component | Import Path | Dependency | Size Impact |
538
- | --------- | --------------------------- | ---------- | ------------------------- |
539
- | `Code` | `svelte-streamdown/code` | Shiki | ~2MB (languages + themes) |
540
- | `Mermaid` | `svelte-streamdown/mermaid` | Mermaid.js | ~1.5MB |
541
- | `Math` | `svelte-streamdown/math` | KaTeX | ~300KB |
537
+ | Component | Import Path | Dependency | Size Impact |
538
+ | --------- | --------------------------- | --------------------- | ---------------------------------------------------- |
539
+ | `Code` | `svelte-streamdown/code` | `@tanstack/highlight` | ~31KB min / ~11KB gzip (all 30 languages + 2 themes) |
540
+ | `Mermaid` | `svelte-streamdown/mermaid` | Mermaid.js | ~1.5MB |
541
+ | `Math` | `svelte-streamdown/math` | KaTeX | ~300KB |
542
542
 
543
543
  > [!TIP]
544
544
  > Only import the components your application actually uses. If your content doesn't include code blocks, mermaid diagrams, or math expressions, you can skip those imports entirely for a much smaller bundle.
@@ -551,71 +551,150 @@ When a heavy component is not provided:
551
551
  - **Mermaid**: Renders the mermaid source as a code block
552
552
  - **Math**: Renders the raw LaTeX/math text
553
553
 
554
- ### Shiki Themes
554
+ ### Highlight themes
555
555
 
556
- The `Code` component bundles two themes out of the box: **`github-dark`** and **`github-light`**. By default `shikiTheme` follows the active color scheme (`github-dark` in dark mode, `github-light` otherwise), so basic light/dark theming works with no extra configuration.
556
+ The `Code` component bundles two themes out of the box: **`github-dark`** and **`github-light`**. By default `highlightTheme` follows the active color scheme (`github-dark` in dark mode, `github-light` otherwise), so basic light/dark theming works with no extra configuration.
557
557
 
558
- To use any other Shiki theme (e.g. `vesper`, `github-dark-default`, `github-light-default`), import it from `@shikijs/themes/<name>` and register it via the `shikiThemes` prop. The **key** you register it under is the value you pass to `shikiTheme`:
558
+ To use any other theme (`aurora-x`, `dracula`, `gruvbox-dark`, `gruvbox-light`, `monokai`, `nord`, `one-dark-pro`, `solarized-dark`, `solarized-light`), import it from `@tanstack/highlight/themes/<name>` and register it via the `highlightThemes` prop. The **key** you register it under is the value you pass to `highlightTheme`:
559
559
 
560
560
  ```svelte
561
561
  <script lang="ts">
562
562
  import { Streamdown } from 'svelte-streamdown';
563
- import Code from 'svelte-streamdown/code'; // enables Shiki highlighting
564
- import vesper from '@shikijs/themes/vesper';
563
+ import Code from 'svelte-streamdown/code'; // enables highlighting
564
+ import dracula from '@tanstack/highlight/themes/dracula';
565
565
 
566
566
  let { content } = $props();
567
567
  </script>
568
568
 
569
- <Streamdown {content} components={{ code: Code }} shikiThemes={{ vesper }} shikiTheme="vesper" />
569
+ <Streamdown
570
+ {content}
571
+ components={{ code: Code }}
572
+ highlightThemes={{ dracula }}
573
+ highlightTheme="dracula"
574
+ />
570
575
  ```
571
576
 
572
- > [!IMPORTANT]
573
- > A theme passed to `shikiTheme` must be one of the two built-in themes **or** registered via `shikiThemes`. If it is neither, the code block stays in its loading (skeleton) state and is never highlighted — this is the most common cause of "my theme stopped working".
577
+ > [!NOTE]
578
+ > An unknown `highlightTheme` key falls back to the built-in `github-dark` / `github-light` (following the color scheme) instead of leaving the code block unhighlighted.
579
+
580
+ A custom theme is just an object of the `HighlightTheme` shape (exported from `svelte-streamdown`) — a `name`, `type`, `background`, `foreground` and a color per token class:
581
+
582
+ ```svelte
583
+ <script lang="ts">
584
+ import { Streamdown, type HighlightTheme } from 'svelte-streamdown';
585
+ import Code from 'svelte-streamdown/code';
586
+ import githubDark from '@tanstack/highlight/themes/github-dark';
587
+
588
+ const myTheme: HighlightTheme = {
589
+ ...githubDark,
590
+ name: 'my-theme',
591
+ tokens: { ...githubDark.tokens, keyword: '#ff0088', comment: '#5a5a5a' }
592
+ };
593
+ </script>
594
+
595
+ <Streamdown
596
+ {content}
597
+ components={{ code: Code }}
598
+ highlightThemes={{ 'my-theme': myTheme }}
599
+ highlightTheme="my-theme"
600
+ />
601
+ ```
574
602
 
575
603
  #### Dynamic (light/dark) theme switching
576
604
 
577
- Register every theme you intend to switch between in `shikiThemes`, then drive `shikiTheme` from your color-scheme store. Switching is fully reactive — code blocks re-highlight when `shikiTheme` changes:
605
+ Register every theme you intend to switch between in `highlightThemes`, then drive `highlightTheme` from your color-scheme store. Switching is fully reactive:
578
606
 
579
607
  ```svelte
580
608
  <script lang="ts">
581
609
  import { Streamdown } from 'svelte-streamdown';
582
610
  import Code from 'svelte-streamdown/code';
583
611
  import { mode } from 'mode-watcher';
584
- import githubDarkDefault from '@shikijs/themes/github-dark-default';
585
- import githubLightDefault from '@shikijs/themes/github-light-default';
612
+ import oneDarkPro from '@tanstack/highlight/themes/one-dark-pro';
613
+ import solarizedLight from '@tanstack/highlight/themes/solarized-light';
586
614
 
587
615
  let { content } = $props();
588
616
 
589
- const shikiTheme = $derived(
590
- mode.current === 'dark' ? 'github-dark-default' : 'github-light-default'
591
- );
617
+ const highlightTheme = $derived(mode.current === 'dark' ? 'one-dark-pro' : 'solarized-light');
592
618
  </script>
593
619
 
594
620
  <Streamdown
595
621
  {content}
596
622
  components={{ code: Code }}
597
623
  baseTheme="shadcn"
598
- shikiThemes={{
599
- 'github-dark-default': githubDarkDefault,
600
- 'github-light-default': githubLightDefault
624
+ highlightThemes={{
625
+ 'one-dark-pro': oneDarkPro,
626
+ 'solarized-light': solarizedLight
601
627
  }}
602
- {shikiTheme}
628
+ {highlightTheme}
603
629
  />
604
630
  ```
605
631
 
606
632
  > [!NOTE]
607
- > The built-in `github-dark` / `github-light` themes can be switched dynamically with just `shikiTheme` (no `shikiThemes` registration needed), since both are always loaded.
633
+ > The built-in `github-dark` / `github-light` themes can be switched dynamically with just `highlightTheme` (no `highlightThemes` registration needed).
634
+
635
+ #### Styling tokens with CSS
636
+
637
+ Every token span carries `th-token` plus a `th-<class>` class (`th-keyword`, `th-string`, `th-comment`, `th-function`, `th-number`, `th-operator`, `th-tag`, `th-attr`, …) alongside its inline color from the active theme, so you can tweak or override token styles from CSS:
638
+
639
+ ```css
640
+ [data-streamdown-code] .th-comment {
641
+ font-style: italic;
642
+ }
643
+ ```
644
+
645
+ ### Supported languages
646
+
647
+ All 30 scanners ship in the same ~31KB bundle, so there is nothing to lazy-load:
648
+
649
+ `apache`, `cmake`, `cpp`, `css`, `diff`, `dockerfile`, `ejs`, `env`, `go`, `html`, `http`, `js`, `json`, `jsx`, `markdown`, `mermaid`, `nginx`, `php`, `plaintext`, `python`, `scheme`, `shell`, `sql`, `svelte`, `toml`, `ts`, `tsrx`, `tsx`, `vue`, `yaml`.
650
+
651
+ Common aliases are normalized (`javascript` → `js`, `typescript` → `ts`, `bash`/`sh`/`zsh` → `shell`, `py` → `python`, `jsonc`/`json5` → `json`, `xml`/`htm` → `html`, `md` → `markdown`, `yml` → `yaml`, `docker` → `dockerfile`).
652
+
653
+ > [!IMPORTANT]
654
+ > Any other language — including `java`, `rust`, `c`, `c#`, `swift`, `kotlin`, `ruby` and `graphql` — is **not** highlighted as of `@tanstack/highlight@0.1.0`: the code block still renders (and keeps its copy/download UI), but as plaintext.
655
+
656
+ You can add your own language with `defineLanguage` and the `highlightLanguages` prop. A language is a tokenizer returning `{ className, start, end }` ranges:
657
+
658
+ ```svelte
659
+ <script lang="ts">
660
+ import { Streamdown, defineLanguage } from 'svelte-streamdown';
661
+ import Code from 'svelte-streamdown/code';
662
+
663
+ const brainfuck = defineLanguage({
664
+ name: 'brainfuck',
665
+ tokenize: (code) =>
666
+ [...code.matchAll(/[+\-<>]+/g)].map((match) => ({
667
+ className: 'operator' as const,
668
+ start: match.index,
669
+ end: match.index + match[0].length
670
+ }))
671
+ });
672
+ </script>
673
+
674
+ <Streamdown {content} components={{ code: Code }} highlightLanguages={[brainfuck]} />
675
+ ```
676
+
677
+ > [!NOTE]
678
+ > Keep the array passed to `highlightLanguages` referentially stable (define it outside the template): a new array builds a new highlighter.
608
679
 
609
- #### Migrating from v2 (`shikiPreloadThemes`)
680
+ ### Migrating from v3 (shiki)
610
681
 
611
- The v2 `shikiPreloadThemes` prop has been **removed**. Themes are no longer referenced by bundled name; instead you import the theme objects yourself and register them with `shikiThemes`. Registered themes are loaded together with the highlighter (i.e. effectively preloaded), so there is no separate preload step:
682
+ v4 replaces shiki with `@tanstack/highlight`. Highlighting is now synchronous, renders during SSR and no longer has a skeleton/loading state. Remove `shiki` / `@shikijs/*` from your dependencies and rename the props:
612
683
 
613
684
  ```diff
614
- - shikiPreloadThemes={['github-dark-default', 'github-light-default']}
615
- + shikiThemes={{ 'github-dark-default': githubDarkDefault, 'github-light-default': githubLightDefault }}
685
+ - shikiTheme="nord"
686
+ - shikiThemes={{ nord }}
687
+ - shikiLanguages={[{ id: 'haskell', name: 'Haskell', import: () => import('@shikijs/langs/haskell') }]}
688
+ + highlightTheme="nord"
689
+ + highlightThemes={{ nord }}
690
+ + highlightLanguages={[myLanguage]}
616
691
  ```
617
692
 
618
- This also keeps the default bundle small: only the themes you actually import are included.
693
+ - Theme objects come from `@tanstack/highlight/themes/<name>` instead of `@shikijs/themes/<name>`, and follow the `HighlightTheme` shape (not shiki's `ThemeRegistration`).
694
+ - `highlightLanguages` takes `LanguageDefinition`s built with `defineLanguage`, not lazy shiki grammar loaders.
695
+ - `bundledLanguagesInfo`, `createLanguageSet` and `LanguageInfo` are gone; `defineLanguage`, `LanguageDefinition` and `HighlightTheme` are exported instead.
696
+ - The `code.skeleton` theme key is gone (there is no loading state anymore).
697
+ - Languages shiki covered but `@tanstack/highlight@0.1.0` does not (`java`, `rust`, `c`, `c#`, `swift`, `kotlin`, `ruby`, `graphql`, …) now render as plaintext.
619
698
 
620
699
  ## 📋 Props API
621
700
 
@@ -634,9 +713,9 @@ This also keeps the default bundle small: only the themes you actually import ar
634
713
  | `theme` | `DeepPartial<Theme>` | - | Custom theme overrides |
635
714
  | `baseTheme` | `'tailwind' \| 'shadcn'` | `'tailwind'` | Base theme to use before applying overrides |
636
715
  | `mergeTheme` | `boolean` | `true` | Whether to merge theme with base theme |
637
- | `shikiTheme` | `string` | auto (dark-mode aware) | Code highlighting theme. Defaults to `github-dark` in dark mode / `github-light` otherwise. Any other value must be a key registered via `shikiThemes`. See [Shiki Themes](#shiki-themes). |
638
- | `shikiThemes` | `Record<string, ThemeRegistration>` | - | Register additional pre-imported themes (e.g. `{ vesper }`) so they can be selected via `shikiTheme`, including dynamic light/dark switching. Replaces the v2 `shikiPreloadThemes` prop. |
639
- | `shikiLanguages` | `LanguageInfo[]` | - | Additional syntax highlighting languages (merged with defaults) |
716
+ | `highlightTheme` | `string` | auto (dark-mode aware) | Code highlighting theme. Defaults to `github-dark` in dark mode / `github-light` otherwise. Any other value must be a key registered via `highlightThemes`. See [Highlight themes](#highlight-themes). |
717
+ | `highlightThemes` | `Record<string, HighlightTheme>` | - | Register additional pre-imported themes (e.g. `{ dracula }`) so they can be selected via `highlightTheme`, including dynamic light/dark switching. |
718
+ | `highlightLanguages` | `LanguageDefinition[]` | - | Additional languages built with `defineLanguage` (merged with the 30 built-in ones) |
640
719
  | `mermaidConfig` | `MermaidConfig` | - | Mermaid diagram configuration |
641
720
  | `katexConfig` | `KatexOptions \| ((inline: boolean) => KatexOptions)` | - | KaTeX math rendering options |
642
721
  | `animation` | `AnimationConfig` | - | Animation configuration for streaming content |
@@ -753,7 +832,7 @@ Each component supports multiple themeable parts:
753
832
 
754
833
  **Links (`a`)**: `base`, `blocked` (for blocked/unsafe links)
755
834
 
756
- **Code (`code`)**: `base`, `container`, `header`, `button`, `language`, `skeleton`, `pre`
835
+ **Code (`code`)**: `base`, `container`, `header`, `buttons`, `language`, `line`, `pre`
757
836
 
758
837
  **Inline Code (`inlineCode`)**: `base`
759
838
 
@@ -2,11 +2,8 @@
2
2
  import { useStreamdown } from '../context.svelte.js';
3
3
  import { save } from '../utils/save.js';
4
4
  import { useCopy } from '../utils/copy.svelte.js';
5
- import { HighlighterManager, languageExtensionMap } from '../utils/hightlighter.svelte.js';
6
- import { bundledLanguagesInfo } from '../utils/bundledLanguages.js';
5
+ import { highlightLines, languageExtensionMap } from '../utils/hightlighter.svelte.js';
7
6
  import type { Tokens } from 'marked';
8
- import { type ThemedToken } from 'shiki';
9
- import { untrack } from 'svelte';
10
7
  import { checkIcon, copyIcon, downloadIcon } from './icons.js';
11
8
 
12
9
  const {
@@ -18,11 +15,6 @@
18
15
  } = $props();
19
16
 
20
17
  const streamdown = useStreamdown();
21
- const highlighter = HighlighterManager.create(
22
- bundledLanguagesInfo,
23
- streamdown.shikiThemes,
24
- streamdown.shikiLanguages
25
- );
26
18
 
27
19
  const copy = useCopy({
28
20
  get content() {
@@ -46,13 +38,7 @@
46
38
  }
47
39
  };
48
40
 
49
- $effect(() => {
50
- const theme = streamdown.shikiTheme;
51
- const lang = token.lang;
52
- untrack(() => {
53
- void highlighter.load(theme, lang);
54
- });
55
- });
41
+ const lines = $derived(highlightLines(token.text, token.lang, streamdown.highlightLanguages));
56
42
  </script>
57
43
 
58
44
  <div
@@ -84,39 +70,15 @@
84
70
  {/if}
85
71
  </div>
86
72
  <div style="height: fit-content; width: 100%;" class={streamdown.theme.code.container}>
87
- {#if highlighter.isReady(streamdown.shikiTheme, token.lang)}
88
- <pre class={streamdown.theme.code.pre}><code
89
- >{@render Tokens(
90
- highlighter.highlightCode(token.text, token.lang, streamdown.shikiTheme)
91
- )}</code
92
- ></pre>
93
- {:else}
94
- <pre class={streamdown.theme.code.pre}><code>{@render Skeleton(token.text.split('\n'))}</code
95
- ></pre>
96
- {/if}
73
+ <pre class={streamdown.theme.code.pre}><code
74
+ >{#each lines as line}<span class={streamdown.theme.code.line}
75
+ >{#if line.length === 0}&#8203;{/if}{#each line as t}<span
76
+ class="th-token{t.className ? ` th-${t.className}` : ''}"
77
+ style={streamdown.isMounted ? streamdown.animationTextStyle : ''}
78
+ style:color={streamdown.highlightTheme.tokens[t.className ?? 'token']}
79
+ >{t.value}</span
80
+ >{/each}</span
81
+ >{/each}</code
82
+ ></pre>
97
83
  </div>
98
84
  </div>
99
-
100
- {#snippet Tokens(lines: ThemedToken[][])}
101
- {#each lines as tokens}
102
- <span class={streamdown.theme.code.line}>
103
- {#each tokens as token}
104
- <span
105
- style={streamdown.isMounted ? streamdown.animationTextStyle : ''}
106
- style:color={token.color}
107
- style:background-color={token.bgColor}
108
- >
109
- {token.content}
110
- </span>
111
- {/each}
112
- </span>
113
- {/each}
114
- {/snippet}
115
-
116
- {#snippet Skeleton(lines: string[])}
117
- {#each lines as line}
118
- <span class={streamdown.theme.code.skeleton}>
119
- {line.trim().length > 0 ? line : '\u200B'}
120
- </span>
121
- {/each}
122
- {/snippet}
@@ -7,9 +7,9 @@
7
7
  let {
8
8
  content = '',
9
9
  class: className,
10
- shikiTheme,
11
- shikiLanguages,
12
- shikiThemes,
10
+ highlightTheme,
11
+ highlightLanguages,
12
+ highlightThemes,
13
13
  parseIncompleteMarkdown,
14
14
  defaultOrigin,
15
15
  allowedLinkPrefixes = ['*'],
@@ -36,15 +36,12 @@
36
36
  ...snippets
37
37
  }: StreamdownProps<Source> = $props();
38
38
  import { useDarkMode } from './utils/darkMode.svelte.js';
39
+ import { resolveHighlightTheme } from './utils/highlightThemes.js';
39
40
 
40
41
  const darkMode = useDarkMode();
41
42
 
42
- const shikiThemedTheme = $derived(
43
- shikiThemes
44
- ? Object.keys(shikiThemes)[0] || 'github-light'
45
- : darkMode.current
46
- ? 'github-dark'
47
- : 'github-light'
43
+ const resolvedHighlightTheme = $derived(
44
+ resolveHighlightTheme(highlightTheme, highlightThemes, darkMode.current)
48
45
  );
49
46
 
50
47
  const mermaidThemedTheme = $derived(
@@ -70,8 +67,8 @@
70
67
  get allowedImagePrefixes() {
71
68
  return allowedImagePrefixes;
72
69
  },
73
- get shikiTheme() {
74
- return shikiTheme || shikiThemedTheme;
70
+ get highlightTheme() {
71
+ return resolvedHighlightTheme;
75
72
  },
76
73
  get snippets() {
77
74
  return snippets;
@@ -99,11 +96,11 @@
99
96
  get translations() {
100
97
  return translations;
101
98
  },
102
- get shikiLanguages() {
103
- return shikiLanguages;
99
+ get highlightLanguages() {
100
+ return highlightLanguages;
104
101
  },
105
- get shikiThemes() {
106
- return shikiThemes;
102
+ get highlightThemes() {
103
+ return highlightThemes;
107
104
  },
108
105
  get sources() {
109
106
  return sources;
@@ -2,11 +2,11 @@ import type { Component, Snippet } from 'svelte';
2
2
  import type { DeepPartialTheme, Theme } from './theme.js';
3
3
  import type { MermaidConfig } from 'mermaid';
4
4
  import type { KatexOptions } from 'katex';
5
- import type { LanguageInfo } from './utils/bundledLanguages.js';
6
- import type { ThemeRegistration } from 'shiki';
7
- export interface StreamdownContext extends Omit<StreamdownProps, keyof Snippets | 'class' | 'theme' | 'shikiTheme' | 'inlineCitationsMode'> {
5
+ import type { HighlightTheme } from '@tanstack/highlight/theme';
6
+ import type { LanguageDefinition } from '@tanstack/highlight';
7
+ export interface StreamdownContext extends Omit<StreamdownProps, keyof Snippets | 'class' | 'theme' | 'highlightTheme' | 'inlineCitationsMode'> {
8
8
  snippets: Snippets;
9
- shikiTheme: string;
9
+ highlightTheme: HighlightTheme;
10
10
  theme: Theme;
11
11
  controls: {
12
12
  code: boolean;
@@ -27,8 +27,9 @@ export declare class StreamdownContext<Source extends Record<string, any> = Reco
27
27
  isMounted: boolean;
28
28
  get animationTextStyle(): string | undefined;
29
29
  get animationBlockStyle(): string | undefined;
30
- constructor(props: Omit<StreamdownProps, keyof Snippets | 'class'> & {
30
+ constructor(props: Omit<StreamdownProps, keyof Snippets | 'class' | 'highlightTheme'> & {
31
31
  snippets: Snippets<Source>;
32
+ highlightTheme: HighlightTheme;
32
33
  });
33
34
  }
34
35
  export declare const useStreamdown: () => StreamdownContext<Record<string, any>>;
@@ -108,9 +109,9 @@ export type StreamdownProps<Source extends Record<string, any> = Record<string,
108
109
  theme?: DeepPartialTheme;
109
110
  baseTheme?: 'tailwind' | 'shadcn';
110
111
  mergeTheme?: boolean;
111
- shikiTheme?: string;
112
- shikiLanguages?: LanguageInfo[];
113
- shikiThemes?: Record<string, ThemeRegistration>;
112
+ highlightTheme?: string;
113
+ highlightLanguages?: LanguageDefinition[];
114
+ highlightThemes?: Record<string, HighlightTheme>;
114
115
  mermaidConfig?: MermaidConfig;
115
116
  katexConfig?: KatexOptions | ((inline: boolean) => KatexOptions);
116
117
  translations?: {
package/dist/index.d.ts CHANGED
@@ -3,4 +3,5 @@ export { useStreamdown, type StreamdownProps } from './context.svelte.js';
3
3
  export { theme, shadcnTheme, mergeTheme, type Theme } from './theme.js';
4
4
  export { type Extension, type StreamdownToken, lex, parseBlocks } from './marked/index.js';
5
5
  export { parseIncompleteMarkdown, type Plugin, IncompleteMarkdownParser } from './utils/parse-incomplete-markdown.js';
6
- export { bundledLanguagesInfo, createLanguageSet, type LanguageInfo } from './utils/bundledLanguages.js';
6
+ export { defineLanguage, type LanguageDefinition } from '@tanstack/highlight';
7
+ export type { HighlightTheme } from '@tanstack/highlight/theme';
package/dist/index.js CHANGED
@@ -3,4 +3,4 @@ export { useStreamdown } from './context.svelte.js';
3
3
  export { theme, shadcnTheme, mergeTheme } from './theme.js';
4
4
  export { lex, parseBlocks } from './marked/index.js';
5
5
  export { parseIncompleteMarkdown, IncompleteMarkdownParser } from './utils/parse-incomplete-markdown.js';
6
- export { bundledLanguagesInfo, createLanguageSet } from './utils/bundledLanguages.js';
6
+ export { defineLanguage } from '@tanstack/highlight';
@@ -31,13 +31,25 @@ export declare const lex: (markdown: string, extensions?: Extension[]) => Stream
31
31
  * instance (or per simulated stream) and pass it on every call: append-only
32
32
  * content updates then re-lex only the last couple of blocks instead of the
33
33
  * whole document. Any non-append update falls back to a full parse.
34
+ *
35
+ * Everything here is sized so the append path costs O(live tail), never
36
+ * O(document): `offsets`/`keptBefore` are prefix sums so the seal point is an
37
+ * array lookup, and `blocks` is a persistent array whose tail is truncated and
38
+ * re-pushed instead of being rebuilt with a `filter` callback every chunk.
34
39
  */
35
40
  export type ParseBlocksCache = {
41
+ /** the content the cache describes (held by reference, never copied); '' = cold */
36
42
  content: string;
37
43
  /** every block token's raw (including space/footnote tokens) in document order */
38
44
  raws: string[];
39
45
  /** parallel to raws: whether the token is part of the rendered block list */
40
46
  keep: boolean[];
47
+ /** prefix sums: offsets[i] = start index of raws[i]; offsets[raws.length] = content.length */
48
+ offsets: number[];
49
+ /** prefix sums: keptBefore[i] = number of kept tokens among raws[0..i) */
50
+ keptBefore: number[];
51
+ /** persistent list of kept raws; only its tail is rewritten on append */
52
+ blocks: string[];
41
53
  };
42
54
  export declare const createParseBlocksCache: () => ParseBlocksCache;
43
55
  export declare const parseBlocks: (markdown: string, extensions?: Extension[], cache?: ParseBlocksCache) => string[];