react-shiki 0.1.0 → 0.1.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 +106 -6
- package/dist/index.d.ts +55 -2
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/package.json +7 -2
package/README.md
CHANGED
|
@@ -1,16 +1,81 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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`
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
# 🎨 [react-shiki](https://react-shiki.vercel.app/)
|
|
6
|
+
|
|
7
|
+
Syntax highlighting component and hook for react using [Shiki](https://shiki.matsu.io/)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
[See the demo page for a version of this README with highlighted code blocks showcasing several Shiki themes!](https://react-shiki.vercel.app/)
|
|
11
|
+
|
|
12
|
+
## Features
|
|
13
|
+
- 🖼️ Provides a `ShikiHighlighter` component for highlighting code as children, as well as a `useShikiHighlighter` hook for more flexibility
|
|
14
|
+
- 🔐 No `dangerouslySetInnerHTML`, output from Shiki is parsed using `html-react-parser`
|
|
15
|
+
- 📦 Supports all Shiki languages and themes
|
|
16
|
+
- 📚 Includes minimal default styles for code blocks
|
|
17
|
+
- 🚀 Shiki dynamically imports only the languages and themes used on a page, optimizing for performance
|
|
18
|
+
- 🖥️ `ShikiHighlighter` component displays a language label for each code block when `showLanguage` is set to `true` (default)
|
|
19
|
+
- 🎨 Users can customize the styling of the generated code blocks by passing a `style` object or a `className`
|
|
20
|
+
|
|
3
21
|
|
|
4
22
|
## Installation
|
|
23
|
+
|
|
5
24
|
```bash
|
|
6
|
-
|
|
7
|
-
```
|
|
8
|
-
```bash
|
|
9
|
-
npm install react-shiki
|
|
25
|
+
[pnpm|bun|yarn|npm] [add|install] react-shiki
|
|
10
26
|
```
|
|
11
27
|
|
|
12
28
|
## Usage
|
|
13
29
|
|
|
30
|
+
You can use the `ShikiHighlighter` component, or the `useShikiHighlighter` hook to highlight code.
|
|
31
|
+
|
|
32
|
+
`useShikiHighlighter` is a hook that takes in the code to be highlighted, the language, and the theme, and returns the highlighted code as React elements. It's useful for users who want full control over the rendering of highlighted code.
|
|
33
|
+
```tsx
|
|
34
|
+
const highlightedCode = useShikiHighlighter(code, language, theme);
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The `ShikiHighlighter` component is imported in your project, with the code to be highlighted passed as it's children.
|
|
38
|
+
|
|
39
|
+
Shiki automatically handles dynamically importing only the languages and themes used on the page.
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
function CodeBlock() {
|
|
43
|
+
return (
|
|
44
|
+
<ShikiHighlighter language="jsx" theme="ayu-dark">
|
|
45
|
+
{code.trim()}
|
|
46
|
+
</ShikiHighlighter>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
The component accepts several props in addition to language and theme:
|
|
53
|
+
|
|
54
|
+
- `showLanguage: boolean` - Shows the language name in the top right corner of the code block.
|
|
55
|
+
- `style: object` - Style object to be passed to the component.
|
|
56
|
+
- `as: string` - The component to be rendered. Defaults to 'pre'.
|
|
57
|
+
- `className: string` - Class name to be passed to the component.
|
|
58
|
+
|
|
59
|
+
```tsx
|
|
60
|
+
function Houston() {
|
|
61
|
+
return (
|
|
62
|
+
<ShikiHighlighter
|
|
63
|
+
language="jsx"
|
|
64
|
+
theme="houston"
|
|
65
|
+
showLanguage={false}
|
|
66
|
+
addDefaultStyles={true}
|
|
67
|
+
as="div"
|
|
68
|
+
style={{
|
|
69
|
+
textAlign: 'left',
|
|
70
|
+
}}
|
|
71
|
+
>
|
|
72
|
+
{code.trim()}
|
|
73
|
+
</ShikiHighlighter>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
It can also be used with `react-markdown`:
|
|
14
79
|
```tsx
|
|
15
80
|
import type { ReactNode } from 'react';
|
|
16
81
|
import type { BundledLanguage } from 'shiki';
|
|
@@ -47,4 +112,39 @@ export const CodeHighlight = ({
|
|
|
47
112
|
</code>
|
|
48
113
|
);
|
|
49
114
|
};
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Pass CodeHighlight to `react-markdown` as a code component:
|
|
118
|
+
```tsx
|
|
119
|
+
import ReactMarkdown from 'react-markdown';
|
|
120
|
+
import { CodeHighlight } from './CodeHighlight';
|
|
121
|
+
|
|
122
|
+
<ReactMarkdown
|
|
123
|
+
components={{
|
|
124
|
+
code: CodeHighlight,
|
|
125
|
+
}}
|
|
126
|
+
>
|
|
127
|
+
{markdown}
|
|
128
|
+
</ReactMarkdown>
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
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:
|
|
132
|
+
```tsx
|
|
133
|
+
const RenderedMessage = React.memo(({ message }: { message: Message }) => (
|
|
134
|
+
<div className={cn(messageStyles[message.role])}>
|
|
135
|
+
<ReactMarkdown components={{ code: CodeHighlight }}>
|
|
136
|
+
{message.content}
|
|
137
|
+
</ReactMarkdown>
|
|
138
|
+
</div>
|
|
139
|
+
));
|
|
140
|
+
|
|
141
|
+
export const ChatMessages = ({ messages }: { messages: Message[] }) => {
|
|
142
|
+
return (
|
|
143
|
+
<div className='space-y-4'>
|
|
144
|
+
{messages.map((message) => (
|
|
145
|
+
<RenderedMessage key={message.id} message={message} />
|
|
146
|
+
))}
|
|
147
|
+
</div>
|
|
148
|
+
);
|
|
149
|
+
};
|
|
50
150
|
```
|
package/dist/index.d.ts
CHANGED
|
@@ -4,18 +4,71 @@ import { Element } from 'hast';
|
|
|
4
4
|
export { Element } from 'hast';
|
|
5
5
|
|
|
6
6
|
interface ShikiHighlighterProps {
|
|
7
|
+
/**
|
|
8
|
+
* The programming language for syntax highlighting
|
|
9
|
+
* @see https://shiki.style/languages
|
|
10
|
+
*/
|
|
7
11
|
language: BundledLanguage;
|
|
12
|
+
/**
|
|
13
|
+
* The code to be highlighted
|
|
14
|
+
*/
|
|
8
15
|
children: string;
|
|
16
|
+
/**
|
|
17
|
+
* The color theme for syntax highlighting, only Shiki themes are supported at this time
|
|
18
|
+
* @see https://shiki.style/themes
|
|
19
|
+
*/
|
|
9
20
|
theme: BundledTheme;
|
|
21
|
+
/**
|
|
22
|
+
* Controls the application of default styles to the generated code blocks
|
|
23
|
+
*
|
|
24
|
+
* Default styles include padding, overflow handling, border radius, language label styling, and font settings
|
|
25
|
+
* @default true
|
|
26
|
+
*/
|
|
10
27
|
addDefaultStyles?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Add custom inline styles to the generated code block
|
|
30
|
+
*/
|
|
11
31
|
style?: React.CSSProperties;
|
|
32
|
+
/**
|
|
33
|
+
* Add custom CSS class names to the generated code block
|
|
34
|
+
*/
|
|
12
35
|
className?: string;
|
|
36
|
+
/**
|
|
37
|
+
* Whether to show the language label
|
|
38
|
+
* @default true
|
|
39
|
+
*/
|
|
13
40
|
showLanguage?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* The HTML element that wraps the generated code block.
|
|
43
|
+
* @default 'pre'
|
|
44
|
+
*/
|
|
14
45
|
as?: React.ElementType;
|
|
15
46
|
}
|
|
16
|
-
|
|
47
|
+
/**
|
|
48
|
+
* ShikiHighlighter is a React component that provides syntax highlighting for code snippets.
|
|
49
|
+
*
|
|
50
|
+
* It uses the Shiki library to render beautiful, theme-based syntax highlighting.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```tsx
|
|
54
|
+
* <ShikiHighlighter language="tsx" theme="vitesse-black">
|
|
55
|
+
* {code.trim()}
|
|
56
|
+
* </ShikiHighlighter>
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
declare const ShikiHighlighter: ({ language, theme, addDefaultStyles, style, className, showLanguage, children: code, as: Element, }: ShikiHighlighterProps) => React.ReactElement;
|
|
17
60
|
|
|
18
|
-
|
|
61
|
+
/**
|
|
62
|
+
*
|
|
63
|
+
* `useShikiHighlighter` is a custom hook that takes in the code to be highlighted, the language, and the theme, and returns the highlighted code parsed as a ReactNode.
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```tsx
|
|
67
|
+
* const highlightedCode = useShikiHighlighter(code, language, theme);
|
|
68
|
+
* ```
|
|
69
|
+
*
|
|
70
|
+
*/
|
|
71
|
+
declare const useShikiHighlighter: (code: string, lang: BundledLanguage | undefined, theme: BundledTheme) => ReactNode | null;
|
|
19
72
|
|
|
20
73
|
declare const isInlineCode: (node: Element) => boolean;
|
|
21
74
|
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
function a(e,{insertAt:
|
|
2
|
-
`);import g from"react";import p from"html-react-parser";import{useEffect as f,useState as y}from"react";import{codeToHtml as h}from"shiki";var c=e=>e.position?.start.line===e.position?.end.line,s={pre(e){e.properties.tabindex="-1"}};var d=(e,
|
|
1
|
+
function a(e,{insertAt:o}={}){if(!e||typeof document>"u")return;let r=document.head||document.getElementsByTagName("head")[0],t=document.createElement("style");t.type="text/css",o==="top"&&r.firstChild?r.insertBefore(t,r.firstChild):r.appendChild(t),t.styleSheet?t.styleSheet.cssText=e:t.appendChild(document.createTextNode(e))}a(`.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 g from"react";import p from"html-react-parser";import{useEffect as f,useState as y}from"react";import{codeToHtml as h}from"shiki";var c=e=>e.position?.start.line===e.position?.end.line,s={pre(e){e.properties.tabindex="-1"}};var d=(e,o,r)=>{let[t,i]=y(null);return f(()=>{(async()=>{try{let n=await h(e,{lang:o,theme:r,transformers:[s]});i(p(n))}catch{let l=await h(e,{lang:"plaintext",theme:r,transformers:[s]});i(p(l))}})()},[e,r,o]),t};import{clsx as x}from"clsx";var C=({language:e,theme:o,addDefaultStyles:r=!0,style:t,className:i,showLanguage:m=!0,children:n,as:l="pre"})=>{let u=d(n,e,o);return g.createElement(l,{className:x("relative",r&&"container",i),style:t},m&&e?g.createElement("span",{className:"languageLabel",style:t},e):null,u)};export{C as default,c as isInlineCode,d as useShikiHighlighter};
|
|
3
3
|
//# 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';\n// biome-ignore lint/style/useImportType: Package needs React to be imported\nimport React from 'react';\nimport type { BundledLanguage, BundledTheme } from 'shiki';\nimport { useShikiHighlighter } from '@/useShiki';\nimport { clsx } from 'clsx';\n\
|
|
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';\n// biome-ignore lint/style/useImportType: Package needs React to be imported\nimport React from 'react';\nimport type { BundledLanguage, BundledTheme } from 'shiki';\nimport { useShikiHighlighter } from '@/useShiki';\nimport { clsx } from 'clsx';\n\nexport interface ShikiHighlighterProps {\n /** \n * The programming language for syntax highlighting\n * @see https://shiki.style/languages\n */\n language: BundledLanguage;\n\n /** \n * The code to be highlighted \n */\n children: string;\n\n /** \n * The color theme for syntax highlighting, only Shiki themes are supported at this time\n * @see https://shiki.style/themes\n */\n theme: BundledTheme;\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 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 *\n * It uses the Shiki library to render beautiful, theme-based syntax highlighting.\n *\n * @example\n * ```tsx\n * <ShikiHighlighter language=\"tsx\" theme=\"vitesse-black\">\n * {code.trim()}\n * </ShikiHighlighter>\n * ```\n */\nexport const ShikiHighlighter = ({\n language,\n theme,\n addDefaultStyles = true,\n style,\n className,\n showLanguage = true,\n children: code,\n as: Element = 'pre',\n}: ShikiHighlighterProps): React.ReactElement => {\n const highlightedCode = useShikiHighlighter(code, language, theme);\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={style}>\n {language}\n </span>\n ) : null}\n {highlightedCode}\n </Element>\n );\n};\n","import parse from 'html-react-parser';\nimport { type ReactNode, useEffect, useState } from 'react';\nimport { type BundledLanguage, type BundledTheme, codeToHtml } from 'shiki';\nimport { removeTabIndexFromPre } from '@/utils';\n\n/**\n * \n * `useShikiHighlighter` is a custom hook that takes in the code to be highlighted, the language, and the theme, and returns the highlighted code parsed as a ReactNode.\n *\n * @example\n * ```tsx\n * const highlightedCode = useShikiHighlighter(code, language, theme);\n * ```\n * \n */\nexport const useShikiHighlighter = (\n code: string,\n lang: BundledLanguage | undefined,\n theme: BundledTheme\n): ReactNode | null => {\n const [highlightedCode, setHighlightedCode] = useState<ReactNode | null>(\n null\n );\n\n useEffect(() => {\n const highlightCode = async () => {\n try {\n const html = await codeToHtml(code, {\n lang: lang as BundledLanguage,\n theme,\n transformers: [removeTabIndexFromPre],\n });\n setHighlightedCode(parse(html));\n } catch (error) {\n const fallbackHtml = await codeToHtml(code, {\n lang: 'plaintext',\n theme,\n transformers: [removeTabIndexFromPre],\n });\n setHighlightedCode(parse(fallbackHtml));\n }\n };\n\n highlightCode();\n }, [code, theme, 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":"AACyB,SAARA,EAA6BC,EAAK,CAAE,SAAAC,CAAS,EAAI,CAAC,EAAG,CAC1D,GAAI,CAACD,GAAO,OAAO,SAAa,IAAa,OAE7C,IAAME,EAAO,SAAS,MAAQ,SAAS,qBAAqB,MAAM,EAAE,CAAC,EAC/DC,EAAQ,SAAS,cAAc,OAAO,EAC5CA,EAAM,KAAO,WAETF,IAAa,OACXC,EAAK,WACPA,EAAK,aAAaC,EAAOD,EAAK,UAAU,EAK1CA,EAAK,YAAYC,CAAK,EAGpBA,EAAM,WACRA,EAAM,WAAW,QAAUH,EAE3BG,EAAM,YAAY,SAAS,eAAeH,CAAG,CAAC,CAElD,CCvB8BI,EAAY;AAAA,CAA6O,ECEjS,OAAOC,MAAW,QCFlB,OAAOC,MAAW,oBAClB,OAAyB,aAAAC,EAAW,YAAAC,MAAgB,QACpD,OAAkD,cAAAC,MAAkB,QCA7D,IAAMC,EAAgBC,GACpBA,EAAK,UAAU,MAAM,OAASA,EAAK,UAAU,IAAI,KAI7CC,EAAwB,CACnC,IAAID,EAAe,CACjBA,EAAK,WAAW,SAAW,IAC7B,CACF,EDIO,IAAME,EAAsB,CACjCC,EACAC,EACAC,IACqB,CACrB,GAAM,CAACC,EAAiBC,CAAkB,EAAIC,EAC5C,IACF,EAEA,OAAAC,EAAU,IAAM,EACQ,SAAY,CAChC,GAAI,CACF,IAAMC,EAAO,MAAMC,EAAWR,EAAM,CAClC,KAAMC,EACN,MAAAC,EACA,aAAc,CAACO,CAAqB,CACtC,CAAC,EACDL,EAAmBM,EAAMH,CAAI,CAAC,CAChC,MAAgB,CACd,IAAMI,EAAe,MAAMH,EAAWR,EAAM,CAC1C,KAAM,YACN,MAAAE,EACA,aAAc,CAACO,CAAqB,CACtC,CAAC,EACDL,EAAmBM,EAAMC,CAAY,CAAC,CACxC,CACF,GAEc,CAChB,EAAG,CAACX,EAAME,EAAOD,CAAI,CAAC,EAEfE,CACT,ED1CA,OAAS,QAAAS,MAAY,OA+Dd,IAAMC,EAAmB,CAAC,CAC/B,SAAAC,EACA,MAAAC,EACA,iBAAAC,EAAmB,GACnB,MAAAC,EACA,UAAAC,EACA,aAAAC,EAAe,GACf,SAAUC,EACV,GAAIC,EAAU,KAChB,IAAiD,CAC/C,IAAMC,EAAkBC,EAAoBH,EAAMN,EAAUC,CAAK,EAEjE,OACES,EAAA,cAACH,EAAA,CACC,UAAWT,EACT,WACAI,GAAoB,YACpBE,CACF,EACA,MAAOD,GAENE,GAAgBL,EACfU,EAAA,cAAC,QAAK,UAAU,gBAAgB,MAAOP,GACpCH,CACH,EACE,KACHQ,CACH,CAEJ","names":["styleInject","css","insertAt","head","style","styleInject","React","parse","useEffect","useState","codeToHtml","isInlineCode","node","removeTabIndexFromPre","useShikiHighlighter","code","lang","theme","highlightedCode","setHighlightedCode","useState","useEffect","html","codeToHtml","removeTabIndexFromPre","parse","fallbackHtml","clsx","ShikiHighlighter","language","theme","addDefaultStyles","style","className","showLanguage","code","Element","highlightedCode","useShikiHighlighter","React"]}
|
package/package.json
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-shiki",
|
|
3
3
|
"description": "Syntax highlighter component for react using shiki",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.1",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Bassim Shahidy",
|
|
8
8
|
"email": "bassim@shahidy.com",
|
|
9
9
|
"url": "https://bassim.build"
|
|
10
10
|
},
|
|
11
|
-
"repository":
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"directory": "package",
|
|
14
|
+
"url": "https://github.com/avgvstvs96/react-shiki"
|
|
15
|
+
},
|
|
16
|
+
"homepage": "https://react-shiki.vercel.app/",
|
|
12
17
|
"keywords": [
|
|
13
18
|
"react",
|
|
14
19
|
"shiki",
|