react-shiki 0.7.4 → 0.8.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
@@ -1,27 +1,31 @@
1
1
  # 🎨 [react-shiki](https://npmjs.com/react-shiki)
2
2
 
3
- > [!NOTE]
4
- > This library is still in development. More features will be implemented, and the API may change.
5
- > Contributions are welcome!
6
3
 
7
4
  A performant client-side syntax highlighting component and hook for React, built with [Shiki](https://shiki.matsu.io/).
8
5
 
9
6
  [See the demo page with highlighted code blocks showcasing several Shiki themes!](https://react-shiki.vercel.app/)
10
7
 
11
8
  <!--toc:start-->
12
-
13
9
  - 🎨 [react-shiki](https://npmjs.com/react-shiki)
14
10
  - [Features](#features)
15
11
  - [Installation](#installation)
16
12
  - [Usage](#usage)
17
13
  - [Bundle Options](#bundle-options)
14
+ - [`react-shiki` (Full Bundle)](#react-shiki-full-bundle)
15
+ - [`react-shiki/web` (Web Bundle)](#react-shikiweb-web-bundle)
16
+ - [`react-shiki/core` (Minimal Bundle)](#react-shikicore-minimal-bundle)
17
+ - [RegExp Engines](#regexp-engines)
18
18
  - [Configuration](#configuration)
19
19
  - [Common Configuration Options](#common-configuration-options)
20
20
  - [Component-specific Props](#component-specific-props)
21
21
  - [Multi-theme Support](#multi-theme-support)
22
+ - [Making Themes Reactive](#making-themes-reactive)
23
+ - [Option 1: Using `light-dark()` Function (Recommended)](#option-1-using-light-dark-function-recommended)
24
+ - [Option 2: CSS Theme Switching](#option-2-css-theme-switching)
22
25
  - [Custom Themes](#custom-themes)
23
26
  - [Custom Languages](#custom-languages)
24
27
  - [Preloading Custom Languages](#preloading-custom-languages)
28
+ - [Language Aliases](#language-aliases)
25
29
  - [Custom Transformers](#custom-transformers)
26
30
  - [Line Numbers](#line-numbers)
27
31
  - [Integration](#integration)
@@ -29,13 +33,14 @@ A performant client-side syntax highlighting component and hook for React, built
29
33
  - [Handling Inline Code](#handling-inline-code)
30
34
  - [Performance](#performance)
31
35
  - [Throttling Real-time Highlighting](#throttling-real-time-highlighting)
36
+ - [Output Format Optimization](#output-format-optimization)
32
37
  - [Streaming and LLM Chat UI](#streaming-and-llm-chat-ui)
33
- <!--toc:end-->
38
+ <!--toc:end-->
34
39
 
35
40
  ## Features
36
41
 
37
42
  - 🖼️ Provides both a `ShikiHighlighter` component and a `useShikiHighlighter` hook for more flexibility
38
- - 🔐 Shiki output is processed from HAST directly into React elements, no `dangerouslySetInnerHTML` required
43
+ - 🔐 Flexible output: Choose between React elements (no `dangerouslySetInnerHTML`) or HTML strings for better performance
39
44
  - 📦 Multiple bundle options: Full bundle (~1.2MB gz), web bundle (~695KB gz), or minimal core bundle for fine-grained bundle control
40
45
  - 🖌️ Full support for custom TextMate themes and languages
41
46
  - 🔧 Supports passing custom Shiki transformers to the highlighter, in addition to all other options supported by `codeToHast`
@@ -90,7 +95,7 @@ function CodeBlock({ code, language }) {
90
95
  ```tsx
91
96
  import ShikiHighlighter from 'react-shiki';
92
97
  ```
93
- - **Size**: ~6.4MB minified, 1.2MB gzipped
98
+ - **Size**: ~6.4MB minified, ~1.2MB gzipped (includes ~12KB react-shiki)
94
99
  - **Languages**: All Shiki languages and themes
95
100
  - **Use case**: Unknown language requirements, maximum language support
96
101
  - **Setup**: Zero configuration required
@@ -99,7 +104,7 @@ import ShikiHighlighter from 'react-shiki';
99
104
  ```tsx
100
105
  import ShikiHighlighter from 'react-shiki/web';
101
106
  ```
102
- - **Size**: ~3.8MB minified, 695KB gzipped
107
+ - **Size**: ~3.8MB minified, ~707KB gzipped (includes ~12KB react-shiki)
103
108
  - **Languages**: Web-focused languages (HTML, CSS, JS, TS, JSON, Markdown, Vue, JSX, Svelte)
104
109
  - **Use case**: Web applications with balanced size/functionality
105
110
  - **Setup**: Drop-in replacement for main entry point
@@ -124,7 +129,7 @@ const highlighter = await createHighlighterCore({
124
129
  {code}
125
130
  </ShikiHighlighter>
126
131
  ```
127
- - **Size**: Minimal (only what you import)
132
+ - **Size**: ~12KB + your imported themes/languages
128
133
  - **Languages**: User-defined via custom highlighter
129
134
  - **Use case**: Production apps requiring maximum bundle control
130
135
  - **Setup**: Requires custom highlighter configuration
@@ -163,6 +168,7 @@ See [Shiki - RegExp Engines](https://shiki.style/guide/regex-engines) for more i
163
168
  | `transformers` | `array` | `[]` | Custom Shiki transformers for modifying the highlighting output |
164
169
  | `cssVariablePrefix` | `string` | `'--shiki'` | Prefix for CSS variables storing theme colors |
165
170
  | `defaultColor` | `string \| false` | `'light'` | Default theme mode when using multiple themes, can also disable default theme |
171
+ | `outputFormat` | `string` | `'react'` | Output format: 'react' for React nodes, 'html' for HTML string |
166
172
  | `tabindex` | `number` | `0` | Tab index for the code block |
167
173
  | `decorations` | `array` | `[]` | Custom decorations to wrap the highlighted tokens with |
168
174
  | `structure` | `string` | `classic` | The structure of the generated HAST and HTML - `classic` or `inline` |
@@ -253,12 +259,12 @@ Ensure your site sets the `color-scheme` CSS property:
253
259
  color-scheme: light dark;
254
260
  }
255
261
 
256
- /* Or dynamically with a class */
257
- * {
262
+ /* Or dynamically for class based dark mode */
263
+ :root {
258
264
  color-scheme: light;
259
265
  }
260
266
 
261
- .dark {
267
+ :root.dark {
262
268
  color-scheme: dark;
263
269
  }
264
270
  ```
@@ -564,121 +570,36 @@ const highlightedCode = useShikiHighlighter(code, "tsx", "github-dark", {
564
570
  });
565
571
  ```
566
572
 
567
- ### Streaming and LLM Chat UI
568
-
569
- `react-shiki` can be used to highlight streamed code from LLM responses in real-time.
573
+ ### Output Format Optimization
570
574
 
571
- I use it for an LLM chatbot UI, it renders markdown and highlights
572
- code in memoized chat messages.
573
-
574
- Using `useShikiHighlighter` hook:
575
+ `react-shiki` provides two output formats to balance safety and performance:
575
576
 
577
+ **React Nodes (Default)** - Safer, no `dangerouslySetInnerHTML` required
576
578
  ```tsx
577
- import type { ReactNode } from "react";
578
- import { isInlineCode, useShikiHighlighter, type Element } from "react-shiki";
579
- import tokyoNight from "@styles/tokyo-night.mjs";
580
-
581
- interface CodeHighlightProps {
582
- className?: string | undefined;
583
- children?: ReactNode | undefined;
584
- node?: Element | undefined;
585
- }
586
-
587
- export const CodeHighlight = ({
588
- className,
589
- children,
590
- node,
591
- ...props
592
- }: CodeHighlightProps) => {
593
- const code = String(children).trim();
594
- const language = className?.match(/language-(\w+)/)?.[1];
595
-
596
- const isInline = node ? isInlineCode(node) : false;
597
-
598
- const highlightedCode = useShikiHighlighter(code, language, tokyoNight, {
599
- delay: 150,
600
- });
579
+ // Hook
580
+ const highlightedCode = useShikiHighlighter(code, "tsx", "github-dark");
601
581
 
602
- return !isInline ? (
603
- <div
604
- className="shiki not-prose relative [&_pre]:overflow-auto
605
- [&_pre]:rounded-lg [&_pre]:px-6 [&_pre]:py-5"
606
- >
607
- {language ? (
608
- <span
609
- className="absolute right-3 top-2 text-xs tracking-tighter
610
- text-muted-foreground/85"
611
- >
612
- {language}
613
- </span>
614
- ) : null}
615
- {highlightedCode}
616
- </div>
617
- ) : (
618
- <code className={className} {...props}>
619
- {children}
620
- </code>
621
- );
622
- };
582
+ // Component
583
+ <ShikiHighlighter language="tsx" theme="github-dark">
584
+ {code}
585
+ </ShikiHighlighter>
623
586
  ```
624
587
 
625
- Or using the `ShikiHighlighter` component:
626
-
588
+ **HTML String** - 15-45% faster performance
627
589
  ```tsx
628
- import type { ReactNode } from "react";
629
- import ShikiHighlighter, { isInlineCode, type Element } from "react-shiki";
630
-
631
- interface CodeHighlightProps {
632
- className?: string | undefined;
633
- children?: ReactNode | undefined;
634
- node?: Element | undefined;
635
- }
636
-
637
- export const CodeHighlight = ({
638
- className,
639
- children,
640
- node,
641
- ...props
642
- }: CodeHighlightProps): JSX.Element => {
643
- const match = className?.match(/language-(\w+)/);
644
- const language = match ? match[1] : undefined;
645
- const code = String(children).trim();
646
-
647
- const isInline: boolean | undefined = node ? isInlineCode(node) : undefined;
590
+ // Hook (returns HTML string, use dangerouslySetInnerHTML to render)
591
+ const highlightedCode = useShikiHighlighter(code, "tsx", "github-dark", {
592
+ outputFormat: 'html'
593
+ });
648
594
 
649
- return !isInline ? (
650
- <ShikiHighlighter
651
- language={language}
652
- theme="github-dark"
653
- delay={150}
654
- {...props}
655
- >
656
- {code}
657
- </ShikiHighlighter>
658
- ) : (
659
- <code className={className}>{code}</code>
660
- );
661
- };
595
+ // Component (automatically uses dangerouslySetInnerHTML when outputFormat is 'html')
596
+ <ShikiHighlighter language="tsx" theme="github-dark" outputFormat="html">
597
+ {code}
598
+ </ShikiHighlighter>
662
599
  ```
663
600
 
664
- Passed to `react-markdown` as a `code` component in memoized chat messages:
601
+ Choose HTML output when performance is critical and you trust the code source. Use the default React output when handling untrusted content or when security is the primary concern.
665
602
 
666
- ```tsx
667
- const RenderedMessage = React.memo(({ message }: { message: Message }) => (
668
- <div className={cn(messageStyles[message.role])}>
669
- <ReactMarkdown components={{ code: CodeHighlight }}>
670
- {message.content}
671
- </ReactMarkdown>
672
- </div>
673
- ));
674
-
675
- export const ChatMessages = ({ messages }: { messages: Message[] }) => {
676
- return (
677
- <div className="space-y-4">
678
- {messages.map((message) => (
679
- <RenderedMessage key={message.id} message={message} />
680
- ))}
681
- </div>
682
- );
683
- };
684
- ```
603
+ ---
604
+
605
+ Made with ❤️ by [Bassim (AVGVSTVS96)](https://github.com/AVGVSTVS96)
@@ -1,17 +1,33 @@
1
+ // src/lib/hook.ts
2
+ import {
3
+ useEffect,
4
+ useMemo,
5
+ useRef as useRef2,
6
+ useState
7
+ } from "react";
8
+ import { jsx, jsxs, Fragment } from "react/jsx-runtime";
9
+ import { toJsxRuntime } from "hast-util-to-jsx-runtime";
10
+
1
11
  // src/lib/utils.ts
2
- import { visit } from "unist-util-visit";
3
- function rehypeInlineCodeProperty() {
4
- return (tree) => {
5
- visit(tree, "element", (node, _index, parent) => {
6
- if (node.tagName === "code" && parent.tagName !== "pre") {
7
- node.properties.inline = true;
8
- }
9
- });
10
- };
11
- }
12
- var isInlineCode = (node) => {
13
- const textContent = (node.children || []).filter((child) => child.type === "text").map((child) => child.value).join("");
14
- return !textContent.includes("\n");
12
+ import { useRef } from "react";
13
+ import { dequal } from "dequal";
14
+ var useStableOptions = (value) => {
15
+ const ref = useRef(value);
16
+ const revision = useRef(0);
17
+ if (typeof value !== "object" || value === null) {
18
+ if (value !== ref.current) {
19
+ ref.current = value;
20
+ revision.current += 1;
21
+ }
22
+ return [value, revision.current];
23
+ }
24
+ if (value !== ref.current) {
25
+ if (!dequal(value, ref.current)) {
26
+ ref.current = value;
27
+ revision.current += 1;
28
+ }
29
+ }
30
+ return [ref.current, revision.current];
15
31
  };
16
32
  var throttleHighlighting = (performHighlight, timeoutControl, throttleMs) => {
17
33
  const now = Date.now();
@@ -23,17 +39,6 @@ var throttleHighlighting = (performHighlight, timeoutControl, throttleMs) => {
23
39
  }, delay);
24
40
  };
25
41
 
26
- // src/lib/hook.ts
27
- import {
28
- useEffect,
29
- useMemo,
30
- useRef,
31
- useState
32
- } from "react";
33
- import { dequal } from "dequal/lite";
34
- import { jsx, jsxs, Fragment } from "react/jsx-runtime";
35
- import { toJsxRuntime } from "hast-util-to-jsx-runtime";
36
-
37
42
  // src/lib/resolvers.ts
38
43
  var resolveLanguage = (lang, customLanguages, langAliases) => {
39
44
  const normalizedCustomLangs = customLanguages ? Array.isArray(customLanguages) ? customLanguages : [customLanguages] : [];
@@ -128,24 +133,6 @@ var DEFAULT_THEMES = {
128
133
  light: "github-light",
129
134
  dark: "github-dark"
130
135
  };
131
- var useStableOptions = (value) => {
132
- const ref = useRef(value);
133
- const revision = useRef(0);
134
- if (typeof value !== "object" || value === null) {
135
- if (value !== ref.current) {
136
- ref.current = value;
137
- revision.current += 1;
138
- }
139
- return [value, revision.current];
140
- }
141
- if (value !== ref.current) {
142
- if (!dequal(value, ref.current)) {
143
- ref.current = value;
144
- revision.current += 1;
145
- }
146
- }
147
- return [ref.current, revision.current];
148
- };
149
136
  var useShikiHighlighter = (code, lang, themeInput, createHighlighter, options = {}) => {
150
137
  const [highlightedCode, setHighlightedCode] = useState(null);
151
138
  const [stableLang, langRev] = useStableOptions(lang);
@@ -160,7 +147,7 @@ var useShikiHighlighter = (code, lang, themeInput, createHighlighter, options =
160
147
  [stableLang, stableOpts.customLanguages, stableOpts.langAlias]
161
148
  );
162
149
  const { isMultiTheme, themeId, multiTheme, singleTheme, themesToLoad } = useMemo(() => resolveTheme(stableTheme), [stableTheme]);
163
- const timeoutControl = useRef({
150
+ const timeoutControl = useRef2({
164
151
  nextAllowedTime: 0,
165
152
  timeoutId: void 0
166
153
  });
@@ -202,9 +189,13 @@ var useShikiHighlighter = (code, lang, themeInput, createHighlighter, options =
202
189
  const loadedLanguages = highlighter.getLoadedLanguages();
203
190
  const langToUse = loadedLanguages.includes(languageId) ? languageId : "plaintext";
204
191
  const finalOptions = { ...shikiOptions, lang: langToUse };
205
- const hast = highlighter.codeToHast(code, finalOptions);
206
192
  if (isMounted) {
207
- setHighlightedCode(toJsxRuntime(hast, { jsx, jsxs, Fragment }));
193
+ const output = stableOpts.outputFormat === "html" ? highlighter.codeToHtml(code, finalOptions) : toJsxRuntime(highlighter.codeToHast(code, finalOptions), {
194
+ jsx,
195
+ jsxs,
196
+ Fragment
197
+ });
198
+ setHighlightedCode(output);
208
199
  }
209
200
  };
210
201
  const { delay } = stableOpts;
@@ -228,6 +219,22 @@ var useShikiHighlighter = (code, lang, themeInput, createHighlighter, options =
228
219
  return highlightedCode;
229
220
  };
230
221
 
222
+ // src/lib/plugins.ts
223
+ import { visit } from "unist-util-visit";
224
+ function rehypeInlineCodeProperty() {
225
+ return (tree) => {
226
+ visit(tree, "element", (node, _index, parent) => {
227
+ if (node.tagName === "code" && parent.tagName !== "pre") {
228
+ node.properties.inline = true;
229
+ }
230
+ });
231
+ };
232
+ }
233
+ var isInlineCode = (node) => {
234
+ const textContent = (node.children || []).filter((child) => child.type === "text").map((child) => child.value).join("");
235
+ return !textContent.includes("\n");
236
+ };
237
+
231
238
  // #style-inject:#style-inject
232
239
  function styleInject(css, { insertAt } = {}) {
233
240
  if (!css || typeof document === "undefined") return;
@@ -299,6 +306,7 @@ var createShikiHighlighterComponent = (useShikiHighlighterImpl) => {
299
306
  theme,
300
307
  options
301
308
  );
309
+ const isHtmlOutput = typeof highlightedCode === "string";
302
310
  return /* @__PURE__ */ jsxs2(
303
311
  Element,
304
312
  {
@@ -322,7 +330,7 @@ var createShikiHighlighterComponent = (useShikiHighlighterImpl) => {
322
330
  children: displayLanguageId
323
331
  }
324
332
  ) : null,
325
- highlightedCode
333
+ isHtmlOutput ? /* @__PURE__ */ jsx2("div", { dangerouslySetInnerHTML: { __html: highlightedCode } }) : highlightedCode
326
334
  ]
327
335
  }
328
336
  );
@@ -331,9 +339,9 @@ var createShikiHighlighterComponent = (useShikiHighlighterImpl) => {
331
339
  };
332
340
 
333
341
  export {
342
+ useShikiHighlighter,
334
343
  rehypeInlineCodeProperty,
335
344
  isInlineCode,
336
- useShikiHighlighter,
337
345
  createShikiHighlighterComponent
338
346
  };
339
- //# sourceMappingURL=chunk-ZUXU23SW.js.map
347
+ //# sourceMappingURL=chunk-JAL4YDG6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/hook.ts","../src/lib/utils.ts","../src/lib/resolvers.ts","../src/lib/transformers.ts","../src/lib/plugins.ts","#style-inject:#style-inject","../src/lib/styles.css","../src/lib/component.tsx"],"sourcesContent":["import {\n useEffect,\n useMemo,\n useRef,\n useState,\n type ReactNode,\n} from 'react';\n\nimport { jsx, jsxs, Fragment } from 'react/jsx-runtime';\nimport { toJsxRuntime } from 'hast-util-to-jsx-runtime';\n\nimport type {\n CodeToHastOptions,\n CodeOptionsSingleTheme,\n CodeOptionsMultipleThemes,\n Highlighter,\n HighlighterCore,\n} from 'shiki';\n\nimport type { ShikiLanguageRegistration } from './extended-types';\n\nimport type {\n Language,\n Theme,\n HighlighterOptions,\n TimeoutState,\n Themes,\n} from './types';\n\nimport { throttleHighlighting, useStableOptions } from './utils';\nimport { resolveLanguage, resolveTheme } from './resolvers';\nimport { lineNumbersTransformer } from './transformers';\n\nconst DEFAULT_THEMES: Themes = {\n light: 'github-light',\n dark: 'github-dark',\n};\n\n/**\n * Base hook for syntax highlighting using Shiki.\n * This is the core implementation used by all entry points.\n *\n * @param code - The code to highlight\n * @param lang - Language for highlighting\n * @param themeInput - Theme or themes to use\n * @param options - Highlighting options\n * @param createHighlighter - Factory function to create highlighter (internal use)\n */\nexport const useShikiHighlighter = (\n code: string,\n lang: Language,\n themeInput: Theme | Themes,\n createHighlighter: (\n langsToLoad: ShikiLanguageRegistration,\n themesToLoad: Theme[]\n ) => Promise<Highlighter | HighlighterCore>,\n options: HighlighterOptions = {}\n) => {\n const [highlightedCode, setHighlightedCode] = useState<\n ReactNode | string | null\n >(null);\n\n // Stabilize options, language and theme inputs to prevent unnecessary\n // re-renders or recalculations when object references change\n const [stableLang, langRev] = useStableOptions(lang);\n const [stableTheme, themeRev] = useStableOptions(themeInput);\n const [stableOpts, optsRev] = useStableOptions(options);\n\n const { languageId, langsToLoad } = useMemo(\n () =>\n resolveLanguage(\n stableLang,\n stableOpts.customLanguages,\n stableOpts.langAlias\n ),\n [stableLang, stableOpts.customLanguages, stableOpts.langAlias]\n );\n\n const { isMultiTheme, themeId, multiTheme, singleTheme, themesToLoad } =\n useMemo(() => resolveTheme(stableTheme), [stableTheme]);\n\n const timeoutControl = useRef<TimeoutState>({\n nextAllowedTime: 0,\n timeoutId: undefined,\n });\n\n const shikiOptions = useMemo<CodeToHastOptions>(() => {\n const languageOption = { lang: languageId };\n const {\n defaultColor,\n cssVariablePrefix,\n showLineNumbers,\n startingLineNumber,\n ...restOptions\n } = stableOpts;\n\n const themeOptions = isMultiTheme\n ? ({\n themes: multiTheme || DEFAULT_THEMES,\n defaultColor,\n cssVariablePrefix,\n } as CodeOptionsMultipleThemes)\n : ({\n theme: singleTheme || DEFAULT_THEMES.dark,\n } as CodeOptionsSingleTheme);\n\n const transformers = restOptions.transformers || [];\n if (showLineNumbers) {\n transformers.push(lineNumbersTransformer(startingLineNumber));\n }\n\n return {\n ...languageOption,\n ...themeOptions,\n ...restOptions,\n transformers,\n };\n }, [languageId, themeId, langRev, themeRev, optsRev]);\n\n useEffect(() => {\n let isMounted = true;\n\n const highlightCode = async () => {\n if (!languageId) return;\n\n const highlighter = stableOpts.highlighter\n ? stableOpts.highlighter\n : await createHighlighter(\n langsToLoad as ShikiLanguageRegistration,\n themesToLoad\n );\n\n const loadedLanguages = highlighter.getLoadedLanguages();\n const langToUse = loadedLanguages.includes(languageId)\n ? languageId\n : 'plaintext';\n const finalOptions = { ...shikiOptions, lang: langToUse };\n\n if (isMounted) {\n const output =\n stableOpts.outputFormat === 'html'\n ? highlighter.codeToHtml(code, finalOptions)\n : toJsxRuntime(highlighter.codeToHast(code, finalOptions), {\n jsx,\n jsxs,\n Fragment,\n });\n setHighlightedCode(output);\n }\n };\n\n const { delay } = stableOpts;\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 }, [\n code,\n shikiOptions,\n stableOpts.delay,\n stableOpts.highlighter,\n langsToLoad,\n themesToLoad,\n ]);\n\n return highlightedCode;\n};\n","import { useRef } from 'react';\nimport { dequal } from 'dequal';\n\nimport type { TimeoutState } from './types';\n\n/**\n * Returns a deep-stable reference and a version counter that only changes when content changes.\n * Includes optimizations for primitive values and reference equality.\n */\nexport const useStableOptions = <T>(value: T) => {\n const ref = useRef(value);\n const revision = useRef(0);\n\n // Fast-path for primitive values\n if (typeof value !== 'object' || value === null) {\n if (value !== ref.current) {\n ref.current = value;\n revision.current += 1;\n }\n return [value, revision.current] as const;\n }\n\n // Reference equality check before expensive deep comparison\n if (value !== ref.current) {\n if (!dequal(value, ref.current)) {\n ref.current = value;\n revision.current += 1;\n }\n }\n\n return [ref.current, revision.current] as const;\n};\n\n/**\n * Optionally throttles rapid sequential highlighting operations\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.RefObject<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","import type { Language, Theme, Themes } from './types';\nimport type { ThemeRegistrationAny } from 'shiki/core';\nimport type { LanguageRegistration } from './extended-types';\n\n/**\n * Resolved languages and metadata\n */\ntype LanguageResult = {\n languageId: string;\n displayLanguageId: string | null;\n langsToLoad: Language;\n};\n\n/**\n * Resolves the language input to standardized IDs and objects for Shiki and UI display\n * @param lang The language input from props\n * @param customLanguages An array of custom textmate grammar objects or a single grammar object\n * @returns A LanguageResult object containing:\n * - languageId: The resolved language ID\n * - displayLanguageId: The display language ID\n * - langToLoad: The language object or string id to load\n */\nexport const resolveLanguage = (\n lang: Language,\n customLanguages?: LanguageRegistration | LanguageRegistration[],\n langAliases?: Record<string, string>\n): LanguageResult => {\n const normalizedCustomLangs = customLanguages\n ? Array.isArray(customLanguages)\n ? customLanguages\n : [customLanguages]\n : [];\n\n // Language is null or empty string\n if (lang == null || (typeof lang === 'string' && !lang.trim())) {\n return {\n languageId: 'plaintext',\n displayLanguageId: 'plaintext',\n langsToLoad: undefined,\n };\n }\n\n // Language is custom\n if (typeof lang === 'object') {\n return {\n languageId: lang.name,\n displayLanguageId: lang.name || null,\n langsToLoad: lang,\n };\n }\n\n // Language is string\n const lowerLang = lang.toLowerCase();\n const matches = (str: string | undefined): boolean =>\n str?.toLowerCase() === lowerLang;\n\n // Check if the string identifies a provided custom language\n const customMatch = normalizedCustomLangs.find(\n (cl) =>\n matches(cl.name) ||\n matches(cl.scopeName) ||\n matches(cl.scopeName?.split('.').pop()) ||\n cl.aliases?.some(matches) ||\n cl.fileTypes?.some(matches)\n );\n\n if (customMatch) {\n return {\n languageId: customMatch.name || lang,\n displayLanguageId: lang,\n langsToLoad: customMatch,\n };\n }\n\n // Check if language is aliased\n if (langAliases?.[lang]) {\n return {\n languageId: langAliases[lang],\n displayLanguageId: lang,\n langsToLoad: langAliases[lang],\n };\n }\n\n // For any other string, pass it through,\n // fallback is handled in highlighter factories\n return {\n languageId: lang,\n displayLanguageId: lang,\n langsToLoad: lang,\n };\n};\n\n/**\n * Resolved themes and metadata\n */\ninterface ThemeResult {\n isMultiTheme: boolean;\n themeId: Theme;\n multiTheme?: Themes | ThemeRegistrationAny | null;\n singleTheme?: Theme | undefined;\n themesToLoad: Theme[];\n}\n\n/**\n * Determines theme configuration and returns the resolved theme with metadata\n * @param themeInput - The theme input, either as a string name or theme object\n * @returns Object containing:\n * - isMultiTheme: If theme input is a multi-theme configuration\n * - themeId: Theme reference identifier\n * - multiTheme: The multi-theme config if it exists\n * - singleTheme: The single theme if it exists\n * - themesToLoad: The themes to load when creating the highlighter\n */\nexport function resolveTheme(themeInput: Theme | Themes): ThemeResult {\n const isTextmateTheme =\n typeof themeInput === 'object' &&\n 'tokenColors' in themeInput &&\n Array.isArray(themeInput.tokenColors);\n\n // Assume non textmate objects are multi theme configs\n const isMultiThemeConfig =\n typeof themeInput === 'object' &&\n themeInput !== null &&\n !isTextmateTheme;\n\n const validMultiThemeObj =\n typeof themeInput === 'object' &&\n themeInput !== null &&\n !isTextmateTheme &&\n Object.entries(themeInput).some(\n ([key, value]) =>\n key &&\n value &&\n key.trim() !== '' &&\n value !== '' &&\n (typeof value === 'string' || isTextmateTheme)\n );\n\n if (isMultiThemeConfig) {\n const themeId = validMultiThemeObj\n ? `multi-${Object.values(themeInput)\n .map(\n (theme) =>\n (typeof theme === 'string' ? theme : theme?.name) ||\n 'custom'\n )\n .sort()\n .join('-')}`\n : 'multi-default';\n\n // If config is invalid, return null to handle fallback in `buildShikiOptions()`\n return {\n isMultiTheme: true,\n themeId,\n multiTheme: validMultiThemeObj ? themeInput : null,\n themesToLoad: validMultiThemeObj ? Object.values(themeInput) : [],\n };\n }\n\n return {\n isMultiTheme: false,\n themeId:\n typeof themeInput === 'string'\n ? themeInput\n : themeInput?.name || 'custom',\n singleTheme: themeInput,\n themesToLoad: [themeInput],\n };\n}\n","import type { ShikiTransformer } from 'shiki/core';\n\n/**\n * Creates a transformer that enables line numbers display\n * @param startLine - The starting line number (defaults to 1)\n */\nexport function lineNumbersTransformer(startLine = 1): ShikiTransformer {\n return {\n name: 'react-shiki:line-numbers',\n code(node) {\n this.addClassToHast(node, 'has-line-numbers');\n if (startLine !== 1) {\n const existingStyle = (node.properties?.style as string) || '';\n const newStyle = existingStyle\n ? `${existingStyle}; --line-start: ${startLine}`\n : `--line-start: ${startLine}`;\n node.properties = {\n ...node.properties,\n style: newStyle,\n };\n }\n },\n line(node) {\n this.addClassToHast(node, 'line-numbers');\n return node;\n },\n };\n}\n","import { visit } from 'unist-util-visit';\nimport type { Element } from './types';\n\n/**\n * Rehype plugin to add an 'inline' property to <code> elements\n * Sets 'inline' property to true if the <code> is not within a <pre> tag\n *\n * Pass this plugin to the `rehypePlugins` prop of react-markdown\n * You can then access `inline` as a prop in components passed to react-markdown\n *\n * @example\n * <ReactMarkdown rehypePlugins={[rehypeInlineCodeProperty]} />\n */\nexport function rehypeInlineCodeProperty() {\n return (tree: any): undefined => {\n visit(tree, 'element', (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 * Function to determine if code is inline based on the presence of line breaks\n *\n * @example\n * const isInline = node && isInlineCode(node: Element)\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 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 {\\n position: relative;\\n}\\n.defaultStyles pre {\\n overflow: auto;\\n border-radius: 0.5rem;\\n padding-left: 1.5rem;\\n padding-right: 1.5rem;\\n padding-top: 1.25rem;\\n padding-bottom: 1.25rem;\\n}\\n.languageLabel {\\n position: absolute;\\n right: 0.75rem;\\n top: 0.5rem;\\n font-family: monospace;\\n font-size: 0.75rem;\\n letter-spacing: -0.05em;\\n color: rgba(107, 114, 128, 0.85);\\n}\\n.line-numbers::before {\\n counter-increment: line-number;\\n content: counter(line-number);\\n display: inline-flex;\\n justify-content: flex-end;\\n align-items: flex-start;\\n box-sizing: content-box;\\n min-width: var(--line-numbers-width, 2ch);\\n padding-left: var(--line-numbers-padding-left, 2ch);\\n padding-right: var(--line-numbers-padding-right, 2ch);\\n color: var(--line-numbers-foreground, rgba(107, 114, 128, 0.6));\\n font-size: var(--line-numbers-font-size, inherit);\\n font-weight: var(--line-numbers-font-weight, inherit);\\n line-height: var(--line-numbers-line-height, inherit);\\n font-family: var(--line-numbers-font-family, inherit);\\n opacity: var(--line-numbers-opacity, 1);\\n user-select: none;\\n pointer-events: none;\\n}\\n.has-line-numbers {\\n counter-reset: line-number calc(var(--line-start, 1) - 1);\\n --line-numbers-foreground: rgba(107, 114, 128, 0.5);\\n --line-numbers-width: 2ch;\\n --line-numbers-padding-left: 0ch;\\n --line-numbers-padding-right: 2ch;\\n --line-numbers-font-size: inherit;\\n --line-numbers-font-weight: inherit;\\n --line-numbers-line-height: inherit;\\n --line-numbers-font-family: inherit;\\n --line-numbers-opacity: 1;\\n}\\n\")","import './styles.css';\nimport { clsx } from 'clsx';\nimport { resolveLanguage } from './resolvers';\n\nimport type {\n HighlighterOptions,\n Language,\n Theme,\n Themes,\n UseShikiHighlighter,\n} from './types';\nimport { forwardRef } from 'react';\n\n/**\n * Props for the ShikiHighlighter component\n */\nexport interface ShikiHighlighterProps extends HighlighterOptions {\n /**\n * The programming language for syntax highlighting\n * Supports custom textmate grammar objects in addition to Shiki's bundled languages\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 or themes for syntax highlighting\n * Supports single, dual, or multiple themes\n * Supports custom textmate theme objects in addition to Shiki's bundled themes\n *\n * @example\n * theme='github-dark' // single theme\n * theme={{ light: 'github-light', dark: 'github-dark' }} // multi-theme\n *\n * @see https://shiki.style/themes\n */\n theme: Theme | Themes;\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 * Whether to show line numbers\n * @default false\n */\n showLineNumbers?: boolean;\n\n /**\n * Starting line number (when showLineNumbers is true)\n * @default 1\n */\n startingLineNumber?: number;\n\n /**\n * The HTML element that wraps the generated code block.\n * @default 'pre'\n */\n as?: React.ElementType;\n}\n\n/**\n * Base ShikiHighlighter component factory.\n * This creates a component that uses the provided hook implementation.\n */\nexport const createShikiHighlighterComponent = (\n useShikiHighlighterImpl: UseShikiHighlighter\n) => {\n return forwardRef<HTMLElement, ShikiHighlighterProps>(\n (\n {\n language,\n theme,\n delay,\n transformers,\n defaultColor,\n cssVariablePrefix,\n addDefaultStyles = true,\n style,\n langStyle,\n className,\n langClassName,\n showLanguage = true,\n showLineNumbers = false,\n startingLineNumber = 1,\n children: code,\n as: Element = 'pre',\n customLanguages,\n ...shikiOptions\n },\n ref\n ) => {\n // Destructure some options for use in hook\n const options: HighlighterOptions = {\n delay,\n transformers,\n customLanguages,\n showLineNumbers,\n defaultColor,\n cssVariablePrefix,\n startingLineNumber,\n ...shikiOptions,\n };\n\n // Use resolveLanguage to get displayLanguageId directly\n const { displayLanguageId } = resolveLanguage(\n language,\n customLanguages\n );\n\n const highlightedCode = useShikiHighlighterImpl(\n code,\n language,\n theme,\n options\n );\n\n const isHtmlOutput = typeof highlightedCode === 'string';\n\n return (\n <Element\n ref={ref}\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 && displayLanguageId ? (\n <span\n className={clsx('languageLabel', langClassName)}\n style={langStyle}\n id=\"language-label\"\n >\n {displayLanguageId}\n </span>\n ) : null}\n {isHtmlOutput ? (\n <div dangerouslySetInnerHTML={{ __html: highlightedCode }} />\n ) : (\n highlightedCode\n )}\n </Element>\n );\n }\n );\n};\n"],"mappings":"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","names":["useRef","useRef","jsx","jsxs"]}
@@ -85,6 +85,13 @@ interface ReactShikiOptions {
85
85
  * Custom textmate grammars to be preloaded for highlighting.
86
86
  */
87
87
  customLanguages?: LanguageRegistration | LanguageRegistration[];
88
+ /**
89
+ * Output format for the highlighted code.
90
+ * - 'react': Returns React nodes (default, safer)
91
+ * - 'html': Returns HTML string (~15-45% faster, requires dangerouslySetInnerHTML)
92
+ * @default 'react'
93
+ */
94
+ outputFormat?: 'react' | 'html';
88
95
  /**
89
96
  * Custom Shiki highlighter instance to use instead of the default one.
90
97
  * Keeps bundle small by only importing specified languages/themes.
@@ -134,7 +141,7 @@ interface HighlighterOptions extends ReactShikiOptions, Pick<CodeOptionsMultiple
134
141
  * Public API signature for the useShikiHighlighter hook.
135
142
  * This ensures all entry points have consistent signatures.
136
143
  */
137
- type UseShikiHighlighter = (code: string, lang: Language, themeInput: Theme | Themes, options?: HighlighterOptions) => ReactNode;
144
+ type UseShikiHighlighter = (code: string, lang: Language, themeInput: Theme | Themes, options?: HighlighterOptions) => ReactNode | string | null;
138
145
 
139
146
  /**
140
147
  * Rehype plugin to add an 'inline' property to <code> elements
package/dist/core.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as react from 'react';
2
- import { S as ShikiHighlighterProps, U as UseShikiHighlighter } from './component-B4tYZaD8.js';
3
- export { E as Element, H as HighlighterOptions, L as Language, T as Theme, a as Themes, i as isInlineCode, r as rehypeInlineCodeProperty } from './component-B4tYZaD8.js';
2
+ import { S as ShikiHighlighterProps, U as UseShikiHighlighter } from './component-DwtCEX7b.js';
3
+ export { E as Element, H as HighlighterOptions, L as Language, T as Theme, a as Themes, i as isInlineCode, r as rehypeInlineCodeProperty } from './component-DwtCEX7b.js';
4
4
  export { createHighlighterCore } from 'shiki/core';
5
5
  export { createOnigurumaEngine } from 'shiki/engine/oniguruma';
6
6
  export { createJavaScriptRegexEngine } from 'shiki/engine/javascript';
package/dist/core.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  isInlineCode,
4
4
  rehypeInlineCodeProperty,
5
5
  useShikiHighlighter
6
- } from "./chunk-ZUXU23SW.js";
6
+ } from "./chunk-JAL4YDG6.js";
7
7
 
8
8
  // src/bundles/core.ts
9
9
  function validateCoreHighlighter(highlighter) {
package/dist/core.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/bundles/core.ts","../src/core.ts"],"sourcesContent":["import type { HighlighterCore } from 'shiki';\n\n/**\n * Validates that a highlighter is provided for the core bundle.\n * The core bundle requires users to provide their own highlighter instance.\n */\nexport function validateCoreHighlighter(\n highlighter: HighlighterCore | undefined\n): HighlighterCore {\n if (!highlighter) {\n throw new Error(\n 'react-shiki/core requires a custom highlighter. ' +\n 'Use createHighlighterCore() from react-shiki or switch to react-shiki for plug-and-play usage.'\n );\n }\n return highlighter;\n}\n","import { useShikiHighlighter as useBaseHook } from './lib/hook';\nimport { validateCoreHighlighter } from './bundles/core';\nimport type { UseShikiHighlighter } from './lib/types';\n\nexport { isInlineCode, rehypeInlineCodeProperty } from './lib/utils';\n\nimport {\n createShikiHighlighterComponent,\n type ShikiHighlighterProps,\n} from './lib/component';\n\nexport type { ShikiHighlighterProps };\n\nexport type {\n UseShikiHighlighter,\n Language,\n Theme,\n Themes,\n Element,\n HighlighterOptions,\n} from './lib/types';\n\nexport { createHighlighterCore } from 'shiki/core';\nexport { createOnigurumaEngine } from 'shiki/engine/oniguruma';\nexport { createJavaScriptRegexEngine } from 'shiki/engine/javascript';\n\n/**\n * A React hook that provides syntax highlighting using Shiki with a custom highlighter.\n * Requires a highlighter to be provided in options for minimal bundle size.\n *\n * @example\n * ```ts\n * import { createHighlighterCore, createOnigurumaEngine } from 'react-shiki/core';\n *\n * const highlighter = await createHighlighterCore({\n * themes: [import('@shikijs/themes/nord')],\n * langs: [import('@shikijs/langs/typescript')],\n * engine: createOnigurumaEngine(import('shiki/wasm'))\n * });\n *\n * const code = useShikiHighlighter(code, 'typescript', 'nord', { highlighter });\n * ```\n *\n * For plug-and-play usage, consider:\n * - `react-shiki` for full shiki bundle (~6.4MB minified, 1.2MB gzipped)\n * - `react-shiki/web` for smaller shiki web bundle (~3.8MB minified, 695KB gzipped)\n */\nexport const useShikiHighlighter: UseShikiHighlighter = (\n code,\n lang,\n themeInput,\n options = {}\n) => {\n // Validate that highlighter is provided\n const highlighter = validateCoreHighlighter(options.highlighter);\n\n return useBaseHook(code, lang, themeInput, async () => highlighter, {\n ...options,\n highlighter,\n });\n};\n\n/**\n * ShikiHighlighter component using a custom highlighter.\n * Requires a highlighter to be provided.\n */\nexport const ShikiHighlighter = createShikiHighlighterComponent(\n useShikiHighlighter\n);\nexport default ShikiHighlighter;\n"],"mappings":";;;;;;;;AAMO,SAAS,wBACd,aACiB;AACjB,MAAI,CAAC,aAAa;AAChB,UAAM,IAAI;AAAA,MACR;AAAA,IAEF;AAAA,EACF;AACA,SAAO;AACT;;;ACMA,SAAS,6BAA6B;AACtC,SAAS,6BAA6B;AACtC,SAAS,mCAAmC;AAuBrC,IAAMA,uBAA2C,CACtD,MACA,MACA,YACA,UAAU,CAAC,MACR;AAEH,QAAM,cAAc,wBAAwB,QAAQ,WAAW;AAE/D,SAAO,oBAAY,MAAM,MAAM,YAAY,YAAY,aAAa;AAAA,IAClE,GAAG;AAAA,IACH;AAAA,EACF,CAAC;AACH;AAMO,IAAM,mBAAmB;AAAA,EAC9BA;AACF;AACA,IAAO,eAAQ;","names":["useShikiHighlighter"]}
1
+ {"version":3,"sources":["../src/bundles/core.ts","../src/core.ts"],"sourcesContent":["import type { HighlighterCore } from 'shiki';\n\n/**\n * Validates that a highlighter is provided for the core bundle.\n * The core bundle requires users to provide their own highlighter instance.\n */\nexport function validateCoreHighlighter(\n highlighter: HighlighterCore | undefined\n): HighlighterCore {\n if (!highlighter) {\n throw new Error(\n 'react-shiki/core requires a custom highlighter. ' +\n 'Use createHighlighterCore() from react-shiki or switch to react-shiki for plug-and-play usage.'\n );\n }\n return highlighter;\n}\n","import { useShikiHighlighter as useBaseHook } from './lib/hook';\nimport { validateCoreHighlighter } from './bundles/core';\nimport type { UseShikiHighlighter } from './lib/types';\n\nexport { isInlineCode, rehypeInlineCodeProperty } from './lib/plugins';\n\nimport {\n createShikiHighlighterComponent,\n type ShikiHighlighterProps,\n} from './lib/component';\n\nexport type { ShikiHighlighterProps };\n\nexport type {\n UseShikiHighlighter,\n Language,\n Theme,\n Themes,\n Element,\n HighlighterOptions,\n} from './lib/types';\n\nexport { createHighlighterCore } from 'shiki/core';\nexport { createOnigurumaEngine } from 'shiki/engine/oniguruma';\nexport { createJavaScriptRegexEngine } from 'shiki/engine/javascript';\n\n/**\n * A React hook that provides syntax highlighting using Shiki with a custom highlighter.\n * Requires a highlighter to be provided in options for minimal bundle size.\n *\n * @example\n * ```ts\n * import { createHighlighterCore, createOnigurumaEngine } from 'react-shiki/core';\n *\n * const highlighter = await createHighlighterCore({\n * themes: [import('@shikijs/themes/nord')],\n * langs: [import('@shikijs/langs/typescript')],\n * engine: createOnigurumaEngine(import('shiki/wasm'))\n * });\n *\n * const code = useShikiHighlighter(code, 'typescript', 'nord', { highlighter });\n * ```\n *\n * For plug-and-play usage, consider:\n * - `react-shiki` for full shiki bundle (~6.4MB minified, 1.2MB gzipped)\n * - `react-shiki/web` for smaller shiki web bundle (~3.8MB minified, 695KB gzipped)\n */\nexport const useShikiHighlighter: UseShikiHighlighter = (\n code,\n lang,\n themeInput,\n options = {}\n) => {\n // Validate that highlighter is provided\n const highlighter = validateCoreHighlighter(options.highlighter);\n\n return useBaseHook(code, lang, themeInput, async () => highlighter, {\n ...options,\n highlighter,\n });\n};\n\n/**\n * ShikiHighlighter component using a custom highlighter.\n * Requires a highlighter to be provided.\n */\nexport const ShikiHighlighter = createShikiHighlighterComponent(\n useShikiHighlighter\n);\nexport default ShikiHighlighter;\n"],"mappings":";;;;;;;;AAMO,SAAS,wBACd,aACiB;AACjB,MAAI,CAAC,aAAa;AAChB,UAAM,IAAI;AAAA,MACR;AAAA,IAEF;AAAA,EACF;AACA,SAAO;AACT;;;ACMA,SAAS,6BAA6B;AACtC,SAAS,6BAA6B;AACtC,SAAS,mCAAmC;AAuBrC,IAAMA,uBAA2C,CACtD,MACA,MACA,YACA,UAAU,CAAC,MACR;AAEH,QAAM,cAAc,wBAAwB,QAAQ,WAAW;AAE/D,SAAO,oBAAY,MAAM,MAAM,YAAY,YAAY,aAAa;AAAA,IAClE,GAAG;AAAA,IACH;AAAA,EACF,CAAC;AACH;AAMO,IAAM,mBAAmB;AAAA,EAC9BA;AACF;AACA,IAAO,eAAQ;","names":["useShikiHighlighter"]}
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as react from 'react';
2
- import { S as ShikiHighlighterProps, U as UseShikiHighlighter } from './component-B4tYZaD8.js';
3
- export { E as Element, H as HighlighterOptions, L as Language, T as Theme, a as Themes, i as isInlineCode, r as rehypeInlineCodeProperty } from './component-B4tYZaD8.js';
2
+ import { S as ShikiHighlighterProps, U as UseShikiHighlighter } from './component-DwtCEX7b.js';
3
+ export { E as Element, H as HighlighterOptions, L as Language, T as Theme, a as Themes, i as isInlineCode, r as rehypeInlineCodeProperty } from './component-DwtCEX7b.js';
4
4
  import 'shiki';
5
5
  import 'shiki/core';
6
6
  import 'hast';
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  isInlineCode,
4
4
  rehypeInlineCodeProperty,
5
5
  useShikiHighlighter
6
- } from "./chunk-ZUXU23SW.js";
6
+ } from "./chunk-JAL4YDG6.js";
7
7
 
8
8
  // src/bundles/full.ts
9
9
  import { getSingletonHighlighter } from "shiki";
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/bundles/full.ts","../src/index.ts"],"sourcesContent":["import { getSingletonHighlighter, type Highlighter } from 'shiki';\nimport type { ShikiLanguageRegistration } from '../lib/extended-types';\nimport type { Theme } from '../lib/types';\n\n/**\n * Creates a highlighter using the full Shiki bundle with all languages and themes.\n * This is the largest bundle but provides maximum compatibility.\n */\nexport async function createFullHighlighter(\n langsToLoad: ShikiLanguageRegistration,\n themesToLoad: Theme[]\n): Promise<Highlighter> {\n try {\n return await getSingletonHighlighter({\n langs: [langsToLoad],\n themes: themesToLoad,\n });\n } catch (error) {\n if (error instanceof Error && error.message.includes('Language')) {\n return await getSingletonHighlighter({\n langs: ['plaintext'],\n themes: themesToLoad,\n });\n }\n throw error;\n }\n}\n","import { useShikiHighlighter as useBaseHook } from './lib/hook';\nimport { createFullHighlighter } from './bundles/full';\nimport type { UseShikiHighlighter } from './lib/types';\n\nexport { isInlineCode, rehypeInlineCodeProperty } from './lib/utils';\n\nimport {\n createShikiHighlighterComponent,\n type ShikiHighlighterProps,\n} from './lib/component';\n\nexport type { ShikiHighlighterProps };\n\nexport type {\n UseShikiHighlighter,\n Language,\n Theme,\n Themes,\n Element,\n HighlighterOptions,\n} from './lib/types';\n\n/**\n * A React hook that provides syntax highlighting using Shiki with the full bundle.\n * Includes all languages and themes for maximum compatibility.\n *\n * Bundle size: ~6.4MB minified (1.2MB gzipped)\n *\n * For smaller bundles, consider:\n * - `react-shiki/web` for smaller shiki web bundle (~3.8MB minified, 695KB gzipped)\n * - `react-shiki/core` for custom fine-grained bundle\n */\nexport const useShikiHighlighter: UseShikiHighlighter = (\n code,\n lang,\n themeInput,\n options = {}\n) => {\n return useBaseHook(\n code,\n lang,\n themeInput,\n createFullHighlighter,\n options\n );\n};\n\n/**\n * ShikiHighlighter component using the full bundle.\n * Includes all languages and themes for maximum compatibility.\n */\nexport const ShikiHighlighter = createShikiHighlighterComponent(\n useShikiHighlighter\n);\nexport default ShikiHighlighter;\n"],"mappings":";;;;;;;;AAAA,SAAS,+BAAiD;AAQ1D,eAAsB,sBACpB,aACA,cACsB;AACtB,MAAI;AACF,WAAO,MAAM,wBAAwB;AAAA,MACnC,OAAO,CAAC,WAAW;AAAA,MACnB,QAAQ;AAAA,IACV,CAAC;AAAA,EACH,SAAS,OAAO;AACd,QAAI,iBAAiB,SAAS,MAAM,QAAQ,SAAS,UAAU,GAAG;AAChE,aAAO,MAAM,wBAAwB;AAAA,QACnC,OAAO,CAAC,WAAW;AAAA,QACnB,QAAQ;AAAA,MACV,CAAC;AAAA,IACH;AACA,UAAM;AAAA,EACR;AACF;;;ACMO,IAAMA,uBAA2C,CACtD,MACA,MACA,YACA,UAAU,CAAC,MACR;AACH,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAMO,IAAM,mBAAmB;AAAA,EAC9BA;AACF;AACA,IAAO,cAAQ;","names":["useShikiHighlighter"]}
1
+ {"version":3,"sources":["../src/bundles/full.ts","../src/index.ts"],"sourcesContent":["import { getSingletonHighlighter, type Highlighter } from 'shiki';\nimport type { ShikiLanguageRegistration } from '../lib/extended-types';\nimport type { Theme } from '../lib/types';\n\n/**\n * Creates a highlighter using the full Shiki bundle with all languages and themes.\n * This is the largest bundle but provides maximum compatibility.\n */\nexport async function createFullHighlighter(\n langsToLoad: ShikiLanguageRegistration,\n themesToLoad: Theme[]\n): Promise<Highlighter> {\n try {\n return await getSingletonHighlighter({\n langs: [langsToLoad],\n themes: themesToLoad,\n });\n } catch (error) {\n if (error instanceof Error && error.message.includes('Language')) {\n return await getSingletonHighlighter({\n langs: ['plaintext'],\n themes: themesToLoad,\n });\n }\n throw error;\n }\n}\n","import { useShikiHighlighter as useBaseHook } from './lib/hook';\nimport { createFullHighlighter } from './bundles/full';\nimport type { UseShikiHighlighter } from './lib/types';\n\nexport { isInlineCode, rehypeInlineCodeProperty } from './lib/plugins';\n\nimport {\n createShikiHighlighterComponent,\n type ShikiHighlighterProps,\n} from './lib/component';\n\nexport type { ShikiHighlighterProps };\n\nexport type {\n UseShikiHighlighter,\n Language,\n Theme,\n Themes,\n Element,\n HighlighterOptions,\n} from './lib/types';\n\n/**\n * A React hook that provides syntax highlighting using Shiki with the full bundle.\n * Includes all languages and themes for maximum compatibility.\n *\n * Bundle size: ~6.4MB minified (1.2MB gzipped)\n *\n * For smaller bundles, consider:\n * - `react-shiki/web` for smaller shiki web bundle (~3.8MB minified, 695KB gzipped)\n * - `react-shiki/core` for custom fine-grained bundle\n */\nexport const useShikiHighlighter: UseShikiHighlighter = (\n code,\n lang,\n themeInput,\n options = {}\n) => {\n return useBaseHook(\n code,\n lang,\n themeInput,\n createFullHighlighter,\n options\n );\n};\n\n/**\n * ShikiHighlighter component using the full bundle.\n * Includes all languages and themes for maximum compatibility.\n */\nexport const ShikiHighlighter = createShikiHighlighterComponent(\n useShikiHighlighter\n);\nexport default ShikiHighlighter;\n"],"mappings":";;;;;;;;AAAA,SAAS,+BAAiD;AAQ1D,eAAsB,sBACpB,aACA,cACsB;AACtB,MAAI;AACF,WAAO,MAAM,wBAAwB;AAAA,MACnC,OAAO,CAAC,WAAW;AAAA,MACnB,QAAQ;AAAA,IACV,CAAC;AAAA,EACH,SAAS,OAAO;AACd,QAAI,iBAAiB,SAAS,MAAM,QAAQ,SAAS,UAAU,GAAG;AAChE,aAAO,MAAM,wBAAwB;AAAA,QACnC,OAAO,CAAC,WAAW;AAAA,QACnB,QAAQ;AAAA,MACV,CAAC;AAAA,IACH;AACA,UAAM;AAAA,EACR;AACF;;;ACMO,IAAMA,uBAA2C,CACtD,MACA,MACA,YACA,UAAU,CAAC,MACR;AACH,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAMO,IAAM,mBAAmB;AAAA,EAC9BA;AACF;AACA,IAAO,cAAQ;","names":["useShikiHighlighter"]}
package/dist/web.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as react from 'react';
2
- import { S as ShikiHighlighterProps, U as UseShikiHighlighter } from './component-B4tYZaD8.js';
3
- export { E as Element, H as HighlighterOptions, L as Language, T as Theme, a as Themes, i as isInlineCode, r as rehypeInlineCodeProperty } from './component-B4tYZaD8.js';
2
+ import { S as ShikiHighlighterProps, U as UseShikiHighlighter } from './component-DwtCEX7b.js';
3
+ export { E as Element, H as HighlighterOptions, L as Language, T as Theme, a as Themes, i as isInlineCode, r as rehypeInlineCodeProperty } from './component-DwtCEX7b.js';
4
4
  import 'shiki';
5
5
  import 'shiki/core';
6
6
  import 'hast';
package/dist/web.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  isInlineCode,
4
4
  rehypeInlineCodeProperty,
5
5
  useShikiHighlighter
6
- } from "./chunk-ZUXU23SW.js";
6
+ } from "./chunk-JAL4YDG6.js";
7
7
 
8
8
  // src/bundles/web.ts
9
9
  import {
package/dist/web.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/bundles/web.ts","../src/web.ts"],"sourcesContent":["import {\n getSingletonHighlighter,\n type Highlighter,\n} from 'shiki/bundle/web';\nimport type { ShikiLanguageRegistration } from '../lib/extended-types';\nimport type { Theme } from '../lib/types';\n\n/**\n * Creates a highlighter using the web Shiki bundle with web-focused languages.\n * Smaller than the full bundle while covering most web development needs.\n * Includes: HTML, CSS, JS, TS, JSON, Markdown, Vue, JSX, Svelte, etc.\n */\nexport async function createWebHighlighter(\n langsToLoad: ShikiLanguageRegistration,\n themesToLoad: Theme[]\n): Promise<Highlighter> {\n try {\n return await getSingletonHighlighter({\n langs: [langsToLoad],\n themes: themesToLoad,\n });\n } catch (error) {\n if (error instanceof Error && error.message.includes('Language')) {\n return await getSingletonHighlighter({\n langs: ['plaintext'],\n themes: themesToLoad,\n });\n }\n throw error;\n }\n}\n","import { useShikiHighlighter as useBaseHook } from './lib/hook';\nimport { createWebHighlighter } from './bundles/web';\nimport type { UseShikiHighlighter } from './lib/types';\n\nexport { isInlineCode, rehypeInlineCodeProperty } from './lib/utils';\n\nimport {\n createShikiHighlighterComponent,\n type ShikiHighlighterProps,\n} from './lib/component';\n\nexport type { ShikiHighlighterProps };\n\nexport type {\n UseShikiHighlighter,\n Language,\n Theme,\n Themes,\n Element,\n HighlighterOptions,\n} from './lib/types';\n\n/**\n * A React hook that provides syntax highlighting using Shiki with the web bundle.\n * Includes web-focused languages (HTML, CSS, JS, TS, JSON, Markdown, Astro, JSX, Svelte, Vue etc.)\n *\n * Bundle size: ~3.8MB minified (695KB gzipped)\n *\n * For other options, consider:\n * - `react-shiki` for full shiki bundle (~6.4MB minified, 1.2MB gzipped)\n * - `react-shiki/core` for custom fine-grained bundle\n */\nexport const useShikiHighlighter: UseShikiHighlighter = (\n code,\n lang,\n themeInput,\n options = {}\n) => {\n return useBaseHook(\n code,\n lang,\n themeInput,\n createWebHighlighter,\n options\n );\n};\n\n/**\n * ShikiHighlighter component using the web bundle.\n * Includes web-focused languages for balanced size and functionality.\n */\nexport const ShikiHighlighter = createShikiHighlighterComponent(\n useShikiHighlighter\n);\nexport default ShikiHighlighter;\n"],"mappings":";;;;;;;;AAAA;AAAA,EACE;AAAA,OAEK;AASP,eAAsB,qBACpB,aACA,cACsB;AACtB,MAAI;AACF,WAAO,MAAM,wBAAwB;AAAA,MACnC,OAAO,CAAC,WAAW;AAAA,MACnB,QAAQ;AAAA,IACV,CAAC;AAAA,EACH,SAAS,OAAO;AACd,QAAI,iBAAiB,SAAS,MAAM,QAAQ,SAAS,UAAU,GAAG;AAChE,aAAO,MAAM,wBAAwB;AAAA,QACnC,OAAO,CAAC,WAAW;AAAA,QACnB,QAAQ;AAAA,MACV,CAAC;AAAA,IACH;AACA,UAAM;AAAA,EACR;AACF;;;ACEO,IAAMA,uBAA2C,CACtD,MACA,MACA,YACA,UAAU,CAAC,MACR;AACH,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAMO,IAAM,mBAAmB;AAAA,EAC9BA;AACF;AACA,IAAO,cAAQ;","names":["useShikiHighlighter"]}
1
+ {"version":3,"sources":["../src/bundles/web.ts","../src/web.ts"],"sourcesContent":["import {\n getSingletonHighlighter,\n type Highlighter,\n} from 'shiki/bundle/web';\nimport type { ShikiLanguageRegistration } from '../lib/extended-types';\nimport type { Theme } from '../lib/types';\n\n/**\n * Creates a highlighter using the web Shiki bundle with web-focused languages.\n * Smaller than the full bundle while covering most web development needs.\n * Includes: HTML, CSS, JS, TS, JSON, Markdown, Vue, JSX, Svelte, etc.\n */\nexport async function createWebHighlighter(\n langsToLoad: ShikiLanguageRegistration,\n themesToLoad: Theme[]\n): Promise<Highlighter> {\n try {\n return await getSingletonHighlighter({\n langs: [langsToLoad],\n themes: themesToLoad,\n });\n } catch (error) {\n if (error instanceof Error && error.message.includes('Language')) {\n return await getSingletonHighlighter({\n langs: ['plaintext'],\n themes: themesToLoad,\n });\n }\n throw error;\n }\n}\n","import { useShikiHighlighter as useBaseHook } from './lib/hook';\nimport { createWebHighlighter } from './bundles/web';\nimport type { UseShikiHighlighter } from './lib/types';\n\nexport { isInlineCode, rehypeInlineCodeProperty } from './lib/plugins';\n\nimport {\n createShikiHighlighterComponent,\n type ShikiHighlighterProps,\n} from './lib/component';\n\nexport type { ShikiHighlighterProps };\n\nexport type {\n UseShikiHighlighter,\n Language,\n Theme,\n Themes,\n Element,\n HighlighterOptions,\n} from './lib/types';\n\n/**\n * A React hook that provides syntax highlighting using Shiki with the web bundle.\n * Includes web-focused languages (HTML, CSS, JS, TS, JSON, Markdown, Astro, JSX, Svelte, Vue etc.)\n *\n * Bundle size: ~3.8MB minified (695KB gzipped)\n *\n * For other options, consider:\n * - `react-shiki` for full shiki bundle (~6.4MB minified, 1.2MB gzipped)\n * - `react-shiki/core` for custom fine-grained bundle\n */\nexport const useShikiHighlighter: UseShikiHighlighter = (\n code,\n lang,\n themeInput,\n options = {}\n) => {\n return useBaseHook(\n code,\n lang,\n themeInput,\n createWebHighlighter,\n options\n );\n};\n\n/**\n * ShikiHighlighter component using the web bundle.\n * Includes web-focused languages for balanced size and functionality.\n */\nexport const ShikiHighlighter = createShikiHighlighterComponent(\n useShikiHighlighter\n);\nexport default ShikiHighlighter;\n"],"mappings":";;;;;;;;AAAA;AAAA,EACE;AAAA,OAEK;AASP,eAAsB,qBACpB,aACA,cACsB;AACtB,MAAI;AACF,WAAO,MAAM,wBAAwB;AAAA,MACnC,OAAO,CAAC,WAAW;AAAA,MACnB,QAAQ;AAAA,IACV,CAAC;AAAA,EACH,SAAS,OAAO;AACd,QAAI,iBAAiB,SAAS,MAAM,QAAQ,SAAS,UAAU,GAAG;AAChE,aAAO,MAAM,wBAAwB;AAAA,QACnC,OAAO,CAAC,WAAW;AAAA,QACnB,QAAQ;AAAA,MACV,CAAC;AAAA,IACH;AACA,UAAM;AAAA,EACR;AACF;;;ACEO,IAAMA,uBAA2C,CACtD,MACA,MACA,YACA,UAAU,CAAC,MACR;AACH,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAMO,IAAM,mBAAmB;AAAA,EAC9BA;AACF;AACA,IAAO,cAAQ;","names":["useShikiHighlighter"]}
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.7.4",
4
+ "version": "0.8.0",
5
5
  "license": "MIT",
6
6
  "author": {
7
7
  "name": "Bassim Shahidy",
@@ -77,7 +77,6 @@
77
77
  "@types/node": "22.17.2",
78
78
  "@types/react": "^19.1.10",
79
79
  "@vitejs/plugin-react": "^4.7.0",
80
- "benny": "^3.7.1",
81
80
  "html-react-parser": "^5.2.6",
82
81
  "jsdom": "^26.1.0",
83
82
  "tsup": "^8.5.0",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/lib/utils.ts","../src/lib/hook.ts","../src/lib/resolvers.ts","../src/lib/transformers.ts","#style-inject:#style-inject","../src/lib/styles.css","../src/lib/component.tsx"],"sourcesContent":["import { visit } from 'unist-util-visit';\n\nimport type { TimeoutState, Element } from './types';\n\n/**\n * Rehype plugin to add an 'inline' property to <code> elements\n * Sets 'inline' property to true if the <code> is not within a <pre> tag\n *\n * Pass this plugin to the `rehypePlugins` prop of react-markdown\n * You can then access `inline` as a prop in components passed to react-markdown\n *\n * @example\n * <ReactMarkdown rehypePlugins={[rehypeInlineCodeProperty]} />\n */\nexport function rehypeInlineCodeProperty() {\n return (tree: any): undefined => {\n visit(tree, 'element', (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 * Function to determine if code is inline based on the presence of line breaks\n *\n * @example\n * const isInline = node && isInlineCode(node: Element)\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 * 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.RefObject<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","import {\n useEffect,\n useMemo,\n useRef,\n useState,\n type ReactNode,\n} from 'react';\n\nimport { dequal } from 'dequal/lite';\n\nimport { jsx, jsxs, Fragment } from 'react/jsx-runtime';\nimport { toJsxRuntime } from 'hast-util-to-jsx-runtime';\n\nimport type {\n CodeToHastOptions,\n CodeOptionsSingleTheme,\n CodeOptionsMultipleThemes,\n Highlighter,\n HighlighterCore,\n} from 'shiki';\n\nimport type { ShikiLanguageRegistration } from './extended-types';\n\nimport type {\n Language,\n Theme,\n HighlighterOptions,\n TimeoutState,\n Themes,\n} from './types';\n\nimport { throttleHighlighting } from './utils';\nimport { resolveLanguage, resolveTheme } from './resolvers';\nimport { lineNumbersTransformer } from './transformers';\n\nconst DEFAULT_THEMES: Themes = {\n light: 'github-light',\n dark: 'github-dark',\n};\n\n/**\n * Returns a deep-stable reference and a version counter that only changes when content changes.\n * Includes optimizations for primitive values and reference equality.\n */\nconst useStableOptions = <T>(value: T) => {\n const ref = useRef(value);\n const revision = useRef(0);\n\n // Fast-path for primitive values\n if (typeof value !== 'object' || value === null) {\n if (value !== ref.current) {\n ref.current = value;\n revision.current += 1;\n }\n return [value, revision.current] as const;\n }\n\n // Reference equality check before expensive deep comparison\n if (value !== ref.current) {\n if (!dequal(value, ref.current)) {\n ref.current = value;\n revision.current += 1;\n }\n }\n\n return [ref.current, revision.current] as const;\n};\n\n/**\n * Base hook for syntax highlighting using Shiki.\n * This is the core implementation used by all entry points.\n *\n * @param code - The code to highlight\n * @param lang - Language for highlighting\n * @param themeInput - Theme or themes to use\n * @param options - Highlighting options\n * @param createHighlighter - Factory function to create highlighter (internal use)\n */\nexport const useShikiHighlighter = (\n code: string,\n lang: Language,\n themeInput: Theme | Themes,\n createHighlighter: (\n langsToLoad: ShikiLanguageRegistration,\n themesToLoad: Theme[]\n ) => Promise<Highlighter | HighlighterCore>,\n options: HighlighterOptions = {}\n) => {\n const [highlightedCode, setHighlightedCode] =\n useState<ReactNode | null>(null);\n\n // Stabilize options, language and theme inputs to prevent unnecessary\n // re-renders or recalculations when object references change\n const [stableLang, langRev] = useStableOptions(lang);\n const [stableTheme, themeRev] = useStableOptions(themeInput);\n const [stableOpts, optsRev] = useStableOptions(options);\n\n const { languageId, langsToLoad } = useMemo(\n () =>\n resolveLanguage(\n stableLang,\n stableOpts.customLanguages,\n stableOpts.langAlias\n ),\n [stableLang, stableOpts.customLanguages, stableOpts.langAlias]\n );\n\n const { isMultiTheme, themeId, multiTheme, singleTheme, themesToLoad } =\n useMemo(() => resolveTheme(stableTheme), [stableTheme]);\n\n const timeoutControl = useRef<TimeoutState>({\n nextAllowedTime: 0,\n timeoutId: undefined,\n });\n\n const shikiOptions = useMemo<CodeToHastOptions>(() => {\n const languageOption = { lang: languageId };\n const {\n defaultColor,\n cssVariablePrefix,\n showLineNumbers,\n startingLineNumber,\n ...restOptions\n } = stableOpts;\n\n const themeOptions = isMultiTheme\n ? ({\n themes: multiTheme || DEFAULT_THEMES,\n defaultColor,\n cssVariablePrefix,\n } as CodeOptionsMultipleThemes)\n : ({\n theme: singleTheme || DEFAULT_THEMES.dark,\n } as CodeOptionsSingleTheme);\n\n const transformers = restOptions.transformers || [];\n if (showLineNumbers) {\n transformers.push(lineNumbersTransformer(startingLineNumber));\n }\n\n return {\n ...languageOption,\n ...themeOptions,\n ...restOptions,\n transformers,\n };\n }, [languageId, themeId, langRev, themeRev, optsRev]);\n\n useEffect(() => {\n let isMounted = true;\n\n const highlightCode = async () => {\n if (!languageId) return;\n\n const highlighter = stableOpts.highlighter\n ? stableOpts.highlighter\n : await createHighlighter(\n langsToLoad as ShikiLanguageRegistration,\n themesToLoad\n );\n\n const loadedLanguages = highlighter.getLoadedLanguages();\n const langToUse = loadedLanguages.includes(languageId)\n ? languageId\n : 'plaintext';\n const finalOptions = { ...shikiOptions, lang: langToUse };\n\n const hast = highlighter.codeToHast(code, finalOptions);\n\n if (isMounted) {\n setHighlightedCode(toJsxRuntime(hast, { jsx, jsxs, Fragment }));\n }\n };\n\n const { delay } = stableOpts;\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 }, [\n code,\n shikiOptions,\n stableOpts.delay,\n stableOpts.highlighter,\n langsToLoad,\n themesToLoad,\n ]);\n\n return highlightedCode;\n};\n","import type { Language, Theme, Themes } from './types';\nimport type { ThemeRegistrationAny } from 'shiki/core';\nimport type { LanguageRegistration } from './extended-types';\n\n/**\n * Resolved languages and metadata\n */\ntype LanguageResult = {\n languageId: string;\n displayLanguageId: string | null;\n langsToLoad: Language;\n};\n\n/**\n * Resolves the language input to standardized IDs and objects for Shiki and UI display\n * @param lang The language input from props\n * @param customLanguages An array of custom textmate grammar objects or a single grammar object\n * @returns A LanguageResult object containing:\n * - languageId: The resolved language ID\n * - displayLanguageId: The display language ID\n * - langToLoad: The language object or string id to load\n */\nexport const resolveLanguage = (\n lang: Language,\n customLanguages?: LanguageRegistration | LanguageRegistration[],\n langAliases?: Record<string, string>\n): LanguageResult => {\n const normalizedCustomLangs = customLanguages\n ? Array.isArray(customLanguages)\n ? customLanguages\n : [customLanguages]\n : [];\n\n // Language is null or empty string\n if (lang == null || (typeof lang === 'string' && !lang.trim())) {\n return {\n languageId: 'plaintext',\n displayLanguageId: 'plaintext',\n langsToLoad: undefined,\n };\n }\n\n // Language is custom\n if (typeof lang === 'object') {\n return {\n languageId: lang.name,\n displayLanguageId: lang.name || null,\n langsToLoad: lang,\n };\n }\n\n // Language is string\n const lowerLang = lang.toLowerCase();\n const matches = (str: string | undefined): boolean =>\n str?.toLowerCase() === lowerLang;\n\n // Check if the string identifies a provided custom language\n const customMatch = normalizedCustomLangs.find(\n (cl) =>\n matches(cl.name) ||\n matches(cl.scopeName) ||\n matches(cl.scopeName?.split('.').pop()) ||\n cl.aliases?.some(matches) ||\n cl.fileTypes?.some(matches)\n );\n\n if (customMatch) {\n return {\n languageId: customMatch.name || lang,\n displayLanguageId: lang,\n langsToLoad: customMatch,\n };\n }\n\n // Check if language is aliased\n if (langAliases?.[lang]) {\n return {\n languageId: langAliases[lang],\n displayLanguageId: lang,\n langsToLoad: langAliases[lang],\n };\n }\n\n // For any other string, pass it through,\n // fallback is handled in highlighter factories\n return {\n languageId: lang,\n displayLanguageId: lang,\n langsToLoad: lang,\n };\n};\n\n/**\n * Resolved themes and metadata\n */\ninterface ThemeResult {\n isMultiTheme: boolean;\n themeId: Theme;\n multiTheme?: Themes | ThemeRegistrationAny | null;\n singleTheme?: Theme | undefined;\n themesToLoad: Theme[];\n}\n\n/**\n * Determines theme configuration and returns the resolved theme with metadata\n * @param themeInput - The theme input, either as a string name or theme object\n * @returns Object containing:\n * - isMultiTheme: If theme input is a multi-theme configuration\n * - themeId: Theme reference identifier\n * - multiTheme: The multi-theme config if it exists\n * - singleTheme: The single theme if it exists\n * - themesToLoad: The themes to load when creating the highlighter\n */\nexport function resolveTheme(themeInput: Theme | Themes): ThemeResult {\n const isTextmateTheme =\n typeof themeInput === 'object' &&\n 'tokenColors' in themeInput &&\n Array.isArray(themeInput.tokenColors);\n\n // Assume non textmate objects are multi theme configs\n const isMultiThemeConfig =\n typeof themeInput === 'object' &&\n themeInput !== null &&\n !isTextmateTheme;\n\n const validMultiThemeObj =\n typeof themeInput === 'object' &&\n themeInput !== null &&\n !isTextmateTheme &&\n Object.entries(themeInput).some(\n ([key, value]) =>\n key &&\n value &&\n key.trim() !== '' &&\n value !== '' &&\n (typeof value === 'string' || isTextmateTheme)\n );\n\n if (isMultiThemeConfig) {\n const themeId = validMultiThemeObj\n ? `multi-${Object.values(themeInput)\n .map(\n (theme) =>\n (typeof theme === 'string' ? theme : theme?.name) ||\n 'custom'\n )\n .sort()\n .join('-')}`\n : 'multi-default';\n\n // If config is invalid, return null to handle fallback in `buildShikiOptions()`\n return {\n isMultiTheme: true,\n themeId,\n multiTheme: validMultiThemeObj ? themeInput : null,\n themesToLoad: validMultiThemeObj ? Object.values(themeInput) : [],\n };\n }\n\n return {\n isMultiTheme: false,\n themeId:\n typeof themeInput === 'string'\n ? themeInput\n : themeInput?.name || 'custom',\n singleTheme: themeInput,\n themesToLoad: [themeInput],\n };\n}\n","import type { ShikiTransformer } from 'shiki/core';\n\n/**\n * Creates a transformer that enables line numbers display\n * @param startLine - The starting line number (defaults to 1)\n */\nexport function lineNumbersTransformer(startLine = 1): ShikiTransformer {\n return {\n name: 'react-shiki:line-numbers',\n code(node) {\n this.addClassToHast(node, 'has-line-numbers');\n if (startLine !== 1) {\n const existingStyle = (node.properties?.style as string) || '';\n const newStyle = existingStyle\n ? `${existingStyle}; --line-start: ${startLine}`\n : `--line-start: ${startLine}`;\n node.properties = {\n ...node.properties,\n style: newStyle,\n };\n }\n },\n line(node) {\n this.addClassToHast(node, 'line-numbers');\n return node;\n },\n };\n}\n","\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 {\\n position: relative;\\n}\\n.defaultStyles pre {\\n overflow: auto;\\n border-radius: 0.5rem;\\n padding-left: 1.5rem;\\n padding-right: 1.5rem;\\n padding-top: 1.25rem;\\n padding-bottom: 1.25rem;\\n}\\n.languageLabel {\\n position: absolute;\\n right: 0.75rem;\\n top: 0.5rem;\\n font-family: monospace;\\n font-size: 0.75rem;\\n letter-spacing: -0.05em;\\n color: rgba(107, 114, 128, 0.85);\\n}\\n.line-numbers::before {\\n counter-increment: line-number;\\n content: counter(line-number);\\n display: inline-flex;\\n justify-content: flex-end;\\n align-items: flex-start;\\n box-sizing: content-box;\\n min-width: var(--line-numbers-width, 2ch);\\n padding-left: var(--line-numbers-padding-left, 2ch);\\n padding-right: var(--line-numbers-padding-right, 2ch);\\n color: var(--line-numbers-foreground, rgba(107, 114, 128, 0.6));\\n font-size: var(--line-numbers-font-size, inherit);\\n font-weight: var(--line-numbers-font-weight, inherit);\\n line-height: var(--line-numbers-line-height, inherit);\\n font-family: var(--line-numbers-font-family, inherit);\\n opacity: var(--line-numbers-opacity, 1);\\n user-select: none;\\n pointer-events: none;\\n}\\n.has-line-numbers {\\n counter-reset: line-number calc(var(--line-start, 1) - 1);\\n --line-numbers-foreground: rgba(107, 114, 128, 0.5);\\n --line-numbers-width: 2ch;\\n --line-numbers-padding-left: 0ch;\\n --line-numbers-padding-right: 2ch;\\n --line-numbers-font-size: inherit;\\n --line-numbers-font-weight: inherit;\\n --line-numbers-line-height: inherit;\\n --line-numbers-font-family: inherit;\\n --line-numbers-opacity: 1;\\n}\\n\")","import './styles.css';\nimport { clsx } from 'clsx';\nimport { resolveLanguage } from './resolvers';\n\nimport type {\n HighlighterOptions,\n Language,\n Theme,\n Themes,\n} from './types';\nimport { forwardRef } from 'react';\n\n/**\n * Props for the ShikiHighlighter component\n */\nexport interface ShikiHighlighterProps extends HighlighterOptions {\n /**\n * The programming language for syntax highlighting\n * Supports custom textmate grammar objects in addition to Shiki's bundled languages\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 or themes for syntax highlighting\n * Supports single, dual, or multiple themes\n * Supports custom textmate theme objects in addition to Shiki's bundled themes\n *\n * @example\n * theme='github-dark' // single theme\n * theme={{ light: 'github-light', dark: 'github-dark' }} // multi-theme\n *\n * @see https://shiki.style/themes\n */\n theme: Theme | Themes;\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 * Whether to show line numbers\n * @default false\n */\n showLineNumbers?: boolean;\n\n /**\n * Starting line number (when showLineNumbers is true)\n * @default 1\n */\n startingLineNumber?: number;\n\n /**\n * The HTML element that wraps the generated code block.\n * @default 'pre'\n */\n as?: React.ElementType;\n}\n\n/**\n * Base ShikiHighlighter component factory.\n * This creates a component that uses the provided hook implementation.\n */\nexport const createShikiHighlighterComponent = (\n useShikiHighlighterImpl: (\n code: string,\n lang: Language,\n themeInput: Theme | Themes,\n options?: HighlighterOptions\n ) => React.ReactNode\n) => {\n return forwardRef<HTMLElement, ShikiHighlighterProps>(\n (\n {\n language,\n theme,\n delay,\n transformers,\n defaultColor,\n cssVariablePrefix,\n addDefaultStyles = true,\n style,\n langStyle,\n className,\n langClassName,\n showLanguage = true,\n showLineNumbers = false,\n startingLineNumber = 1,\n children: code,\n as: Element = 'pre',\n customLanguages,\n ...shikiOptions\n },\n ref\n ) => {\n // Destructure some options for use in hook\n const options: HighlighterOptions = {\n delay,\n transformers,\n customLanguages,\n showLineNumbers,\n defaultColor,\n cssVariablePrefix,\n startingLineNumber,\n ...shikiOptions,\n };\n\n // Use resolveLanguage to get displayLanguageId directly\n const { displayLanguageId } = resolveLanguage(\n language,\n customLanguages\n );\n\n const highlightedCode = useShikiHighlighterImpl(\n code,\n language,\n theme,\n options\n );\n\n return (\n <Element\n ref={ref}\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 && displayLanguageId ? (\n <span\n className={clsx('languageLabel', langClassName)}\n style={langStyle}\n id=\"language-label\"\n >\n {displayLanguageId}\n </span>\n ) : null}\n {highlightedCode}\n </Element>\n );\n }\n );\n};\n"],"mappings":"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","names":["jsx","jsxs"]}