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 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 in addition to
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`, but uses Shiki and is optimized for performant sequential highlighting. As of now, not all of `react-syntax-highlighter` functionality is supported, but the goal of this component is to eventually act as a drop in replacement for `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 name to be passed to the component
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:title=CodeHighlight.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
- LLM chatbot UI, it renders markdown and highlights code in memoized chat messages.
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 title=CodeHighlight.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 title=CodeHighlight.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 title=ChatMessages.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 | "plaintext" | (string & {}) | undefined;
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
- * Defaults to undefined (no throttling)
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. Uses a
101
- * singleton highlighter instance and supports optional throttled highlights
102
- */
103
- declare const useShikiHighlighter: (code: string, lang: Language, theme: Theme, options?: HighlighterOptions) => ReactNode | null;
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 m(e,{insertAt:i}={}){if(!e||typeof document>"u")return;let t=document.head||document.getElementsByTagName("head")[0],r=document.createElement("style");r.type="text/css",i==="top"&&t.firstChild?t.insertBefore(r,t.firstChild):t.appendChild(r),r.styleSheet?r.styleSheet.cssText=e:r.appendChild(document.createTextNode(e))}m(`.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}
2
- `);import f from"react";import{clsx as k}from"clsx";import{useEffect as T,useRef as H,useState as S}from"react";import b from"html-react-parser";import{createHighlighter as w,ShikiError as p}from"shiki";var x=e=>e.position?.start.line===e.position?.end.line,u={pre(e){e.properties.tabindex="-1"}};var c=null,L=async e=>{c||(c=w({themes:[],langs:[]}));let i=await c;return await E(i,e),i},E=async(e,i)=>{try{await e.loadTheme(i)}catch(t){throw t instanceof p&&console.warn("Error loading theme:",t.message),t}},P=async(e,i)=>{let t=i??"plaintext";try{return await e.loadLanguage(t),t}catch(r){return r instanceof p&&r.message.includes("not included in this bundle")&&console.warn(`Language '${t}' not supported, falling back to plaintext`),"plaintext"}},R=(e,i,t)=>{let r=Date.now();clearTimeout(i.current.timeoutId);let o=Math.max(0,i.current.nextAllowedTime-r);i.current.timeoutId=setTimeout(()=>{e().catch(console.error),i.current.nextAllowedTime=r+t},o)},d=(e,i,t,r={})=>{let[o,h]=S(null),n=H({nextAllowedTime:0,timeoutId:void 0});return T(()=>{let a=!0,l=async()=>{let s=await L(t),g=await P(s,i),y=s.codeToHtml(e,{lang:g,theme:t,transformers:[u]});a&&h(b(y))};return r.delay?R(l,n,r.delay):l().catch(console.error),()=>{a=!1,clearTimeout(n.current.timeoutId)}},[e,i]),o};var C=({language:e,theme:i,delay:t,addDefaultStyles:r=!0,style:o,langStyle:h,className:n,showLanguage:a=!0,children:l,as:s="pre"})=>{let g=d(l,e,i,{delay:t});return f.createElement(s,{className:k("relative",r&&"container",n),style:o},a&&e?f.createElement("span",{className:"languageLabel",style:h},e):null,g)};export{C as default,x as isInlineCode,d as useShikiHighlighter};
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.2.3",
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
- "tsup": "^8.2.4"
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
  }