diff --git a/package/README.md b/package/README.md index 008a7e3..b43dc20 100644 --- a/package/README.md +++ b/package/README.md @@ -1,17 +1,18 @@ -> [!WARNING] -> 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` +> [!NOTE] +> This package is still a work in progress, fully functional but not extensively tested. # 🎨 [react-shiki](https://react-shiki.vercel.app/) Syntax highlighting component and hook for react using [Shiki](https://shiki.matsu.io/) -[See the demo page for a version of this README with highlighted code blocks showcasing several Shiki themes!](https://react-shiki.vercel.app/) +[See the demo page with highlighted code blocks showcasing several Shiki themes!](https://react-shiki.vercel.app/) ## Features - 🖼️ Provides a `ShikiHighlighter` component for highlighting code as children, as well as a `useShikiHighlighter` hook for more flexibility - 🔐 No `dangerouslySetInnerHTML`, output from Shiki is parsed using `html-react-parser` - 📦 Supports all Shiki languages and themes +- 🚰 Supports performant highlighting of streamed code on the client, with optional throttling - 📚 Includes minimal default styles for code blocks - 🚀 Shiki dynamically imports only the languages and themes used on a page, optimizing for performance - 🖥️ `ShikiHighlighter` component displays a language label for each code block when `showLanguage` is set to `true` (default) @@ -20,7 +21,7 @@ Syntax highlighting component and hook for react using [Shiki](https://shiki.mat ## Installation ```bash -[pnpm|bun|yarn|npm] [add|install] react-shiki +[pnpm|bun|yarn|npm] install react-shiki ``` ## Usage @@ -74,11 +75,27 @@ function Houston() { } ``` +react-shiki exports `isInlineCode` to check if a code block is inline. + +```tsx +const isInline: boolean | undefined = node ? isInlineCode(node) : undefined; + +return !isInline ? ( + + {String(children)} + +) : ( + + {children} + +); +``` + It can also be used with `react-markdown`: ```tsx import type { ReactNode } from "react"; -import ShikiHighlighter, { isInlineCode, type Element } from "react-shiki"; +import ShikiHighlighter, { type Element } from "react-shiki"; interface CodeHighlightProps { className?: string | undefined; @@ -95,17 +112,9 @@ export const CodeHighlight = ({ const match = className?.match(/language-(\w+)/); const language = match ? match[1] : undefined; - const isInline: boolean | undefined = node ? isInlineCode(node) : undefined; - - return !isInline ? ( - - {String(children)} - - ) : ( - - {children} - - ); + + {String(children)} + ; }; ``` @@ -124,9 +133,103 @@ import { CodeHighlight } from "./CodeHighlight"; ; ``` -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: +### Custom themes -```tsx +```tsx:title=CodeHighlight.tsx +import tokyoNight from '@styles/tokyo-night.mjs'; + + + {String(code)} +; +``` + +## Client-side highlighting + +This works great for highlighting in real-time on the client, I use it for an LLM chatbot UI, it renders markdown and highlights code in memoized chat messages. It also Supports an optional delay between highlights for throttling. + +Using `useShikiHighlighter` hook: + +```tsx title=CodeHighlight.tsx +import type { ReactNode } from "react"; +import { isInlineCode, useShikiHighlighter, type Element } from "react-shiki"; +import tokyoNight from "@styles/tokyo-night.mjs"; + +interface CodeHighlightProps { + className?: string | undefined; + children?: ReactNode | undefined; + node?: Element | undefined; +} + +export const CodeHighlight = ({ + className, + children, + node, + ...props +}: CodeHighlightProps) => { + const code = String(children); + const language = className?.match(/language-(\w+)/)?.[1]; + + const isInline = node ? isInlineCode(node) : false; + + const highlightedCode = useShikiHighlighter(language, code, tokyoNight, { + delay: 150, + }); + + return !isInline ? ( +
+ {language ? ( + + {language} + + ) : null} + {highlightedCode} +
+ ) : ( + + {children} + + ); +}; +``` + +Or using the `ShikiHighlighter` component: + +```tsx title=CodeHighlight.tsx +import type { ReactNode } from "react"; +import ShikiHighlighter, { isInlineCode, type Element } from "react-shiki"; + +interface CodeHighlightProps { + className?: string | undefined; + children?: ReactNode | undefined; + node?: Element | undefined; +} + +export const CodeHighlight = ({ + className, + children, + node, + ...props +}: CodeHighlightProps): JSX.Element => { + const match = className?.match(/language-(\w+)/); + const language = match ? match[1] : undefined; + + const isInline: boolean | undefined = node ? isInlineCode(node) : undefined; + + return !isInline ? ( + + {String(children)} + + ) : ( + + {children} + + ); +}; +``` + +Passed to `react-markdown` as a code component in memoized chat messages: + +```tsx title=ChatMessages.tsx const RenderedMessage = React.memo(({ message }: { message: Message }) => (
@@ -145,4 +248,3 @@ export const ChatMessages = ({ messages }: { messages: Message[] }) => { ); }; ``` -