react-shiki 0.2.0 → 0.2.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 +121 -19
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
|
-
> [!
|
|
2
|
-
> This package is still a work in progress,
|
|
1
|
+
> [!NOTE]
|
|
2
|
+
> This package is still a work in progress, fully functional but not extensively tested.
|
|
3
3
|
|
|
4
4
|
# 🎨 [react-shiki](https://react-shiki.vercel.app/)
|
|
5
5
|
|
|
6
6
|
Syntax highlighting component and hook for react using [Shiki](https://shiki.matsu.io/)
|
|
7
7
|
|
|
8
|
-
[See the demo page
|
|
8
|
+
[See the demo page with highlighted code blocks showcasing several Shiki themes!](https://react-shiki.vercel.app/)
|
|
9
9
|
|
|
10
10
|
## Features
|
|
11
11
|
|
|
12
12
|
- 🖼️ Provides a `ShikiHighlighter` component for highlighting code as children, as well as a `useShikiHighlighter` hook for more flexibility
|
|
13
13
|
- 🔐 No `dangerouslySetInnerHTML`, output from Shiki is parsed using `html-react-parser`
|
|
14
14
|
- 📦 Supports all Shiki languages and themes
|
|
15
|
+
- 🚰 Supports performant highlighting of streamed code on the client, with optional throttling
|
|
15
16
|
- 📚 Includes minimal default styles for code blocks
|
|
16
17
|
- 🚀 Shiki dynamically imports only the languages and themes used on a page, optimizing for performance
|
|
17
18
|
- 🖥️ `ShikiHighlighter` component displays a language label for each code block when `showLanguage` is set to `true` (default)
|
|
@@ -20,7 +21,7 @@ Syntax highlighting component and hook for react using [Shiki](https://shiki.mat
|
|
|
20
21
|
## Installation
|
|
21
22
|
|
|
22
23
|
```bash
|
|
23
|
-
[pnpm|bun|yarn|npm]
|
|
24
|
+
[pnpm|bun|yarn|npm] install react-shiki
|
|
24
25
|
```
|
|
25
26
|
|
|
26
27
|
## Usage
|
|
@@ -74,11 +75,27 @@ function Houston() {
|
|
|
74
75
|
}
|
|
75
76
|
```
|
|
76
77
|
|
|
78
|
+
react-shiki exports `isInlineCode` to check if a code block is inline.
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
const isInline: boolean | undefined = node ? isInlineCode(node) : undefined;
|
|
82
|
+
|
|
83
|
+
return !isInline ? (
|
|
84
|
+
<ShikiHighlighter language={language} theme={"houston"} {...props}>
|
|
85
|
+
{String(children)}
|
|
86
|
+
</ShikiHighlighter>
|
|
87
|
+
) : (
|
|
88
|
+
<code className={className} {...props}>
|
|
89
|
+
{children}
|
|
90
|
+
</code>
|
|
91
|
+
);
|
|
92
|
+
```
|
|
93
|
+
|
|
77
94
|
It can also be used with `react-markdown`:
|
|
78
95
|
|
|
79
96
|
```tsx
|
|
80
97
|
import type { ReactNode } from "react";
|
|
81
|
-
import ShikiHighlighter, {
|
|
98
|
+
import ShikiHighlighter, { type Element } from "react-shiki";
|
|
82
99
|
|
|
83
100
|
interface CodeHighlightProps {
|
|
84
101
|
className?: string | undefined;
|
|
@@ -95,17 +112,9 @@ export const CodeHighlight = ({
|
|
|
95
112
|
const match = className?.match(/language-(\w+)/);
|
|
96
113
|
const language = match ? match[1] : undefined;
|
|
97
114
|
|
|
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
|
-
);
|
|
115
|
+
<ShikiHighlighter language={language} theme={"houston"} {...props}>
|
|
116
|
+
{String(children)}
|
|
117
|
+
</ShikiHighlighter>;
|
|
109
118
|
};
|
|
110
119
|
```
|
|
111
120
|
|
|
@@ -124,9 +133,103 @@ import { CodeHighlight } from "./CodeHighlight";
|
|
|
124
133
|
</ReactMarkdown>;
|
|
125
134
|
```
|
|
126
135
|
|
|
127
|
-
|
|
136
|
+
### Custom themes
|
|
128
137
|
|
|
129
|
-
```tsx
|
|
138
|
+
```tsx:title=CodeHighlight.tsx
|
|
139
|
+
import tokyoNight from '@styles/tokyo-night.mjs';
|
|
140
|
+
|
|
141
|
+
<ShikiHighlighter language="tsx" theme={tokyoNight}>
|
|
142
|
+
{String(code)}
|
|
143
|
+
</ShikiHighlighter>;
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Client-side highlighting
|
|
147
|
+
|
|
148
|
+
This works great for highlighting in real-time on the client, I use it for an LLM chatbot UI, it renders markdown and highlights code in memoized chat messages. It also Supports an optional delay between highlights for throttling.
|
|
149
|
+
|
|
150
|
+
Using `useShikiHighlighter` hook:
|
|
151
|
+
|
|
152
|
+
```tsx title=CodeHighlight.tsx
|
|
153
|
+
import type { ReactNode } from "react";
|
|
154
|
+
import { isInlineCode, useShikiHighlighter, type Element } from "react-shiki";
|
|
155
|
+
import tokyoNight from "@styles/tokyo-night.mjs";
|
|
156
|
+
|
|
157
|
+
interface CodeHighlightProps {
|
|
158
|
+
className?: string | undefined;
|
|
159
|
+
children?: ReactNode | undefined;
|
|
160
|
+
node?: Element | undefined;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export const CodeHighlight = ({
|
|
164
|
+
className,
|
|
165
|
+
children,
|
|
166
|
+
node,
|
|
167
|
+
...props
|
|
168
|
+
}: CodeHighlightProps) => {
|
|
169
|
+
const code = String(children);
|
|
170
|
+
const language = className?.match(/language-(\w+)/)?.[1];
|
|
171
|
+
|
|
172
|
+
const isInline = node ? isInlineCode(node) : false;
|
|
173
|
+
|
|
174
|
+
const highlightedCode = useShikiHighlighter(language, code, tokyoNight, {
|
|
175
|
+
delay: 150,
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
return !isInline ? (
|
|
179
|
+
<div className="shiki not-prose relative [&_pre]:overflow-auto [&_pre]:rounded-lg [&_pre]:px-6 [&_pre]:py-5">
|
|
180
|
+
{language ? (
|
|
181
|
+
<span className="absolute right-3 top-2 text-xs tracking-tighter text-muted-foreground/85">
|
|
182
|
+
{language}
|
|
183
|
+
</span>
|
|
184
|
+
) : null}
|
|
185
|
+
{highlightedCode}
|
|
186
|
+
</div>
|
|
187
|
+
) : (
|
|
188
|
+
<code className={className} {...props}>
|
|
189
|
+
{children}
|
|
190
|
+
</code>
|
|
191
|
+
);
|
|
192
|
+
};
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Or using the `ShikiHighlighter` component:
|
|
196
|
+
|
|
197
|
+
```tsx title=CodeHighlight.tsx
|
|
198
|
+
import type { ReactNode } from "react";
|
|
199
|
+
import ShikiHighlighter, { isInlineCode, type Element } from "react-shiki";
|
|
200
|
+
|
|
201
|
+
interface CodeHighlightProps {
|
|
202
|
+
className?: string | undefined;
|
|
203
|
+
children?: ReactNode | undefined;
|
|
204
|
+
node?: Element | undefined;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export const CodeHighlight = ({
|
|
208
|
+
className,
|
|
209
|
+
children,
|
|
210
|
+
node,
|
|
211
|
+
...props
|
|
212
|
+
}: CodeHighlightProps): JSX.Element => {
|
|
213
|
+
const match = className?.match(/language-(\w+)/);
|
|
214
|
+
const language = match ? match[1] : undefined;
|
|
215
|
+
|
|
216
|
+
const isInline: boolean | undefined = node ? isInlineCode(node) : undefined;
|
|
217
|
+
|
|
218
|
+
return !isInline ? (
|
|
219
|
+
<ShikiHighlighter language={language} theme={"houston"} {...props}>
|
|
220
|
+
{String(children)}
|
|
221
|
+
</ShikiHighlighter>
|
|
222
|
+
) : (
|
|
223
|
+
<code className={className} {...props}>
|
|
224
|
+
{children}
|
|
225
|
+
</code>
|
|
226
|
+
);
|
|
227
|
+
};
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Passed to `react-markdown` as a code component in memoized chat messages:
|
|
231
|
+
|
|
232
|
+
```tsx title=ChatMessages.tsx
|
|
130
233
|
const RenderedMessage = React.memo(({ message }: { message: Message }) => (
|
|
131
234
|
<div className={cn(messageStyles[message.role])}>
|
|
132
235
|
<ReactMarkdown components={{ code: CodeHighlight }}>
|
|
@@ -145,4 +248,3 @@ export const ChatMessages = ({ messages }: { messages: Message[] }) => {
|
|
|
145
248
|
);
|
|
146
249
|
};
|
|
147
250
|
```
|
|
148
|
-
|