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.
@@ -1,219 +1,34 @@
1
- import { SvelteMap } from 'svelte/reactivity';
2
- import { supportedLanguages, createLanguageSet, bundledLanguagesInfo } from './bundledLanguages.js';
3
- import { createHighlighterCore } from 'shiki/core';
4
- import { createJavaScriptRegexEngine } from 'shiki/engine/javascript';
5
- // Import default themes directly
6
- import githubDark from '@shikijs/themes/github-dark';
7
- import githubLight from '@shikijs/themes/github-light';
8
- // Default themes that are always loaded
9
- const DEFAULT_THEMES = {
10
- 'github-dark': githubDark,
11
- 'github-light': githubLight
12
- };
13
- class HighlighterManager {
14
- loadedLanguages = new SvelteMap();
15
- highlighter = $state(null);
16
- customLanguages;
17
- languageLoaders;
18
- additionalThemes;
19
- constructor(languages, additionalThemes, additionalLanguages) {
20
- // Store additional themes (will be loaded with highlighter)
21
- this.additionalThemes = additionalThemes || {};
22
- // Merge languages: default + additional
23
- const allLanguages = additionalLanguages ? [...languages, ...additionalLanguages] : languages;
24
- this.languageLoaders = new Map();
25
- allLanguages.forEach((l) => {
26
- this.languageLoaders.set(l.id, l.import);
27
- if (l.aliases) {
28
- l.aliases.forEach((alias) => this.languageLoaders.set(alias, l.import));
29
- }
30
- });
31
- // Build custom languages set for validation
32
- if (additionalLanguages) {
33
- const additionalSet = createLanguageSet(additionalLanguages);
34
- this.customLanguages = new Set([...supportedLanguages, ...additionalSet]);
35
- }
36
- else {
37
- this.customLanguages = supportedLanguages;
38
- }
39
- if (typeof window !== 'undefined') {
40
- Object.assign(window, {
41
- STREAMDOWN_HIGHLIGHTER: this
42
- });
43
- }
44
- }
45
- async loadHighlighter() {
46
- if (this.highlighter instanceof Promise) {
47
- return this.highlighter;
48
- }
49
- else if (!this.highlighter) {
50
- this.highlighter = new Promise(async (resolve, reject) => {
51
- try {
52
- const engine = createJavaScriptRegexEngine({ forgiving: true });
53
- // Load default themes + any additional themes immediately
54
- const allThemes = [
55
- ...Object.values(DEFAULT_THEMES),
56
- ...Object.values(this.additionalThemes)
57
- ];
58
- const highlighter = await createHighlighterCore({
59
- themes: allThemes,
60
- langs: [],
61
- engine
62
- });
63
- this.highlighter = highlighter;
64
- resolve(highlighter);
65
- }
66
- catch (error) {
67
- reject(error);
68
- }
69
- });
70
- return this.highlighter;
71
- }
72
- else {
73
- return this.highlighter;
74
- }
75
- }
76
- isThemeAvailable(theme) {
77
- return theme in DEFAULT_THEMES || theme in this.additionalThemes;
1
+ import { createHighlighter, defaultHighlighter, allLanguages } from '@tanstack/highlight';
2
+ const highlighters = new WeakMap();
3
+ const getHighlighter = (extra) => {
4
+ if (!extra?.length)
5
+ return defaultHighlighter;
6
+ let highlighter = highlighters.get(extra);
7
+ if (!highlighter) {
8
+ highlighter = createHighlighter({ languages: [...allLanguages, ...extra] });
9
+ highlighters.set(extra, highlighter);
78
10
  }
79
- async loadLanguage(language, highlighter) {
80
- // Skip loading if language is not supported
81
- if (!this.isLanguageSupported(language)) {
82
- return;
83
- }
84
- const languageLoader = this.loadedLanguages.get(language);
85
- if (languageLoader instanceof Promise) {
86
- await languageLoader;
87
- }
88
- else if (!languageLoader) {
89
- const loader = this.languageLoaders.get(language);
90
- if (!loader) {
91
- // Language not available, mark as failed
92
- this.loadedLanguages.set(language, false);
93
- return;
94
- }
95
- const languageLoaderPromise = loader()
96
- .then((langModule) => {
97
- const langObj = langModule.default || langModule;
98
- return highlighter.loadLanguage(langObj);
99
- })
100
- .then(() => {
101
- this.loadedLanguages.set(language, true);
102
- })
103
- .catch((err) => {
104
- this.loadedLanguages.set(language, false);
105
- throw err;
106
- });
107
- this.loadedLanguages.set(language, languageLoaderPromise);
108
- await languageLoaderPromise;
109
- }
110
- }
111
- isLanguageSupported = (language) => {
112
- return this.customLanguages.has(language);
113
- };
114
- isReady(theme, language) {
115
- // Check if theme is available
116
- if (!this.isThemeAvailable(theme)) {
117
- return false;
118
- }
119
- // For unsupported languages, we don't need to load anything (will use plaintext)
120
- if (!language || !this.isLanguageSupported(language)) {
121
- return !!this.highlighter && !(this.highlighter instanceof Promise);
122
- }
123
- return (!!this.highlighter &&
124
- !(this.highlighter instanceof Promise) &&
125
- this.loadedLanguages.get(language) === true);
126
- }
127
- /**
128
- * Ensures the highlighter is ready for the given theme and language.
129
- */
130
- async load(theme, language) {
131
- // For unsupported languages, only need to load highlighter (themes are pre-loaded)
132
- if (!language || !this.isLanguageSupported(language)) {
133
- if (this.highlighter !== null && !(this.highlighter instanceof Promise)) {
134
- return;
135
- }
136
- await this.loadHighlighter();
137
- return;
138
- }
139
- const languageLoader = this.loadedLanguages.get(language);
140
- if (this.highlighter !== null &&
141
- !(this.highlighter instanceof Promise) &&
142
- languageLoader === true) {
143
- return;
144
- }
145
- const highlighter = await this.loadHighlighter();
146
- await this.loadLanguage(language, highlighter);
147
- }
148
- /**
149
- * Highlights code synchronously. Must call isReady() first.
150
- * Returns plaintext tokens for unsupported languages.
151
- */
152
- highlightCode(code, language, theme) {
153
- try {
154
- const highlighter = this.highlighter;
155
- if (!highlighter || highlighter instanceof Promise) {
156
- // Return plaintext tokens when highlighter is not ready
157
- return code.split('\n').map((line) => [
158
- {
159
- content: line,
160
- color: undefined,
161
- bgColor: undefined
162
- }
163
- ]);
164
- }
165
- // For unsupported languages, return plaintext tokens
166
- if (!language || !this.isLanguageSupported(language)) {
167
- return code.split('\n').map((line) => [
168
- {
169
- content: line,
170
- color: undefined,
171
- bgColor: undefined
172
- }
173
- ]);
174
- }
175
- const tokens = highlighter.codeToTokensBase(code, {
176
- lang: language,
177
- theme
178
- });
179
- return tokens;
180
- }
181
- catch (error) {
182
- // Return plaintext tokens on error
183
- return code.split('\n').map((line) => [
184
- {
185
- content: line,
186
- color: undefined,
187
- bgColor: undefined
188
- }
189
- ]);
190
- }
191
- }
192
- static create(languages = bundledLanguagesInfo, additionalThemes, additionalLanguages) {
193
- if (typeof window !== 'undefined' && 'STREAMDOWN_HIGHLIGHTER' in window) {
194
- const previousHighlighter = window.STREAMDOWN_HIGHLIGHTER;
195
- // Merge additional themes
196
- if (additionalThemes) {
197
- Object.assign(previousHighlighter.additionalThemes, additionalThemes);
198
- }
199
- // Merge additional languages with existing set
200
- if (additionalLanguages) {
201
- additionalLanguages.forEach((lang) => {
202
- previousHighlighter.languageLoaders.set(lang.id, lang.import);
203
- if (lang.aliases) {
204
- lang.aliases.forEach((alias) => previousHighlighter.languageLoaders.set(alias, lang.import));
205
- }
206
- });
207
- const additionalSet = createLanguageSet(additionalLanguages);
208
- additionalSet.forEach((lang) => previousHighlighter.customLanguages.add(lang));
209
- }
210
- return previousHighlighter;
11
+ return highlighter;
12
+ };
13
+ /**
14
+ * Tokenizes code into lines of tokens. Synchronous and isomorphic: works during SSR.
15
+ * Unknown languages fall back to plaintext.
16
+ */
17
+ export function highlightLines(code, lang, extra) {
18
+ const tokens = getHighlighter(extra).tokenize(code, { lang }).tokens;
19
+ const lines = [[]];
20
+ for (const token of tokens) {
21
+ const parts = token.value.split('\n');
22
+ for (let i = 0; i < parts.length; i++) {
23
+ if (i > 0)
24
+ lines.push([]);
25
+ // ponytail: empty segments carry no text, so skip them instead of emitting empty spans
26
+ if (parts[i])
27
+ lines[lines.length - 1].push({ ...token, value: parts[i] });
211
28
  }
212
- return new HighlighterManager(languages, additionalThemes, additionalLanguages);
213
29
  }
30
+ return lines;
214
31
  }
215
- // Export the class for those who want to create their own instances
216
- export { HighlighterManager };
217
32
  export const languageExtensionMap = {
218
33
  '1c': '1c',
219
34
  '1c-query': '1cq',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelte-streamdown",
3
- "version": "3.1.2",
3
+ "version": "4.0.0",
4
4
  "packageManager": "pnpm@10.32.1",
5
5
  "repository": {
6
6
  "type": "git",
@@ -94,13 +94,11 @@
94
94
  },
95
95
  "dependencies": {
96
96
  "@floating-ui/dom": "^1.7.4",
97
- "@shikijs/langs": "^3.17.1",
98
- "@shikijs/themes": "^3.17.1",
97
+ "@tanstack/highlight": "^0.1.0",
99
98
  "clsx": "^2.1.1",
100
99
  "katex": "^0.16.22",
101
- "marked": "^16.2.1",
100
+ "marked": "18.0.12",
102
101
  "mermaid": "^11.15.0",
103
- "shiki": "^3.13.0",
104
102
  "tailwind-merge": "^3.3.1"
105
103
  }
106
104
  }
@@ -1,8 +0,0 @@
1
- export type LanguageInfo = {
2
- id: string;
3
- aliases?: string[];
4
- import: () => Promise<any>;
5
- };
6
- export declare const bundledLanguagesInfo: LanguageInfo[];
7
- export declare function createLanguageSet(languages: LanguageInfo[]): Set<string>;
8
- export declare const supportedLanguages: Set<string>;
@@ -1,143 +0,0 @@
1
- export const bundledLanguagesInfo = [
2
- // Web essentials
3
- {
4
- id: 'javascript',
5
- aliases: ['js'],
6
- import: () => import('@shikijs/langs/javascript')
7
- },
8
- {
9
- id: 'typescript',
10
- aliases: ['ts'],
11
- import: () => import('@shikijs/langs/typescript')
12
- },
13
- {
14
- id: 'html',
15
- import: () => import('@shikijs/langs/html')
16
- },
17
- {
18
- id: 'css',
19
- import: () => import('@shikijs/langs/css')
20
- },
21
- {
22
- id: 'json',
23
- import: () => import('@shikijs/langs/json')
24
- },
25
- {
26
- id: 'jsx',
27
- import: () => import('@shikijs/langs/jsx')
28
- },
29
- {
30
- id: 'tsx',
31
- import: () => import('@shikijs/langs/tsx')
32
- },
33
- {
34
- id: 'markdown',
35
- aliases: ['md'],
36
- import: () => import('@shikijs/langs/markdown')
37
- },
38
- {
39
- id: 'yaml',
40
- aliases: ['yml'],
41
- import: () => import('@shikijs/langs/yaml')
42
- },
43
- {
44
- id: 'xml',
45
- import: () => import('@shikijs/langs/xml')
46
- },
47
- // Backend languages
48
- {
49
- id: 'python',
50
- aliases: ['py'],
51
- import: () => import('@shikijs/langs/python')
52
- },
53
- {
54
- id: 'java',
55
- import: () => import('@shikijs/langs/java')
56
- },
57
- {
58
- id: 'go',
59
- import: () => import('@shikijs/langs/go')
60
- },
61
- {
62
- id: 'rust',
63
- aliases: ['rs'],
64
- import: () => import('@shikijs/langs/rust')
65
- },
66
- {
67
- id: 'ruby',
68
- aliases: ['rb'],
69
- import: () => import('@shikijs/langs/ruby')
70
- },
71
- {
72
- id: 'php',
73
- import: () => import('@shikijs/langs/php')
74
- },
75
- {
76
- id: 'c',
77
- import: () => import('@shikijs/langs/c')
78
- },
79
- {
80
- id: 'cpp',
81
- aliases: ['c++'],
82
- import: () => import('@shikijs/langs/cpp')
83
- },
84
- {
85
- id: 'csharp',
86
- aliases: ['c#', 'cs'],
87
- import: () => import('@shikijs/langs/csharp')
88
- },
89
- {
90
- id: 'sql',
91
- import: () => import('@shikijs/langs/sql')
92
- },
93
- {
94
- id: 'swift',
95
- import: () => import('@shikijs/langs/swift')
96
- },
97
- {
98
- id: 'kotlin',
99
- aliases: ['kt', 'kts'],
100
- import: () => import('@shikijs/langs/kotlin')
101
- },
102
- // Config/Shell
103
- {
104
- id: 'shellscript',
105
- aliases: ['bash', 'sh', 'shell', 'zsh'],
106
- import: () => import('@shikijs/langs/shellscript')
107
- },
108
- {
109
- id: 'docker',
110
- aliases: ['dockerfile'],
111
- import: () => import('@shikijs/langs/docker')
112
- },
113
- {
114
- id: 'toml',
115
- import: () => import('@shikijs/langs/toml')
116
- },
117
- {
118
- id: 'graphql',
119
- aliases: ['gql'],
120
- import: () => import('@shikijs/langs/graphql')
121
- },
122
- {
123
- id: 'svelte',
124
- import: () => import('@shikijs/langs/svelte')
125
- },
126
- {
127
- id: 'vue',
128
- import: () => import('@shikijs/langs/vue')
129
- }
130
- ];
131
- // Helper function to create a Set of all language IDs and aliases from language info
132
- export function createLanguageSet(languages) {
133
- const set = new Set();
134
- languages.forEach((lang) => {
135
- set.add(lang.id);
136
- if (lang.aliases) {
137
- lang.aliases.forEach((alias) => set.add(alias));
138
- }
139
- });
140
- return set;
141
- }
142
- // Create a Set of all supported language IDs and aliases for fast lookup (default set)
143
- export const supportedLanguages = createLanguageSet(bundledLanguagesInfo);