react-shiki 0.2.3 → 0.3.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.
- package/README.md +94 -28
- package/dist/index.d.ts +43 -10
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/package.json +12 -4
package/README.md
CHANGED
|
@@ -16,7 +16,9 @@ for react using [Shiki](https://shiki.matsu.io/)
|
|
|
16
16
|
- [Installation](#installation)
|
|
17
17
|
- [Usage](#usage)
|
|
18
18
|
- [`react-markdown`](#react-markdown)
|
|
19
|
+
- [Check if code is inline](#check-if-code-is-inline)
|
|
19
20
|
- [Custom themes](#custom-themes)
|
|
21
|
+
- [Custom transformers](#custom-transformers)
|
|
20
22
|
- [Performance](#performance)
|
|
21
23
|
- [Throttling real-time highlighting](#throttling-real-time-highlighting)
|
|
22
24
|
- [Streaming and LLM chat UI](#streaming-and-llm-chat-ui)
|
|
@@ -27,8 +29,9 @@ for react using [Shiki](https://shiki.matsu.io/)
|
|
|
27
29
|
- 🖼️ Provides a `ShikiHighlighter` component for highlighting code as children,
|
|
28
30
|
as well as a `useShikiHighlighter` hook for more flexibility
|
|
29
31
|
- 🔐 No `dangerouslySetInnerHTML`, output from Shiki is parsed using `html-react-parser`
|
|
30
|
-
- 📦 Supports all Shiki languages and themes
|
|
32
|
+
- 📦 Supports all Shiki languages and themes
|
|
31
33
|
- 🖌️ Full support for custom TextMate themes in a JavaScript object format
|
|
34
|
+
- 🔧 Supports passing custom Shiki transformers to the highlighter
|
|
32
35
|
- 🚰 Performant highlighting of streamed code on the client, with optional throttling
|
|
33
36
|
- 📚 Includes minimal default styles for code blocks
|
|
34
37
|
- 🚀 Shiki dynamically imports only the languages and themes used on a page,
|
|
@@ -74,7 +77,10 @@ function CodeBlock() {
|
|
|
74
77
|
}
|
|
75
78
|
```
|
|
76
79
|
|
|
77
|
-
The `ShikiHighlighter` component will follow a similar API to `react-syntax-highlighter`,
|
|
80
|
+
The `ShikiHighlighter` component will follow a similar API to `react-syntax-highlighter`,
|
|
81
|
+
but uses Shiki and is optimized for performant sequential highlighting. As of now,
|
|
82
|
+
not all of `react-syntax-highlighter` functionality is supported, but the goal of
|
|
83
|
+
this component is to eventually act as a drop in replacement for `react-syntax-highlighter`.
|
|
78
84
|
|
|
79
85
|
The component accepts several props in addition to language and theme:
|
|
80
86
|
|
|
@@ -85,9 +91,10 @@ The component accepts several props in addition to language and theme:
|
|
|
85
91
|
- `as: string` - The component to be rendered. Defaults to 'pre'
|
|
86
92
|
- `delay: number` - Delay between highlights in milliseconds, useful for throttling
|
|
87
93
|
rapid highlighting on the client
|
|
88
|
-
- `className: string` - Class
|
|
94
|
+
- `className: string` - Class names to be passed to the component
|
|
89
95
|
- `style: object` - Inline style object to be passed to the component
|
|
90
96
|
- `langStyle: object` - Inline style object to be passed to the language label
|
|
97
|
+
- `langClassName: string` - Class names to be passed to the language label
|
|
91
98
|
|
|
92
99
|
```tsx
|
|
93
100
|
function Houston() {
|
|
@@ -109,22 +116,6 @@ function Houston() {
|
|
|
109
116
|
}
|
|
110
117
|
```
|
|
111
118
|
|
|
112
|
-
`react-shiki` exports `isInlineCode` to check if a code block is inline:
|
|
113
|
-
|
|
114
|
-
```tsx
|
|
115
|
-
const isInline: boolean | undefined = node ? isInlineCode(node) : undefined;
|
|
116
|
-
|
|
117
|
-
return !isInline ? (
|
|
118
|
-
<ShikiHighlighter language={language} theme={"houston"} {...props}>
|
|
119
|
-
{String(children)}
|
|
120
|
-
</ShikiHighlighter>
|
|
121
|
-
) : (
|
|
122
|
-
<code className={className} {...props}>
|
|
123
|
-
{children}
|
|
124
|
-
</code>
|
|
125
|
-
);
|
|
126
|
-
```
|
|
127
|
-
|
|
128
119
|
### `react-markdown`
|
|
129
120
|
|
|
130
121
|
```tsx
|
|
@@ -147,7 +138,7 @@ export const CodeHighlight = ({
|
|
|
147
138
|
const language = match ? match[1] : undefined;
|
|
148
139
|
|
|
149
140
|
<ShikiHighlighter language={language} theme={"houston"} {...props}>
|
|
150
|
-
{String(children)}
|
|
141
|
+
{String(children).trim()}
|
|
151
142
|
</ShikiHighlighter>;
|
|
152
143
|
};
|
|
153
144
|
```
|
|
@@ -167,9 +158,72 @@ import { CodeHighlight } from "./CodeHighlight";
|
|
|
167
158
|
</ReactMarkdown>;
|
|
168
159
|
```
|
|
169
160
|
|
|
161
|
+
### Check if code is inline
|
|
162
|
+
|
|
163
|
+
There are two ways to check if a code block is inline:
|
|
164
|
+
`react-shiki` exports `isInlineCode`, good but marks multiline inline
|
|
165
|
+
code tags as code blocks.
|
|
166
|
+
|
|
167
|
+
```tsx
|
|
168
|
+
const isInline: boolean | undefined = node ? isInlineCode(node) : undefined;
|
|
169
|
+
|
|
170
|
+
return !isInline ? (
|
|
171
|
+
<ShikiHighlighter language={language} theme={"houston"} {...props}>
|
|
172
|
+
{String(children).trim()}
|
|
173
|
+
</ShikiHighlighter>
|
|
174
|
+
) : (
|
|
175
|
+
<code className={className} {...props}>
|
|
176
|
+
{children}
|
|
177
|
+
</code>
|
|
178
|
+
);
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
`react-shiki` also exports `rehypeInlineCodeProperty`, a more accurate way
|
|
182
|
+
to determine if code is inline.
|
|
183
|
+
It's passed as a rehype plugin to `react-markdown`:
|
|
184
|
+
|
|
185
|
+
```tsx
|
|
186
|
+
import ReactMarkdown from "react-markdown";
|
|
187
|
+
import { rehypeInlineCodeProperty } from "react-shiki";
|
|
188
|
+
|
|
189
|
+
<ReactMarkdown
|
|
190
|
+
rehypePlugins={[rehypeInlineCodeProperty]}
|
|
191
|
+
components={{
|
|
192
|
+
code: CodeHighlight,
|
|
193
|
+
}}
|
|
194
|
+
>
|
|
195
|
+
{markdown}
|
|
196
|
+
</ReactMarkdown>;
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
And can be accessed as a prop:
|
|
200
|
+
|
|
201
|
+
```tsx
|
|
202
|
+
const CodeHighlight = ({
|
|
203
|
+
inline,
|
|
204
|
+
className,
|
|
205
|
+
children,
|
|
206
|
+
node,
|
|
207
|
+
...props
|
|
208
|
+
}: CodeHighlightProps): JSX.Element => {
|
|
209
|
+
const match = className?.match(/language-(\w+)/);
|
|
210
|
+
const language = match ? match[1] : undefined;
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
return !inline ? (
|
|
214
|
+
<ShikiHighlighter language={language} theme={"houston"} {...props}>
|
|
215
|
+
{String(children).trim()}
|
|
216
|
+
</ShikiHighlighter>
|
|
217
|
+
) : (
|
|
218
|
+
<code className={className} {...props}>
|
|
219
|
+
{children}
|
|
220
|
+
</code>
|
|
221
|
+
);
|
|
222
|
+
```
|
|
223
|
+
|
|
170
224
|
### Custom themes
|
|
171
225
|
|
|
172
|
-
```tsx
|
|
226
|
+
```tsx
|
|
173
227
|
import tokyoNight from '@styles/tokyo-night.mjs';
|
|
174
228
|
|
|
175
229
|
<ShikiHighlighter language="tsx" theme={tokyoNight}>
|
|
@@ -177,6 +231,18 @@ import tokyoNight from '@styles/tokyo-night.mjs';
|
|
|
177
231
|
</ShikiHighlighter>;
|
|
178
232
|
```
|
|
179
233
|
|
|
234
|
+
### Custom transformers
|
|
235
|
+
|
|
236
|
+
```tsx
|
|
237
|
+
import { customTransformer } from '@utils/customTransformers';
|
|
238
|
+
|
|
239
|
+
<ShikiHighlighter
|
|
240
|
+
language="tsx"
|
|
241
|
+
transformers={[customTransformer]}>
|
|
242
|
+
{String(code).trim()}
|
|
243
|
+
</ShikiHighlighter>;
|
|
244
|
+
```
|
|
245
|
+
|
|
180
246
|
## Performance
|
|
181
247
|
|
|
182
248
|
`react-shiki` supports performance-optimized highlighting on the client.
|
|
@@ -196,12 +262,12 @@ const highlightedCode = useShikiHighlighter(code, language, theme, {
|
|
|
196
262
|
|
|
197
263
|
`react-shiki` can be used to highlight streamed code from LLM responses in real-time.
|
|
198
264
|
|
|
199
|
-
I use it for an
|
|
200
|
-
|
|
265
|
+
I use it for an LLM chatbot UI, it renders markdown and highlights
|
|
266
|
+
code in memoized chat messages.
|
|
201
267
|
|
|
202
268
|
Using `useShikiHighlighter` hook:
|
|
203
269
|
|
|
204
|
-
```tsx
|
|
270
|
+
```tsx
|
|
205
271
|
import type { ReactNode } from "react";
|
|
206
272
|
import { isInlineCode, useShikiHighlighter, type Element } from "react-shiki";
|
|
207
273
|
import tokyoNight from "@styles/tokyo-night.mjs";
|
|
@@ -218,7 +284,7 @@ export const CodeHighlight = ({
|
|
|
218
284
|
node,
|
|
219
285
|
...props
|
|
220
286
|
}: CodeHighlightProps) => {
|
|
221
|
-
const code = String(children);
|
|
287
|
+
const code = String(children).trim();
|
|
222
288
|
const language = className?.match(/language-(\w+)/)?.[1];
|
|
223
289
|
|
|
224
290
|
const isInline = node ? isInlineCode(node) : false;
|
|
@@ -252,7 +318,7 @@ export const CodeHighlight = ({
|
|
|
252
318
|
|
|
253
319
|
Or using the `ShikiHighlighter` component:
|
|
254
320
|
|
|
255
|
-
```tsx
|
|
321
|
+
```tsx
|
|
256
322
|
import type { ReactNode } from "react";
|
|
257
323
|
import ShikiHighlighter, { isInlineCode, type Element } from "react-shiki";
|
|
258
324
|
|
|
@@ -280,7 +346,7 @@ export const CodeHighlight = ({
|
|
|
280
346
|
delay={150}
|
|
281
347
|
{...props}
|
|
282
348
|
>
|
|
283
|
-
{String(children)}
|
|
349
|
+
{String(children).trim()}
|
|
284
350
|
</ShikiHighlighter>
|
|
285
351
|
) : (
|
|
286
352
|
<code className={className} {...props}>
|
|
@@ -292,7 +358,7 @@ export const CodeHighlight = ({
|
|
|
292
358
|
|
|
293
359
|
Passed to `react-markdown` as a `code` component in memo-ized chat messages:
|
|
294
360
|
|
|
295
|
-
```tsx
|
|
361
|
+
```tsx
|
|
296
362
|
const RenderedMessage = React.memo(({ message }: { message: Message }) => (
|
|
297
363
|
<div className={cn(messageStyles[message.role])}>
|
|
298
364
|
<ReactMarkdown components={{ code: CodeHighlight }}>
|
package/dist/index.d.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import React, { ReactNode } from 'react';
|
|
2
|
-
import { BundledLanguage, ThemeRegistration, BundledTheme } from 'shiki';
|
|
3
|
-
import { Element } from 'hast';
|
|
2
|
+
import { BundledLanguage, SpecialLanguage, ThemeRegistration, BundledTheme, ShikiTransformer } from 'shiki';
|
|
3
|
+
import { Root, Element } from 'hast';
|
|
4
4
|
export { Element } from 'hast';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Languages for syntax highlighting.
|
|
8
8
|
* @see https://shiki.style/languages
|
|
9
9
|
*/
|
|
10
|
-
type Language = BundledLanguage |
|
|
10
|
+
type Language = BundledLanguage | SpecialLanguage | (string & {}) | undefined;
|
|
11
11
|
/**
|
|
12
12
|
* A textmate theme object or a Shiki BundledTheme
|
|
13
13
|
* @see https://shiki.style/themes
|
|
@@ -19,9 +19,13 @@ type Theme = ThemeRegistration | BundledTheme;
|
|
|
19
19
|
type HighlighterOptions = {
|
|
20
20
|
/**
|
|
21
21
|
* Minimum time (in milliseconds) between highlight operations.
|
|
22
|
-
*
|
|
22
|
+
* @default undefined (no throttling)
|
|
23
23
|
*/
|
|
24
24
|
delay?: number;
|
|
25
|
+
/**
|
|
26
|
+
* Custom Shiki transformers to apply to the highlighted code.
|
|
27
|
+
*/
|
|
28
|
+
transformers?: ShikiTransformer[];
|
|
25
29
|
};
|
|
26
30
|
|
|
27
31
|
/**
|
|
@@ -49,6 +53,10 @@ interface ShikiHighlighterProps extends HighlighterOptions {
|
|
|
49
53
|
* @default undefined (no delay)
|
|
50
54
|
*/
|
|
51
55
|
delay?: number;
|
|
56
|
+
/**
|
|
57
|
+
* Pass custom Shiki transformers to the highlighter
|
|
58
|
+
*/
|
|
59
|
+
transformers?: ShikiTransformer[];
|
|
52
60
|
/**
|
|
53
61
|
* Controls the application of default styles to the generated code blocks
|
|
54
62
|
*
|
|
@@ -68,6 +76,10 @@ interface ShikiHighlighterProps extends HighlighterOptions {
|
|
|
68
76
|
* Add custom CSS class names to the generated code block
|
|
69
77
|
*/
|
|
70
78
|
className?: string;
|
|
79
|
+
/**
|
|
80
|
+
* Add custom CSS class names to the language label
|
|
81
|
+
*/
|
|
82
|
+
langClassName?: string;
|
|
71
83
|
/**
|
|
72
84
|
* Whether to show the language label
|
|
73
85
|
* @default true
|
|
@@ -94,14 +106,35 @@ interface ShikiHighlighterProps extends HighlighterOptions {
|
|
|
94
106
|
* </ShikiHighlighter>
|
|
95
107
|
* ```
|
|
96
108
|
*/
|
|
97
|
-
declare const ShikiHighlighter: ({ language, theme, delay, addDefaultStyles, style, langStyle, className, showLanguage, children: code, as: Element, }: ShikiHighlighterProps) => React.ReactElement;
|
|
109
|
+
declare const ShikiHighlighter: ({ language, theme, delay, transformers, addDefaultStyles, style, langStyle, className, langClassName, showLanguage, children: code, as: Element, }: ShikiHighlighterProps) => React.ReactElement;
|
|
98
110
|
|
|
99
111
|
/**
|
|
100
|
-
* A React hook that provides syntax highlighting using Shiki.
|
|
101
|
-
*
|
|
102
|
-
|
|
103
|
-
|
|
112
|
+
* A React hook that provides syntax highlighting using Shiki.
|
|
113
|
+
* Supports optional throttled highlights and custom themes.
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* const highlightedCode = useShikiHighlighter(code, language, theme, {
|
|
117
|
+
* transformers: [removeTabIndexFromPre],
|
|
118
|
+
* delay: 150
|
|
119
|
+
* });
|
|
120
|
+
*/
|
|
121
|
+
declare const useShikiHighlighter: (code: string, lang: Language, theme: Theme, options?: HighlighterOptions) => ReactNode;
|
|
104
122
|
|
|
123
|
+
/**
|
|
124
|
+
* Rehype plugin to add an 'inline' property to <code> elements.
|
|
125
|
+
* Sets an 'inline' property to true if the <code> is not within a <pre> tag.
|
|
126
|
+
*
|
|
127
|
+
* Pass this plugin to the `rehypePlugins` prop of ReactMarkdown.
|
|
128
|
+
* You can then access `inline` as a prop from ReactMarkdown.
|
|
129
|
+
*
|
|
130
|
+
* @example
|
|
131
|
+
* <ReactMarkdown rehypePlugins={[rehypeInlineCodeProperty]} />
|
|
132
|
+
*/
|
|
133
|
+
declare function rehypeInlineCodeProperty(): (tree: Root) => undefined;
|
|
134
|
+
/**
|
|
135
|
+
* Function to determine if code is inline based on the presence of line breaks.
|
|
136
|
+
* Reports `inline = true` for single line fenced code blocks.
|
|
137
|
+
*/
|
|
105
138
|
declare const isInlineCode: (node: Element) => boolean;
|
|
106
139
|
|
|
107
|
-
export { ShikiHighlighter as default, isInlineCode, useShikiHighlighter };
|
|
140
|
+
export { ShikiHighlighter as default, isInlineCode, rehypeInlineCodeProperty, useShikiHighlighter };
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
function
|
|
2
|
-
`);import
|
|
1
|
+
function h(e,{insertAt:i}={}){if(!e||typeof document>"u")return;let r=document.head||document.getElementsByTagName("head")[0],t=document.createElement("style");t.type="text/css",i==="top"&&r.firstChild?r.insertBefore(t,r.firstChild):r.appendChild(t),t.styleSheet?t.styleSheet.cssText=e:t.appendChild(document.createTextNode(e))}h(`.relative{position:relative}.defaultStyles pre{overflow:auto;border-radius:.5rem;padding:1.25rem 1.5rem}.languageLabel{position:absolute;right:.75rem;top:.5rem;font-family:monospace;font-size:.75rem;letter-spacing:-.05em;color:#6b7280d9}
|
|
2
|
+
`);import x from"react";import{clsx as S}from"clsx";import{useEffect as E,useRef as I,useState as v}from"react";import R from"html-react-parser";import{createHighlighter as N,createSingletonShorthands as P}from"shiki";import{visit as L}from"unist-util-visit";import{bundledLanguages as b,isSpecialLang as H}from"shiki";function C(){return function(e){L(e,"element",function(i,r,t){i.tagName==="code"&&t.tagName!=="pre"&&(i.properties.inline=!0)})}}var k=e=>!(e.children||[]).filter(r=>r.type==="text").map(r=>r.value).join("").includes(`
|
|
3
|
+
`),c={pre(e){return"properties"in e&&(e.properties.tabindex="-1"),e}},f=(e,i,r)=>{let t=Date.now();clearTimeout(i.current.timeoutId);let o=Math.max(0,i.current.nextAllowedTime-t);i.current.timeoutId=setTimeout(()=>{e().catch(console.error),i.current.nextAllowedTime=t+r},o)},y=e=>typeof e=="string"&&!(e in b)&&!H(e)?"plaintext":e;var w=P(N),u=(e,i,r,t={})=>{let[o,m]=v(null),p=y(i),n=I({nextAllowedTime:0,timeoutId:void 0});return E(()=>{let a=!0,g=[c,...t.transformers||[]],s=async()=>{let d=await w.codeToHtml(e,{lang:p,theme:r,transformers:g});a&&m(R(d))},{delay:l}=t;return l?f(s,n,l):s().catch(console.error),()=>{a=!1,clearTimeout(n.current.timeoutId)}},[e,i]),o};var B=({language:e,theme:i,delay:r,transformers:t,addDefaultStyles:o=!0,style:m,langStyle:p,className:n,langClassName:a,showLanguage:g=!0,children:s,as:l="pre"})=>{let T=u(s,e,i,{delay:r,transformers:t});return x.createElement(l,{"data-testid":"shiki-container",className:S("relative","not-prose",o&&"defaultStyles",n),style:m,id:"shiki-container"},g&&e?x.createElement("span",{className:S("languageLabel",a),style:p,id:"language-label"},e):null,T)};export{B as default,k as isInlineCode,C as rehypeInlineCodeProperty,u as useShikiHighlighter};
|
|
3
4
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["#style-inject:#style-inject","../src/styles.css","../src/ShikiHighlighter.tsx","../src/useShiki.ts","../src/utils.ts"],"sourcesContent":["\n export default function styleInject(css, { insertAt } = {}) {\n if (!css || typeof document === 'undefined') return\n \n const head = document.head || document.getElementsByTagName('head')[0]\n const style = document.createElement('style')\n style.type = 'text/css'\n \n if (insertAt === 'top') {\n if (head.firstChild) {\n head.insertBefore(style, head.firstChild)\n } else {\n head.appendChild(style)\n }\n } else {\n head.appendChild(style)\n }\n \n if (style.styleSheet) {\n style.styleSheet.cssText = css\n } else {\n style.appendChild(document.createTextNode(css))\n }\n }\n ","import styleInject from '#style-inject';styleInject(\".relative{position:relative}.container pre{overflow:auto;border-radius:.5rem;padding:1.25rem 1.5rem}.languageLabel{position:absolute;right:.75rem;top:.5rem;font-family:monospace;font-size:.75rem;letter-spacing:-.05em;color:#6b7280d9}\\n\")","import './styles.css';\nimport React from 'react';\nimport { clsx } from 'clsx';\nimport { useShikiHighlighter } from './useShiki';\nimport type { Language, Theme, HighlighterOptions } from './types';\n\n/**\n * Props for the ShikiHighlighter component\n */\nexport interface ShikiHighlighterProps extends HighlighterOptions {\n /** \n * The programming language for syntax highlighting\n * @see https://shiki.style/languages\n */\n language: Language;\n\n /** \n * The code to be highlighted \n */\n children: string;\n\n /** \n * The color theme for syntax highlighting\n * Supports Shiki and custom textmate themes\n * @see https://shiki.style/themes\n */\n theme: Theme;\n\n /** \n * The delay in milliseconds between streamed updates\n * Use this when highlighting code being streamed on the client\n * @default undefined (no delay)\n */\n delay?: number;\n\n /**\n * Controls the application of default styles to the generated code blocks\n *\n * Default styles include padding, overflow handling, border radius, language label styling, and font settings\n * @default true\n */\n addDefaultStyles?: boolean;\n\n /** \n * Add custom inline styles to the generated code block \n */\n style?: React.CSSProperties;\n\n /** \n * Add custom inline styles to the language label \n */\n langStyle?: React.CSSProperties;\n\n /** \n * Add custom CSS class names to the generated code block \n */\n className?: string;\n\n /** \n * Whether to show the language label\n * @default true\n */\n showLanguage?: boolean;\n\n /** \n * The HTML element that wraps the generated code block.\n * @default 'pre'\n */\n as?: React.ElementType;\n}\n\n/**\n * ShikiHighlighter is a React component that provides syntax highlighting for code snippets.\n * It uses Shiki to render beautiful, theme-based syntax highlighting with optimized performance.\n *\n * @example\n * ```tsx\n * <ShikiHighlighter \n * language=\"typescript\" \n * theme=\"github-dark\"\n * delay={100} // Optional throttling for streamed updates\n * >\n * {code}\n * </ShikiHighlighter>\n * ```\n */\nexport const ShikiHighlighter = ({\n language,\n theme,\n delay,\n addDefaultStyles = true,\n style,\n langStyle,\n className,\n showLanguage = true,\n children: code,\n as: Element = 'pre',\n}: ShikiHighlighterProps): React.ReactElement => {\n const highlightedCode = useShikiHighlighter(code, language, theme, { delay });\n\n return (\n <Element\n className={clsx(\n 'relative',\n addDefaultStyles && 'container',\n className\n )}\n style={style}\n >\n {showLanguage && language ? (\n <span className=\"languageLabel\" style={langStyle}>\n {language}\n </span>\n ) : null}\n {highlightedCode}\n </Element>\n );\n};\n","import { useEffect, useRef, useState, type ReactNode } from 'react';\nimport parse from 'html-react-parser';\n\nimport {\n createHighlighter,\n ShikiError,\n type BundledLanguage,\n type Highlighter\n} from 'shiki';\n\nimport type {\n Language,\n Theme,\n HighlighterOptions,\n TimeoutState\n} from './types';\n\nimport { removeTabIndexFromPre } from '@/utils';\n\n\n/**\n* Singleton highlighter instance\n*/\nlet highlighterPromise: Promise<Highlighter> | null = null;\n\n\n/**\n* Creates or returns the singleton highlighter instance\n*/\nconst makeHighlighter = async (theme: Theme): Promise<Highlighter> => {\n if (!highlighterPromise) {\n highlighterPromise = createHighlighter({\n themes: [],\n langs: []\n });\n }\n\n const highlighter = await highlighterPromise;\n await loadTheme(highlighter, theme);\n return highlighter;\n};\n\n\n/**\n* Loads a theme dynamically if it hasn't been loaded yet\n*/\nconst loadTheme = async (\n highlighter: Highlighter,\n theme: Theme\n): Promise<void> => {\n try {\n await highlighter.loadTheme(theme);\n } catch (error) {\n if (error instanceof ShikiError) {\n console.warn('Error loading theme:', error.message);\n }\n throw error;\n }\n};\n\n\n/**\n* Loads a language dynamically, falling back to plaintext if not available\n*/\nconst loadLanguage = async (\n highlighter: Highlighter,\n lang: Language\n): Promise<string> => {\n const resolvedLanguage = lang ?? 'plaintext';\n\n try {\n await highlighter.loadLanguage(resolvedLanguage as BundledLanguage);\n return resolvedLanguage;\n } catch (error) {\n if (error instanceof ShikiError && error.message.includes('not included in this bundle')) {\n console.warn(`Language '${resolvedLanguage}' not supported, falling back to plaintext`);\n }\n return 'plaintext';\n }\n};\n\n\n/**\n* Optionally throttles rapid sequential highlighting operations to conserve resources\n*/\nconst throttleHighlighting = (\n performHighlight: () => Promise<void>,\n timeoutControl: React.MutableRefObject<TimeoutState>,\n throttleMs: number\n) => {\n const now = Date.now();\n clearTimeout(timeoutControl.current.timeoutId);\n\n const delay = Math.max(0, timeoutControl.current.nextAllowedTime - now);\n timeoutControl.current.timeoutId = setTimeout(() => {\n performHighlight().catch(console.error);\n timeoutControl.current.nextAllowedTime = now + throttleMs;\n }, delay);\n};\n\n\n/**\n* A React hook that provides syntax highlighting using Shiki. Uses a\n* singleton highlighter instance and supports optional throttled highlights\n*/\nexport const useShikiHighlighter = (\n code: string,\n lang: Language,\n theme: Theme,\n options: HighlighterOptions = {}\n): ReactNode | null => {\n const [highlightedCode, setHighlightedCode] = useState<ReactNode | null>(null);\n const delayRef = useRef<TimeoutState>({\n nextAllowedTime: 0,\n timeoutId: undefined\n });\n\n useEffect(() => {\n let isMounted = true;\n\n const highlightCode = async () => {\n const highlighter = await makeHighlighter(theme);\n const language = await loadLanguage(highlighter, lang);\n\n const html = highlighter.codeToHtml(code, {\n lang: language,\n theme,\n transformers: [removeTabIndexFromPre],\n });\n\n if (isMounted) {\n setHighlightedCode(parse(html));\n }\n };\n\n if (options.delay) {\n throttleHighlighting(highlightCode, delayRef, options.delay);\n } else {\n highlightCode().catch(console.error);\n }\n\n return () => {\n isMounted = false;\n clearTimeout(delayRef.current.timeoutId);\n };\n }, [code, lang]);\n\n return highlightedCode;\n};\n","import type { Element } from 'hast';\n\nexport const isInlineCode = (node: Element): boolean => {\n return node.position?.start.line === node.position?.end.line;\n};\n\n\nexport const removeTabIndexFromPre = {\n pre(node: Element) {\n node.properties.tabindex = '-1';\n },\n};\n\nexport type { Element };\n"],"mappings":"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","names":["styleInject","css","insertAt","head","style","styleInject","React","clsx","useEffect","useRef","useState","parse","createHighlighter","ShikiError","isInlineCode","node","removeTabIndexFromPre","highlighterPromise","makeHighlighter","theme","createHighlighter","highlighter","loadTheme","error","ShikiError","loadLanguage","lang","resolvedLanguage","throttleHighlighting","performHighlight","timeoutControl","throttleMs","now","delay","useShikiHighlighter","code","options","highlightedCode","setHighlightedCode","useState","delayRef","useRef","useEffect","isMounted","highlightCode","language","html","removeTabIndexFromPre","parse","ShikiHighlighter","language","theme","delay","addDefaultStyles","style","langStyle","className","showLanguage","code","Element","highlightedCode","useShikiHighlighter","React","clsx"]}
|
|
1
|
+
{"version":3,"sources":["#style-inject:#style-inject","../src/styles.css","../src/ShikiHighlighter.tsx","../src/useShiki.ts","../src/utils.ts"],"sourcesContent":["\n export default function styleInject(css, { insertAt } = {}) {\n if (!css || typeof document === 'undefined') return\n \n const head = document.head || document.getElementsByTagName('head')[0]\n const style = document.createElement('style')\n style.type = 'text/css'\n \n if (insertAt === 'top') {\n if (head.firstChild) {\n head.insertBefore(style, head.firstChild)\n } else {\n head.appendChild(style)\n }\n } else {\n head.appendChild(style)\n }\n \n if (style.styleSheet) {\n style.styleSheet.cssText = css\n } else {\n style.appendChild(document.createTextNode(css))\n }\n }\n ","import styleInject from '#style-inject';styleInject(\".relative{position:relative}.defaultStyles pre{overflow:auto;border-radius:.5rem;padding:1.25rem 1.5rem}.languageLabel{position:absolute;right:.75rem;top:.5rem;font-family:monospace;font-size:.75rem;letter-spacing:-.05em;color:#6b7280d9}\\n\")","import './styles.css';\nimport React from 'react';\nimport { clsx } from 'clsx';\nimport { useShikiHighlighter } from './useShiki';\nimport type { Language, Theme, HighlighterOptions } from './types';\nimport type { ShikiTransformer } from 'shiki';\n\n/**\n * Props for the ShikiHighlighter component\n */\nexport interface ShikiHighlighterProps extends HighlighterOptions {\n /** \n * The programming language for syntax highlighting\n * @see https://shiki.style/languages\n */\n language: Language;\n\n /** \n * The code to be highlighted \n */\n children: string;\n\n /** \n * The color theme for syntax highlighting\n * Supports Shiki and custom textmate themes\n * @see https://shiki.style/themes\n */\n theme: Theme;\n\n /** \n * The delay in milliseconds between streamed updates\n * Use this when highlighting code being streamed on the client\n * @default undefined (no delay)\n */\n delay?: number;\n\n /**\n * Pass custom Shiki transformers to the highlighter\n */\n transformers?: ShikiTransformer[];\n\n /**\n * Controls the application of default styles to the generated code blocks\n *\n * Default styles include padding, overflow handling, border radius, language label styling, and font settings\n * @default true\n */\n addDefaultStyles?: boolean;\n\n /** \n * Add custom inline styles to the generated code block \n */\n style?: React.CSSProperties;\n\n /** \n * Add custom inline styles to the language label \n */\n langStyle?: React.CSSProperties;\n\n /** \n * Add custom CSS class names to the generated code block \n */\n className?: string;\n\n /** \n * Add custom CSS class names to the language label \n */\n langClassName?: string;\n\n /** \n * Whether to show the language label\n * @default true\n */\n showLanguage?: boolean;\n\n /** \n * The HTML element that wraps the generated code block.\n * @default 'pre'\n */\n as?: React.ElementType;\n}\n\n/**\n * ShikiHighlighter is a React component that provides syntax highlighting for code snippets.\n * It uses Shiki to render beautiful, theme-based syntax highlighting with optimized performance.\n *\n * @example\n * ```tsx\n * <ShikiHighlighter \n * language=\"typescript\" \n * theme=\"github-dark\"\n * delay={100} // Optional throttling for streamed updates\n * >\n * {code}\n * </ShikiHighlighter>\n * ```\n */\nexport const ShikiHighlighter = ({\n language,\n theme,\n delay,\n transformers,\n addDefaultStyles = true,\n style,\n langStyle,\n className,\n langClassName,\n showLanguage = true,\n children: code,\n as: Element = 'pre',\n}: ShikiHighlighterProps): React.ReactElement => {\n const options: HighlighterOptions = { delay, transformers };\n const highlightedCode = useShikiHighlighter(code, language, theme, options);\n\n return (\n <Element\n data-testid=\"shiki-container\"\n className={clsx(\n 'relative',\n 'not-prose',\n addDefaultStyles && 'defaultStyles',\n className\n )}\n style={style}\n id='shiki-container'\n >\n {showLanguage && language ? (\n <span\n className={clsx('languageLabel', langClassName)}\n style={langStyle}\n id='language-label'\n >\n {language}\n </span>\n ) : null}\n {highlightedCode}\n </Element>\n );\n};\n","import { useEffect, useRef, useState, type ReactNode } from 'react';\nimport parse from 'html-react-parser';\n\nimport {\n createHighlighter,\n createSingletonShorthands,\n} from 'shiki';\n\nimport type {\n Language,\n Theme,\n HighlighterOptions,\n TimeoutState\n} from './types';\n\nimport { \n removeTabIndexFromPre,\n throttleHighlighting,\n resolvedLang\n} from './utils';\n\n\n/**\n * Create singleton shorthands which handle automatic loading\n * of the languages and themes, with support for custom themes.\n */\nconst highlighter = createSingletonShorthands(createHighlighter);\n\n\n/**\n * A React hook that provides syntax highlighting using Shiki.\n * Supports optional throttled highlights and custom themes.\n *\n * @example\n * const highlightedCode = useShikiHighlighter(code, language, theme, {\n * transformers: [removeTabIndexFromPre],\n * delay: 150\n * });\n */\nexport const useShikiHighlighter = (\n code: string,\n lang: Language,\n theme: Theme,\n options: HighlighterOptions = {}\n) => {\n const [highlightedCode, setHighlightedCode] = useState<ReactNode | null>(null);\n const language = resolvedLang(lang);\n\n const timeoutControl = useRef<TimeoutState>({\n nextAllowedTime: 0,\n timeoutId: undefined\n });\n\n useEffect(() => {\n let isMounted = true;\n\n const transformers = [removeTabIndexFromPre, ...(options.transformers || [])];\n\n const highlightCode = async () => {\n const html = await highlighter.codeToHtml(code, {\n lang: language,\n theme,\n transformers\n });\n\n if (isMounted) {\n setHighlightedCode(parse(html));\n }\n };\n\n const { delay } = options;\n\n if (delay) {\n throttleHighlighting(highlightCode, timeoutControl, delay);\n } else {\n highlightCode().catch(console.error);\n }\n\n return () => {\n isMounted = false;\n clearTimeout(timeoutControl.current.timeoutId);\n };\n }, [code, lang]);\n\n return highlightedCode;\n};\n\n","import { visit } from 'unist-util-visit';\nimport { bundledLanguages, isSpecialLang, } from 'shiki';\nimport type { BundledLanguage, ShikiTransformer } from 'shiki';\nimport type { Element, Root } from 'hast';\nimport type { Language, TimeoutState } from './types';\n\n/**\n * Rehype plugin to add an 'inline' property to <code> elements.\n * Sets an 'inline' property to true if the <code> is not within a <pre> tag.\n *\n * Pass this plugin to the `rehypePlugins` prop of ReactMarkdown.\n * You can then access `inline` as a prop from ReactMarkdown.\n *\n * @example\n * <ReactMarkdown rehypePlugins={[rehypeInlineCodeProperty]} />\n */\nexport function rehypeInlineCodeProperty() {\n return function (tree: Root): undefined {\n visit(tree as any, 'element', function (node: Element, _index, parent: Element) {\n if (node.tagName === 'code' && parent.tagName !== 'pre') {\n node.properties.inline = true;\n }\n });\n }\n}\n\n\n/**\n * Function to determine if code is inline based on the presence of line breaks.\n * Reports `inline = true` for single line fenced code blocks.\n */\nexport const isInlineCode = (node: Element): boolean => {\n const textContent = (node.children || [])\n .filter((child) => child.type === 'text')\n .map((child) => child.value)\n .join('');\n\n return !textContent.includes('\\n');\n};\n\n\n/**\n * Shiki transformer to remove tabindex from <pre> elements.\n */\nexport const removeTabIndexFromPre: ShikiTransformer = {\n pre(node) {\n if ('properties' in node) {\n node.properties.tabindex = '-1';\n }\n return node;\n },\n};\n\n\n/**\n * Optionally throttles rapid sequential highlighting operations.\n * Exported for testing in __tests__/throttleHighlighting.test.ts\n *\n * @example\n * const timeoutControl = useRef<TimeoutState>({\n * nextAllowedTime: 0,\n * timeoutId: undefined\n * });\n *\n * throttleHighlighting(highlightCode, timeoutControl, 1000);\n */\nexport const throttleHighlighting = (\n performHighlight: () => Promise<void>,\n timeoutControl: React.MutableRefObject<TimeoutState>,\n throttleMs: number\n) => {\n const now = Date.now();\n clearTimeout(timeoutControl.current.timeoutId);\n\n const delay = Math.max(0, timeoutControl.current.nextAllowedTime - now);\n timeoutControl.current.timeoutId = setTimeout(() => {\n performHighlight().catch(console.error);\n timeoutControl.current.nextAllowedTime = now + throttleMs;\n }, delay);\n};\n\n/**\n * Resolve the requested language to a bundled language.\n * If the language is not special or not included in Shiki’s bundledLanguages,\n * fall back to \"plaintext\".\n *\n * @returns {BundledLanguage} The resolved language.\n */\nexport const resolvedLang = (lang: Language): BundledLanguage => {\n if (typeof lang === 'string') {\n if (!(lang in bundledLanguages) && !isSpecialLang(lang)) {\n return 'plaintext' as BundledLanguage;\n }\n return lang as BundledLanguage;\n }\n return lang as Language as BundledLanguage;\n};\n\nexport type { Element };\n"],"mappings":"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","names":["styleInject","css","insertAt","head","style","styleInject","React","clsx","useEffect","useRef","useState","parse","createHighlighter","createSingletonShorthands","visit","bundledLanguages","isSpecialLang","rehypeInlineCodeProperty","tree","node","_index","parent","isInlineCode","child","removeTabIndexFromPre","throttleHighlighting","performHighlight","timeoutControl","throttleMs","now","delay","resolvedLang","lang","highlighter","createSingletonShorthands","createHighlighter","useShikiHighlighter","code","lang","theme","options","highlightedCode","setHighlightedCode","useState","language","resolvedLang","timeoutControl","useRef","useEffect","isMounted","transformers","removeTabIndexFromPre","highlightCode","html","parse","delay","throttleHighlighting","ShikiHighlighter","language","theme","delay","transformers","addDefaultStyles","style","langStyle","className","langClassName","showLanguage","code","Element","highlightedCode","useShikiHighlighter","React","clsx"]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-shiki",
|
|
3
3
|
"description": "Syntax highlighter component for react using shiki",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.3.0",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Bassim Shahidy",
|
|
@@ -40,18 +40,26 @@
|
|
|
40
40
|
"react-dom": ">= 15.0.0"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
+
"@types/jest": "^29.5.14",
|
|
43
44
|
"clsx": "^2.1.1",
|
|
44
45
|
"html-react-parser": "^5.1.12",
|
|
45
|
-
"shiki": "^1.12.1"
|
|
46
|
+
"shiki": "^1.12.1",
|
|
47
|
+
"unist-util-visit": "^5.0.0"
|
|
46
48
|
},
|
|
47
49
|
"devDependencies": {
|
|
50
|
+
"@testing-library/jest-dom": "^6.0.0",
|
|
51
|
+
"@testing-library/react": "^14.0.0",
|
|
48
52
|
"@types/hast": "^3.0.4",
|
|
49
53
|
"@types/node": "22.10.10",
|
|
50
54
|
"@types/react": "^18.3.3",
|
|
51
|
-
"
|
|
55
|
+
"@vitejs/plugin-react": "^4.0.0",
|
|
56
|
+
"jsdom": "^22.1.0",
|
|
57
|
+
"tsup": "^8.2.4",
|
|
58
|
+
"vitest": "^0.34.0"
|
|
52
59
|
},
|
|
53
60
|
"scripts": {
|
|
54
61
|
"dev": "tsup --watch",
|
|
55
|
-
"build": "tsup"
|
|
62
|
+
"build": "tsup",
|
|
63
|
+
"test": "vitest"
|
|
56
64
|
}
|
|
57
65
|
}
|