react-shiki
Advanced tools
Comparing version
@@ -1,3 +0,4 @@ | ||
function c(e,{insertAt:t}={}){if(!e||typeof document>"u")return;let i=document.head||document.getElementsByTagName("head")[0],r=document.createElement("style");r.type="text/css",t==="top"&&i.firstChild?i.insertBefore(r,i.firstChild):i.appendChild(r),r.styleSheet?r.styleSheet.cssText=e:r.appendChild(document.createTextNode(e))}c(`.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} | ||
`);import y from"react";import{clsx as x}from"clsx";import{useEffect as b,useRef as w,useState as k}from"react";import E from"html-react-parser";import{createHighlighter as L,ShikiError as f}from"shiki";import{visit as T}from"unist-util-visit";function S(){return function(e){T(e,"element",function(t,i,r){r&&r.tagName==="pre"?t.properties.inline=!1:t.properties.inline=!0})}}var H=e=>e.position?.start.line===e.position?.end.line,u={pre(e){return"properties"in e&&(e.properties.tabindex="-1"),e}};var d=null,P=async e=>{d||(d=L({themes:[],langs:[]}));let t=await d;return await R(t,e),t},R=async(e,t)=>{try{await e.loadTheme(t)}catch(i){throw i instanceof f&&console.warn("Error loading theme:",i.message),i}},C=async(e,t)=>{let i=t??"plaintext";try{return await e.loadLanguage(i),i}catch(r){return r instanceof f&&r.message.includes("not included in this bundle")&&console.warn(`Language '${i}' not supported, falling back to plaintext`),"plaintext"}},I=(e,t,i)=>{let r=Date.now();clearTimeout(t.current.timeoutId);let n=Math.max(0,t.current.nextAllowedTime-r);t.current.timeoutId=setTimeout(()=>{e().catch(console.error),t.current.nextAllowedTime=r+i},n)},p=(e,t,i,r={})=>{let[n,h]=k(null),o=w({nextAllowedTime:0,timeoutId:void 0});return b(()=>{let a=!0,l=async()=>{let s=await P(i),m=await C(s,t),g=s.codeToHtml(e,{lang:m,theme:i,transformers:[u]});a&&h(E(g))};return r.delay?I(l,o,r.delay):l().catch(console.error),()=>{a=!1,clearTimeout(o.current.timeoutId)}},[e,t]),n};var v=({language:e,theme:t,delay:i,addDefaultStyles:r=!0,style:n,langStyle:h,className:o,langClassName:a,showLanguage:l=!0,children:s,as:m="pre"})=>{let g=p(s,e,t,{delay:i});return y.createElement(m,{className:x("relative","not-prose",r&&"defaultStyles",o),style:n,id:"shiki-container"},l&&e?y.createElement("span",{className:x("languageLabel",a),style:h,id:"language-label"},e):null,g)};export{v as default,H as isInlineCode,S as rehypeInlineCodeProperty,p as useShikiHighlighter}; | ||
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} | ||
`);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(` | ||
`),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}; | ||
//# sourceMappingURL=index.js.map |
{ | ||
"name": "react-shiki", | ||
"description": "Syntax highlighter component for react using shiki", | ||
"version": "0.2.4", | ||
"version": "0.3.0", | ||
"license": "MIT", | ||
@@ -6,0 +6,0 @@ "author": { |
@@ -21,2 +21,3 @@ # 🎨 [react-shiki](https://npmjs.com/react-shiki) | ||
- [Custom themes](#custom-themes) | ||
- [Custom transformers](#custom-transformers) | ||
- [Performance](#performance) | ||
@@ -32,4 +33,5 @@ - [Throttling real-time highlighting](#throttling-real-time-highlighting) | ||
- 🔐 No `dangerouslySetInnerHTML`, output from Shiki is parsed using `html-react-parser` | ||
- 📦 Supports all Shiki languages and themes in addition to | ||
- 📦 Supports all Shiki languages and themes | ||
- 🖌️ Full support for custom TextMate themes in a JavaScript object format | ||
- 🔧 Supports passing custom Shiki transformers to the highlighter | ||
- 🚰 Performant highlighting of streamed code on the client, with optional throttling | ||
@@ -141,3 +143,3 @@ - 📚 Includes minimal default styles for code blocks | ||
<ShikiHighlighter language={language} theme={"houston"} {...props}> | ||
{String(children)} | ||
{String(children).trim()} | ||
</ShikiHighlighter>; | ||
@@ -173,3 +175,3 @@ }; | ||
<ShikiHighlighter language={language} theme={"houston"} {...props}> | ||
{String(children)} | ||
{String(children).trim()} | ||
</ShikiHighlighter> | ||
@@ -217,3 +219,3 @@ ) : ( | ||
<ShikiHighlighter language={language} theme={"houston"} {...props}> | ||
{String(children)} | ||
{String(children).trim()} | ||
</ShikiHighlighter> | ||
@@ -229,3 +231,3 @@ ) : ( | ||
```tsx:title=CodeHighlight.tsx | ||
```tsx | ||
import tokyoNight from '@styles/tokyo-night.mjs'; | ||
@@ -238,2 +240,14 @@ | ||
### Custom transformers | ||
```tsx | ||
import { customTransformer } from '@utils/customTransformers'; | ||
<ShikiHighlighter | ||
language="tsx" | ||
transformers={[customTransformer]}> | ||
{String(code).trim()} | ||
</ShikiHighlighter>; | ||
``` | ||
## Performance | ||
@@ -258,8 +272,8 @@ | ||
I use it for an | ||
LLM chatbot UI, it renders markdown and highlights code in memoized chat messages. | ||
I use it for an LLM chatbot UI, it renders markdown and highlights | ||
code in memoized chat messages. | ||
Using `useShikiHighlighter` hook: | ||
```tsx title=CodeHighlight.tsx | ||
```tsx | ||
import type { ReactNode } from "react"; | ||
@@ -281,3 +295,3 @@ import { isInlineCode, useShikiHighlighter, type Element } from "react-shiki"; | ||
}: CodeHighlightProps) => { | ||
const code = String(children); | ||
const code = String(children).trim(); | ||
const language = className?.match(/language-(\w+)/)?.[1]; | ||
@@ -316,3 +330,3 @@ | ||
```tsx title=CodeHighlight.tsx | ||
```tsx | ||
import type { ReactNode } from "react"; | ||
@@ -345,3 +359,3 @@ import ShikiHighlighter, { isInlineCode, type Element } from "react-shiki"; | ||
> | ||
{String(children)} | ||
{String(children).trim()} | ||
</ShikiHighlighter> | ||
@@ -358,3 +372,3 @@ ) : ( | ||
```tsx title=ChatMessages.tsx | ||
```tsx | ||
const RenderedMessage = React.memo(({ message }: { message: Message }) => ( | ||
@@ -361,0 +375,0 @@ <div className={cn(messageStyles[message.role])}> |
Sorry, the diff of this file is not supported yet
32512
14.31%6
20%145
1218.18%380
3.83%