1
- > [ !WARNING ]
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
+ > [ !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 for a version of this README with highlighted code blocks showcasing several Shiki themes!] ( https://react-shiki.vercel.app/ )
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] [add | install] react-shiki
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 , { isInlineCode , type Element } from " react-shiki" ;
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
- const isInline: boolean | undefined = node ? isInlineCode (node ) : undefined ;
99
-
100
- return ! isInline ? (
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
- This works great for highlighting in realtime on the client, I use it for an LLM chatbot UI, it renders markdown and highlights code in memoized chat messages:
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
-
0 commit comments