react-shiki 0.6.0 → 0.7.1

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
@@ -14,33 +14,38 @@ A performant client-side syntax highlighting component and hook for React, built
14
14
  - [Features](#features)
15
15
  - [Installation](#installation)
16
16
  - [Usage](#usage)
17
+ - [Bundle Options](#bundle-options)
18
+ - [Configuration](#configuration)
17
19
  - [Common Configuration Options](#common-configuration-options)
18
20
  - [Component-specific Props](#component-specific-props)
19
- - [Integration with react-markdown](#integration-with-react-markdown)
20
- - [Handling Inline Code](#handling-inline-code)
21
21
  - [Multi-theme Support](#multi-theme-support)
22
22
  - [Custom Themes](#custom-themes)
23
23
  - [Custom Languages](#custom-languages)
24
24
  - [Preloading Custom Languages](#preloading-custom-languages)
25
25
  - [Custom Transformers](#custom-transformers)
26
- - [Performance](#performance)
27
- - [Throttling Real-time Highlighting](#throttling-real-time-highlighting)
28
- - [Streaming and LLM Chat UI](#streaming-and-llm-chat-ui)
26
+ - [Line Numbers](#line-numbers)
27
+ - [Integration](#integration)
28
+ - [Integration with react-markdown](#integration-with-react-markdown)
29
+ - [Handling Inline Code](#handling-inline-code)
30
+ - [Performance](#performance)
31
+ - [Throttling Real-time Highlighting](#throttling-real-time-highlighting)
32
+ - [Streaming and LLM Chat UI](#streaming-and-llm-chat-ui)
29
33
  <!--toc:end-->
30
34
 
31
35
  ## Features
32
36
 
33
37
  - 🖼️ Provides both a `ShikiHighlighter` component and a `useShikiHighlighter` hook for more flexibility
34
- - 🔐 No `dangerouslySetInnerHTML` - output from Shiki is parsed using `html-react-parser`
35
- - 📦 Supports all built-in Shiki languages and themes
38
+ - 🔐 Shiki output is processed from HAST directly into React elements, no `dangerouslySetInnerHTML` required
39
+ - 📦 Multiple bundle options: Full bundle (~1.2MB gz), web bundle (~695KB gz), or minimal core bundle for fine-grained bundle control
36
40
  - 🖌️ Full support for custom TextMate themes and languages
37
- - 🔧 Supports passing custom Shiki transformers to the highlighter
41
+ - 🔧 Supports passing custom Shiki transformers to the highlighter, in addition to all other options supported by `codeToHast`
38
42
  - 🚰 Performant highlighting of streamed code, with optional throttling
39
43
  - 📚 Includes minimal default styles for code blocks
40
44
  - 🚀 Shiki dynamically imports only the languages and themes used on a page for optimal performance
41
45
  - 🖥️ `ShikiHighlighter` component displays a language label for each code block
42
46
  when `showLanguage` is set to `true` (default)
43
47
  - 🎨 Customizable styling of generated code blocks and language labels
48
+ - 📏 Optional line numbers with customizable starting number and styling
44
49
 
45
50
  ## Installation
46
51
 
@@ -55,7 +60,7 @@ You can use either the `ShikiHighlighter` component or the `useShikiHighlighter`
55
60
  **Using the Component:**
56
61
 
57
62
  ```tsx
58
- import { ShikiHighlighter } from "react-shiki";
63
+ import ShikiHighlighter from "react-shiki";
59
64
 
60
65
  function CodeBlock() {
61
66
  return (
@@ -78,18 +83,89 @@ function CodeBlock({ code, language }) {
78
83
  }
79
84
  ```
80
85
 
86
+ ## Bundle Options
87
+ `react-shiki`, like `shiki`, offers three entry points to balance convenience and bundle optimization:
88
+
89
+ ### `react-shiki` (Full Bundle)
90
+ ```tsx
91
+ import ShikiHighlighter from 'react-shiki';
92
+ ```
93
+ - **Size**: ~6.4MB minified, 1.2MB gzipped
94
+ - **Languages**: All Shiki languages and themes
95
+ - **Use case**: Unknown language requirements, maximum language support
96
+ - **Setup**: Zero configuration required
97
+
98
+ ### `react-shiki/web` (Web Bundle)
99
+ ```tsx
100
+ import ShikiHighlighter from 'react-shiki/web';
101
+ ```
102
+ - **Size**: ~3.8MB minified, 695KB gzipped
103
+ - **Languages**: Web-focused languages (HTML, CSS, JS, TS, JSON, Markdown, Vue, JSX, Svelte)
104
+ - **Use case**: Web applications with balanced size/functionality
105
+ - **Setup**: Drop-in replacement for main entry point
106
+
107
+ ### `react-shiki/core` (Minimal Bundle)
108
+ ```tsx
109
+ import ShikiHighlighter, {
110
+ createHighlighterCore, // re-exported from shiki/core
111
+ createOnigurumaEngine, // re-exported from shiki/engine/oniguruma
112
+ createJavaScriptRegexEngine, // re-exported from shiki/engine/javascript
113
+ } from 'react-shiki/core';
114
+
115
+ // Create custom highlighter with dynamic imports to optimize client-side bundle size
116
+ const highlighter = await createHighlighterCore({
117
+ themes: [import('@shikijs/themes/nord')],
118
+ langs: [import('@shikijs/langs/typescript')],
119
+ engine: createOnigurumaEngine(import('shiki/wasm'))
120
+ // or createJavaScriptRegexEngine()
121
+ });
122
+
123
+ <ShikiHighlighter highlighter={highlighter} language="typescript" theme="nord">
124
+ {code}
125
+ </ShikiHighlighter>
126
+ ```
127
+ - **Size**: Minimal (only what you import)
128
+ - **Languages**: User-defined via custom highlighter
129
+ - **Use case**: Production apps requiring maximum bundle control
130
+ - **Setup**: Requires custom highlighter configuration
131
+ - **Engine options**: Choose JavaScript engine (smaller bundle, faster startup) or Oniguruma (WASM, maximum language support)
132
+
133
+ ### RegExp Engines
134
+
135
+ Shiki offers two built-in engines:
136
+ - **Oniguruma** - default, uses the compiled Oniguruma WebAssembly, and offer maximum language support
137
+ - **JavaScript** - smaller bundle, faster startup, recommended when running highlighting on the client
138
+
139
+ Unlike the Oniguruma engine, the JavaScript engine is [strict by default](https://shiki.style/guide/regex-engines#use-with-unsupported-languages). It will throw an error if it encounters an invalid Oniguruma pattern or a pattern that it cannot convert. If you want best-effort results for unsupported grammars, you can enable the forgiving option to suppress any conversion errors:
140
+
141
+ ```tsx
142
+ createJavaScriptRegexEngine({ forgiving: true });
143
+ ```
144
+
145
+ See [Shiki - RegExp Engines](https://shiki.style/guide/regex-engines) for more info.
146
+
147
+
148
+ ## Configuration
149
+
81
150
  ### Common Configuration Options
82
151
 
83
- | Option | Type | Default | Description |
84
- | ------------------- | ------------------ | --------------- | ------------------------------------------------------------------------- |
85
- | `code` | `string` | - | Code to highlight |
86
- | `language` | `string \| object` | - | Language to highlight, built-in or custom textmate grammer object |
152
+
153
+ | Option | Type | Default | Description |
154
+ | ------------------- | ------------------ | --------------- | ----------------------------------------------------------------------------- |
155
+ | `code` | `string` | - | Code to highlight |
156
+ | `language` | `string \| object` | - | Language to highlight, built-in or custom textmate grammer object |
87
157
  | `theme` | `string \| object` | `'github-dark'` | Single or multi-theme configuration, built-in or custom textmate theme object |
88
158
  | `delay` | `number` | `0` | Delay between highlights (in milliseconds) |
89
- | `transformers` | `array` | `[]` | Custom Shiki transformers for modifying the highlighting output |
90
159
  | `customLanguages` | `array` | `[]` | Array of custom languages to preload |
160
+ | `showLineNumbers` | `boolean` | `false` | Display line numbers alongside code |
161
+ | `startingLineNumber` | `number` | `1` | Starting line number when line numbers are enabled |
162
+ | `transformers` | `array` | `[]` | Custom Shiki transformers for modifying the highlighting output |
91
163
  | `cssVariablePrefix` | `string` | `'--shiki'` | Prefix for CSS variables storing theme colors |
92
164
  | `defaultColor` | `string \| false` | `'light'` | Default theme mode when using multiple themes, can also disable default theme |
165
+ | `tabindex` | `number` | `0` | Tab index for the code block |
166
+ | `decorations` | `array` | `[]` | Custom decorations to wrap the highlighted tokens with |
167
+ | `structure` | `string` | `classic` | The structure of the generated HAST and HTML - `classic` or `inline` |
168
+ | [`codeToHastOptions`](https://github.com/shikijs/shiki/blob/main/packages/types/src/options.ts#L121) | - | - | All other options supported by Shiki's `codeToHast` |
93
169
 
94
170
  ### Component-specific Props
95
171
 
@@ -105,13 +181,188 @@ The `ShikiHighlighter` component offers minimal built-in styling and customizati
105
181
  | `style` | `object` | - | Inline style object for the code block |
106
182
  | `langStyle` | `object` | - | Inline style object for the language label |
107
183
 
184
+ ### Multi-theme Support
185
+
186
+ To use multiple theme modes, pass an object with your multi-theme configuration to the `theme` prop in the `ShikiHighlighter` component:
187
+
188
+ ```tsx
189
+ <ShikiHighlighter
190
+ language="tsx"
191
+ theme={{
192
+ light: "github-light",
193
+ dark: "github-dark",
194
+ dim: "github-dark-dimmed",
195
+ }}
196
+ defaultColor="dark"
197
+ >
198
+ {code.trim()}
199
+ </ShikiHighlighter>
200
+ ```
201
+
202
+ Or, when using the hook, pass it to the `theme` parameter:
203
+
204
+ ```tsx
205
+ const highlightedCode = useShikiHighlighter(
206
+ code,
207
+ "tsx",
208
+ {
209
+ light: "github-light",
210
+ dark: "github-dark",
211
+ dim: "github-dark-dimmed",
212
+ },
213
+ {
214
+ defaultColor: "dark",
215
+ }
216
+ );
217
+ ```
218
+
219
+ See [shiki's documentation](https://shiki.matsu.io/docs/themes) for more information on dual and multi theme support, and for the CSS needed to make the themes reactive to your site's theme.
220
+
221
+ ### Custom Themes
222
+
223
+ Custom themes can be passed as a TextMate theme in JavaScript object. For example, [it should look like this](https://github.com/antfu/textmate-grammars-themes/blob/main/packages/tm-themes/themes/dark-plus.json).
224
+
225
+ ```tsx
226
+ import tokyoNight from "../styles/tokyo-night.json";
227
+
228
+ // Component
229
+ <ShikiHighlighter language="tsx" theme={tokyoNight}>
230
+ {code.trim()}
231
+ </ShikiHighlighter>
232
+
233
+ // Hook
234
+ const highlightedCode = useShikiHighlighter(code, "tsx", tokyoNight);
235
+ ```
236
+
237
+ ### Custom Languages
238
+
239
+ Custom languages should be passed as a TextMate grammar in JavaScript object. For example, [it should look like this](https://github.com/shikijs/textmate-grammars-themes/blob/main/packages/tm-grammars/grammars/typescript.json)
240
+
241
+ ```tsx
242
+ import mcfunction from "../langs/mcfunction.tmLanguage.json";
243
+
244
+ // Component
245
+ <ShikiHighlighter language={mcfunction} theme="github-dark">
246
+ {code.trim()}
247
+ </ShikiHighlighter>
248
+
249
+ // Hook
250
+ const highlightedCode = useShikiHighlighter(code, mcfunction, "github-dark");
251
+ ```
252
+
253
+ #### Preloading Custom Languages
254
+
255
+ For dynamic highlighting scenarios where language selection happens at runtime:
256
+
257
+ ```tsx
258
+ import mcfunction from "../langs/mcfunction.tmLanguage.json";
259
+ import bosque from "../langs/bosque.tmLanguage.json";
260
+
261
+ // Component
262
+ <ShikiHighlighter
263
+ language="typescript"
264
+ theme="github-dark"
265
+ customLanguages={[mcfunction, bosque]}
266
+ >
267
+ {code.trim()}
268
+ </ShikiHighlighter>
269
+
270
+ // Hook
271
+ const highlightedCode = useShikiHighlighter(code, "typescript", "github-dark", {
272
+ customLanguages: [mcfunction, bosque],
273
+ });
274
+ ```
275
+
276
+ ### Custom Transformers
277
+
278
+ ```tsx
279
+ import { customTransformer } from "../utils/shikiTransformers";
280
+
281
+ // Component
282
+ <ShikiHighlighter language="tsx" transformers={[customTransformer]}>
283
+ {code.trim()}
284
+ </ShikiHighlighter>
285
+
286
+ // Hook
287
+ const highlightedCode = useShikiHighlighter(code, "tsx", "github-dark", {
288
+ transformers: [customTransformer],
289
+ });
290
+ ```
291
+
292
+ ### Line Numbers
293
+
294
+ Display line numbers alongside your code, these are CSS-based
295
+ and can be customized with CSS variables:
296
+
297
+ ```tsx
298
+ // Component
299
+ <ShikiHighlighter
300
+ language="javascript"
301
+ theme="github-dark"
302
+ showLineNumbers,
303
+ startingLineNumber={0} // default is 1
304
+ >
305
+ {code}
306
+ </ShikiHighlighter>
307
+
308
+ <ShikiHighlighter
309
+ language="python"
310
+ theme="github-dark"
311
+ showLineNumbers
312
+ startingLineNumber={0}
313
+ >
314
+ {code}
315
+ </ShikiHighlighter>
316
+
317
+ // Hook (import 'react-shiki/css' for line numbers to work)
318
+ const highlightedCode = useShikiHighlighter(code, "javascript", "github-dark", {
319
+ showLineNumbers: true,
320
+ startingLineNumber: 0,
321
+ });
322
+ ```
323
+
324
+ > [!NOTE]
325
+ > When using the hook with line numbers, import the CSS file for the line numbers to work:
326
+ > ```tsx
327
+ > import 'react-shiki/css';
328
+ > ```
329
+ > Or provide your own CSS counter implementation and styles for `.line-numbers` (line `span`) and `.has-line-numbers` (container `code` element)
330
+
331
+ Available CSS variables for customization:
332
+ ```css
333
+ --line-numbers-foreground: rgba(107, 114, 128, 0.5);
334
+ --line-numbers-width: 2ch;
335
+ --line-numbers-padding-left: 0ch;
336
+ --line-numbers-padding-right: 2ch;
337
+ --line-numbers-font-size: inherit;
338
+ --line-numbers-font-weight: inherit;
339
+ --line-numbers-opacity: 1;
340
+ ```
341
+
342
+ You can customize them in your own CSS or by using the style prop on the component:
343
+ ```tsx
344
+ <ShikiHighlighter
345
+ language="javascript"
346
+ theme="github-dark"
347
+ showLineNumbers
348
+ style={{
349
+ '--line-numbers-foreground': '#60a5fa',
350
+ '--line-numbers-width': '3ch'
351
+ }}
352
+ >
353
+ {code}
354
+ </ShikiHighlighter>
355
+ ```
356
+
357
+ ## Integration
358
+
108
359
  ### Integration with react-markdown
109
360
 
110
361
  Create a component to handle syntax highlighting:
111
362
 
112
363
  ```tsx
113
364
  import ReactMarkdown from "react-markdown";
114
- import { ShikiHighlighter, isInlineCode } from "react-shiki";
365
+ import ShikiHighlighter, { isInlineCode } from "react-shiki";
115
366
 
116
367
  const CodeHighlight = ({ className, children, node, ...props }) => {
117
368
  const code = String(children).trim();
@@ -155,7 +406,7 @@ ways to replicate this functionality and API.
155
406
  `react-shiki` exports `isInlineCode` which parses the `node` prop from `react-markdown` and identifies inline code by checking for the absence of newline characters:
156
407
 
157
408
  ```tsx
158
- import { isInlineCode, ShikiHighlighter } from "react-shiki";
409
+ import ShikiHighlighter, { isInlineCode } from "react-shiki";
159
410
 
160
411
  const CodeHighlight = ({ className, children, node, ...props }) => {
161
412
  const match = className?.match(/language-(\w+)/);
@@ -224,114 +475,6 @@ const CodeHighlight = ({
224
475
  };
225
476
  ```
226
477
 
227
- ### Multi-theme Support
228
-
229
- To use multiple theme modes, pass an object with your multi-theme configuration to the `theme` prop in the `ShikiHighlighter` component:
230
-
231
- ```tsx
232
- <ShikiHighlighter
233
- language="tsx"
234
- theme={{
235
- light: "github-light",
236
- dark: "github-dark",
237
- dim: "github-dark-dimmed",
238
- }}
239
- defaultColor="dark"
240
- >
241
- {code.trim()}
242
- </ShikiHighlighter>
243
- ```
244
-
245
- Or, when using the hook, pass it to the `theme` parameter:
246
-
247
- ```tsx
248
- const highlightedCode = useShikiHighlighter(
249
- code,
250
- "tsx",
251
- {
252
- light: "github-light",
253
- dark: "github-dark",
254
- dim: "github-dark-dimmed",
255
- },
256
- {
257
- defaultColor: "dark",
258
- }
259
- );
260
- ```
261
-
262
- See [shiki's documentation](https://shiki.matsu.io/docs/themes) for more information on dual and multi theme support, and for the CSS needed to make the themes reactive to your site's theme.
263
-
264
- ### Custom Themes
265
-
266
- Custom themes can be passed as a TextMate theme in JavaScript object. For example, [it should look like this](https://github.com/antfu/textmate-grammars-themes/blob/main/packages/tm-themes/themes/dark-plus.json).
267
-
268
- ```tsx
269
- import tokyoNight from "../styles/tokyo-night.json";
270
-
271
- // Using the component
272
- <ShikiHighlighter language="tsx" theme={tokyoNight}>
273
- {code.trim()}
274
- </ShikiHighlighter>
275
-
276
- // Using the hook
277
- const highlightedCode = useShikiHighlighter(code, "tsx", tokyoNight);
278
- ```
279
-
280
- ### Custom Languages
281
-
282
- Custom languages should be passed as a TextMate grammar in JavaScript object. For example, [it should look like this](https://github.com/shikijs/textmate-grammars-themes/blob/main/packages/tm-grammars/grammars/typescript.json)
283
-
284
- ```tsx
285
- import mcfunction from "../langs/mcfunction.tmLanguage.json";
286
-
287
- // Using the component
288
- <ShikiHighlighter language={mcfunction} theme="github-dark">
289
- {code.trim()}
290
- </ShikiHighlighter>
291
-
292
- // Using the hook
293
- const highlightedCode = useShikiHighlighter(code, mcfunction, "github-dark");
294
- ```
295
-
296
- #### Preloading Custom Languages
297
-
298
- For dynamic highlighting scenarios where language selection happens at runtime:
299
-
300
- ```tsx
301
- import mcfunction from "../langs/mcfunction.tmLanguage.json";
302
- import bosque from "../langs/bosque.tmLanguage.json";
303
-
304
- // With the component
305
- <ShikiHighlighter
306
- language="typescript"
307
- theme="github-dark"
308
- customLanguages={[mcfunction, bosque]}
309
- >
310
- {code.trim()}
311
- </ShikiHighlighter>
312
-
313
- // With the hook
314
- const highlightedCode = useShikiHighlighter(code, "typescript", "github-dark", {
315
- customLanguages: [mcfunction, bosque],
316
- });
317
- ```
318
-
319
- ### Custom Transformers
320
-
321
- ```tsx
322
- import { customTransformer } from "../utils/shikiTransformers";
323
-
324
- // Using the component
325
- <ShikiHighlighter language="tsx" transformers={[customTransformer]}>
326
- {code.trim()}
327
- </ShikiHighlighter>
328
-
329
- // Using the hook
330
- const highlightedCode = useShikiHighlighter(code, "tsx", "github-dark", {
331
- transformers: [customTransformer],
332
- });
333
- ```
334
-
335
478
  ## Performance
336
479
 
337
480
  ### Throttling Real-time Highlighting
@@ -447,7 +590,7 @@ export const CodeHighlight = ({
447
590
  };
448
591
  ```
449
592
 
450
- Passed to `react-markdown` as a `code` component in memo-ized chat messages:
593
+ Passed to `react-markdown` as a `code` component in memoized chat messages:
451
594
 
452
595
  ```tsx
453
596
  const RenderedMessage = React.memo(({ message }: { message: Message }) => (