react-shiki 0.2.0 → 0.2.2
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 +198 -37
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,41 +1,68 @@
|
|
|
1
|
-
|
|
2
|
-
> This package is still a work in progress, it is not yet recommended for production use. Contributions are welcome! My goal is to eventually build this out as a near-drop-in replacement for `react-syntax-highlighter`
|
|
1
|
+
# 🎨 [react-shiki](https://npmjs.com/react-shiki)
|
|
3
2
|
|
|
4
|
-
|
|
3
|
+
> [!NOTE]
|
|
4
|
+
> This package is still a work in progress, fully functional but not
|
|
5
|
+
> extensively tested.
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
Performant server and client side syntax highlighting component + hook
|
|
8
|
+
for react using [Shiki](https://shiki.matsu.io/)
|
|
7
9
|
|
|
8
|
-
[See the demo page
|
|
10
|
+
[See the demo page with highlighted code blocks showcasing several Shiki themes!](https://react-shiki.vercel.app/)
|
|
11
|
+
|
|
12
|
+
<!--toc:start-->
|
|
13
|
+
|
|
14
|
+
- 🎨 [react-shiki](https://npmjs.com/react-shiki)
|
|
15
|
+
- [Features](#features)
|
|
16
|
+
- [Installation](#installation)
|
|
17
|
+
- [Usage](#usage)
|
|
18
|
+
- [`react-markdown`](#react-markdown)
|
|
19
|
+
- [Custom themes](#custom-themes)
|
|
20
|
+
- [Client-side highlighting](#client-side-highlighting)
|
|
21
|
+
- [Throttling real-time highlighting](#throttling-real-time-highlighting)
|
|
22
|
+
- [Streaming and LLM chat UI](#streaming-and-llm-chat-ui)
|
|
23
|
+
<!--toc:end-->
|
|
9
24
|
|
|
10
25
|
## Features
|
|
11
26
|
|
|
12
|
-
- 🖼️ Provides a `ShikiHighlighter` component for highlighting code as children,
|
|
27
|
+
- 🖼️ Provides a `ShikiHighlighter` component for highlighting code as children,
|
|
28
|
+
as well as a `useShikiHighlighter` hook for more flexibility
|
|
13
29
|
- 🔐 No `dangerouslySetInnerHTML`, output from Shiki is parsed using `html-react-parser`
|
|
14
|
-
- 📦 Supports all Shiki languages and themes
|
|
30
|
+
- 📦 Supports all Shiki languages and themes in addition to
|
|
31
|
+
- 🖌️ Full support for custom TextMate themes in a JavaScript object format
|
|
32
|
+
- 🚰 Performant highlighting of streamed code on the client, with optional throttling
|
|
15
33
|
- 📚 Includes minimal default styles for code blocks
|
|
16
|
-
- 🚀 Shiki dynamically imports only the languages and themes used on a page,
|
|
17
|
-
|
|
18
|
-
-
|
|
34
|
+
- 🚀 Shiki dynamically imports only the languages and themes used on a page,
|
|
35
|
+
optimizing for performance
|
|
36
|
+
- 🖥️ `ShikiHighlighter` component displays a language label for each code block
|
|
37
|
+
when `showLanguage` is set to `true` (default)
|
|
38
|
+
- 🎨 Users can customize the styling of the generated code blocks by passing
|
|
39
|
+
a `style` object or a `className`
|
|
19
40
|
|
|
20
41
|
## Installation
|
|
21
42
|
|
|
22
43
|
```bash
|
|
23
|
-
[pnpm|bun|yarn|npm]
|
|
44
|
+
[pnpm|bun|yarn|npm] install react-shiki
|
|
24
45
|
```
|
|
25
46
|
|
|
26
47
|
## Usage
|
|
27
48
|
|
|
28
|
-
You can use the `ShikiHighlighter` component, or the `useShikiHighlighter` hook
|
|
49
|
+
You can use the `ShikiHighlighter` component, or the `useShikiHighlighter` hook
|
|
50
|
+
to highlight code.
|
|
29
51
|
|
|
30
|
-
`useShikiHighlighter` is a hook that takes in the code to be highlighted, the
|
|
52
|
+
`useShikiHighlighter` is a hook that takes in the code to be highlighted, the
|
|
53
|
+
language, and the theme, and returns the highlighted code as React elements.
|
|
54
|
+
It's useful for users who want full control over the rendering of highlighted
|
|
55
|
+
code.
|
|
31
56
|
|
|
32
57
|
```tsx
|
|
33
|
-
const highlightedCode = useShikiHighlighter(code, language, theme);
|
|
58
|
+
const highlightedCode = useShikiHighlighter(code, language, theme, options);
|
|
34
59
|
```
|
|
35
60
|
|
|
36
|
-
The `ShikiHighlighter` component is imported in your project, with the code to
|
|
61
|
+
The `ShikiHighlighter` component is imported in your project, with the code to
|
|
62
|
+
be highlighted passed as it's children.
|
|
37
63
|
|
|
38
|
-
Shiki automatically handles dynamically importing only the languages and themes
|
|
64
|
+
Shiki automatically handles dynamically importing only the languages and themes
|
|
65
|
+
used on the page.
|
|
39
66
|
|
|
40
67
|
```tsx
|
|
41
68
|
function CodeBlock() {
|
|
@@ -49,11 +76,15 @@ function CodeBlock() {
|
|
|
49
76
|
|
|
50
77
|
The component accepts several props in addition to language and theme:
|
|
51
78
|
|
|
52
|
-
- `showLanguage: boolean` - Shows the language name in the top right corner of
|
|
53
|
-
|
|
54
|
-
- `
|
|
55
|
-
|
|
56
|
-
- `
|
|
79
|
+
- `showLanguage: boolean` - Shows the language name in the top right corner of
|
|
80
|
+
the code block
|
|
81
|
+
- `addDefaultStyles`: boolean - Adds default styles (padding, overflow handling,
|
|
82
|
+
and border-radius) to the code block
|
|
83
|
+
- `as: string` - The component to be rendered. Defaults to 'pre'
|
|
84
|
+
- `delay: number` - Delay between highlights in milliseconds, useful for throttling
|
|
85
|
+
rapid highlighting on the client
|
|
86
|
+
- `className: string` - Class name to be passed to the component
|
|
87
|
+
- `style: object` - Style object to be passed to the component
|
|
57
88
|
|
|
58
89
|
```tsx
|
|
59
90
|
function Houston() {
|
|
@@ -74,11 +105,27 @@ function Houston() {
|
|
|
74
105
|
}
|
|
75
106
|
```
|
|
76
107
|
|
|
77
|
-
|
|
108
|
+
**react-shiki** exports `isInlineCode` to check if a code block is inline:
|
|
109
|
+
|
|
110
|
+
```tsx
|
|
111
|
+
const isInline: boolean | undefined = node ? isInlineCode(node) : undefined;
|
|
112
|
+
|
|
113
|
+
return !isInline ? (
|
|
114
|
+
<ShikiHighlighter language={language} theme={"houston"} {...props}>
|
|
115
|
+
{String(children)}
|
|
116
|
+
</ShikiHighlighter>
|
|
117
|
+
) : (
|
|
118
|
+
<code className={className} {...props}>
|
|
119
|
+
{children}
|
|
120
|
+
</code>
|
|
121
|
+
);
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### `react-markdown`
|
|
78
125
|
|
|
79
126
|
```tsx
|
|
80
127
|
import type { ReactNode } from "react";
|
|
81
|
-
import ShikiHighlighter, {
|
|
128
|
+
import ShikiHighlighter, { type Element } from "react-shiki";
|
|
82
129
|
|
|
83
130
|
interface CodeHighlightProps {
|
|
84
131
|
className?: string | undefined;
|
|
@@ -95,21 +142,13 @@ export const CodeHighlight = ({
|
|
|
95
142
|
const match = className?.match(/language-(\w+)/);
|
|
96
143
|
const language = match ? match[1] : undefined;
|
|
97
144
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
<ShikiHighlighter language={language} theme={"houston"} {...props}>
|
|
102
|
-
{String(children)}
|
|
103
|
-
</ShikiHighlighter>
|
|
104
|
-
) : (
|
|
105
|
-
<code className={className} {...props}>
|
|
106
|
-
{children}
|
|
107
|
-
</code>
|
|
108
|
-
);
|
|
145
|
+
<ShikiHighlighter language={language} theme={"houston"} {...props}>
|
|
146
|
+
{String(children)}
|
|
147
|
+
</ShikiHighlighter>;
|
|
109
148
|
};
|
|
110
149
|
```
|
|
111
150
|
|
|
112
|
-
Pass CodeHighlight to `react-markdown` as a code component:
|
|
151
|
+
Pass `CodeHighlight` to `react-markdown` as a code component:
|
|
113
152
|
|
|
114
153
|
```tsx
|
|
115
154
|
import ReactMarkdown from "react-markdown";
|
|
@@ -124,9 +163,132 @@ import { CodeHighlight } from "./CodeHighlight";
|
|
|
124
163
|
</ReactMarkdown>;
|
|
125
164
|
```
|
|
126
165
|
|
|
127
|
-
|
|
166
|
+
### Custom themes
|
|
167
|
+
|
|
168
|
+
```tsx:title=CodeHighlight.tsx
|
|
169
|
+
import tokyoNight from '@styles/tokyo-night.mjs';
|
|
170
|
+
|
|
171
|
+
<ShikiHighlighter language="tsx" theme={tokyoNight}>
|
|
172
|
+
{String(code)}
|
|
173
|
+
</ShikiHighlighter>;
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Client-side highlighting
|
|
177
|
+
|
|
178
|
+
react-shiki supports performance-optimized highlighting on the client.
|
|
179
|
+
|
|
180
|
+
### Throttling real-time highlighting
|
|
181
|
+
|
|
182
|
+
Throttling real-time highlighting on the client is possible with the
|
|
183
|
+
`delay` option.
|
|
128
184
|
|
|
129
185
|
```tsx
|
|
186
|
+
const highlightedCode = useShikiHighlighter(code, language, theme, {
|
|
187
|
+
delay: 150,
|
|
188
|
+
});
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Streaming and LLM chat UI
|
|
192
|
+
|
|
193
|
+
react-shiki can be used to highlight streamed code from LLM responses in real-time.
|
|
194
|
+
|
|
195
|
+
I use it for an
|
|
196
|
+
LLM chatbot UI, it renders markdown and highlights code in memoized chat messages.
|
|
197
|
+
|
|
198
|
+
Using `useShikiHighlighter` hook:
|
|
199
|
+
|
|
200
|
+
```tsx title=CodeHighlight.tsx
|
|
201
|
+
import type { ReactNode } from "react";
|
|
202
|
+
import { isInlineCode, useShikiHighlighter, type Element } from "react-shiki";
|
|
203
|
+
import tokyoNight from "@styles/tokyo-night.mjs";
|
|
204
|
+
|
|
205
|
+
interface CodeHighlightProps {
|
|
206
|
+
className?: string | undefined;
|
|
207
|
+
children?: ReactNode | undefined;
|
|
208
|
+
node?: Element | undefined;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export const CodeHighlight = ({
|
|
212
|
+
className,
|
|
213
|
+
children,
|
|
214
|
+
node,
|
|
215
|
+
...props
|
|
216
|
+
}: CodeHighlightProps) => {
|
|
217
|
+
const code = String(children);
|
|
218
|
+
const language = className?.match(/language-(\w+)/)?.[1];
|
|
219
|
+
|
|
220
|
+
const isInline = node ? isInlineCode(node) : false;
|
|
221
|
+
|
|
222
|
+
const highlightedCode = useShikiHighlighter(language, code, tokyoNight, {
|
|
223
|
+
delay: 150,
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
return !isInline ? (
|
|
227
|
+
<div
|
|
228
|
+
className="shiki not-prose relative [&_pre]:overflow-auto
|
|
229
|
+
[&_pre]:rounded-lg [&_pre]:px-6 [&_pre]:py-5"
|
|
230
|
+
>
|
|
231
|
+
{language ? (
|
|
232
|
+
<span
|
|
233
|
+
className="absolute right-3 top-2 text-xs tracking-tighter
|
|
234
|
+
text-muted-foreground/85"
|
|
235
|
+
>
|
|
236
|
+
{language}
|
|
237
|
+
</span>
|
|
238
|
+
) : null}
|
|
239
|
+
{highlightedCode}
|
|
240
|
+
</div>
|
|
241
|
+
) : (
|
|
242
|
+
<code className={className} {...props}>
|
|
243
|
+
{children}
|
|
244
|
+
</code>
|
|
245
|
+
);
|
|
246
|
+
};
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Or using the `ShikiHighlighter` component:
|
|
250
|
+
|
|
251
|
+
```tsx title=CodeHighlight.tsx
|
|
252
|
+
import type { ReactNode } from "react";
|
|
253
|
+
import ShikiHighlighter, { isInlineCode, type Element } from "react-shiki";
|
|
254
|
+
|
|
255
|
+
interface CodeHighlightProps {
|
|
256
|
+
className?: string | undefined;
|
|
257
|
+
children?: ReactNode | undefined;
|
|
258
|
+
node?: Element | undefined;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export const CodeHighlight = ({
|
|
262
|
+
className,
|
|
263
|
+
children,
|
|
264
|
+
node,
|
|
265
|
+
...props
|
|
266
|
+
}: CodeHighlightProps): JSX.Element => {
|
|
267
|
+
const match = className?.match(/language-(\w+)/);
|
|
268
|
+
const language = match ? match[1] : undefined;
|
|
269
|
+
|
|
270
|
+
const isInline: boolean | undefined = node ? isInlineCode(node) : undefined;
|
|
271
|
+
|
|
272
|
+
return !isInline ? (
|
|
273
|
+
<ShikiHighlighter
|
|
274
|
+
language={language}
|
|
275
|
+
theme={"houston"}
|
|
276
|
+
delay={150}
|
|
277
|
+
{...props}
|
|
278
|
+
>
|
|
279
|
+
{String(children)}
|
|
280
|
+
</ShikiHighlighter>
|
|
281
|
+
) : (
|
|
282
|
+
<code className={className} {...props}>
|
|
283
|
+
{children}
|
|
284
|
+
</code>
|
|
285
|
+
);
|
|
286
|
+
};
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
Passed to `react-markdown` as a `code` component in memo-ized chat messages:
|
|
290
|
+
|
|
291
|
+
```tsx title=ChatMessages.tsx
|
|
130
292
|
const RenderedMessage = React.memo(({ message }: { message: Message }) => (
|
|
131
293
|
<div className={cn(messageStyles[message.role])}>
|
|
132
294
|
<ReactMarkdown components={{ code: CodeHighlight }}>
|
|
@@ -145,4 +307,3 @@ export const ChatMessages = ({ messages }: { messages: Message[] }) => {
|
|
|
145
307
|
);
|
|
146
308
|
};
|
|
147
309
|
```
|
|
148
|
-
|