react-shiki 0.11.2 → 0.11.3

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
@@ -306,6 +306,7 @@ The core inputs, positional arguments on the hook, props/children on the compone
306
306
  | Option | Type | Default | Description |
307
307
  | ------------------- | ------------------ | --------------- | ----------------------------------------------------------------------------- |
308
308
  | `delay` | `number` | - | Minimum time between highlights (in milliseconds), no throttling by default |
309
+ | `cache` | `Map` | - | Keeps highlighted output across unmounts, see [Caching Across Remounts](#caching-across-remounts) |
309
310
  | `engine` | `'javascript' \| 'oniguruma' \| RegexEngine` | `'oniguruma'` | RegExp engine for syntax highlighting; named engines are created and cached internally |
310
311
  | `highlighter` | `Highlighter \| HighlighterCore` | - | Custom highlighter instance, required for the core bundle |
311
312
  | `outputFormat` | `string` | `'react'` | Output format: 'react' for React nodes, 'html' for HTML string, or 'tokens' for Shiki tokens (hook only, experimental) |
@@ -798,6 +799,22 @@ const highlightedCode = useShikiHighlighter(code, "tsx", "github-dark", {
798
799
  });
799
800
  ```
800
801
 
802
+ ### Caching Across Remounts
803
+
804
+ Lists that remount their content, like chat transcripts when switching threads or virtualized lists while scrolling, highlight every code block again by default. Pass a `Map` as `cache` and unchanged blocks paint on their first render instead:
805
+
806
+ ```tsx
807
+ const cache = new Map();
808
+
809
+ <ShikiHighlighter language="tsx" theme="github-dark" cache={cache}>
810
+ {code}
811
+ </ShikiHighlighter>
812
+ ```
813
+
814
+ - A block's output is stored when it unmounts, so the partial code of a streaming block is never cached.
815
+ - Entries are keyed by code, language, theme and options, so blocks with different settings can share one cache. Transformers are told apart by `name`, so give blocks with different transformers of the same name their own cache.
816
+ - The cache is yours: keep one per app or per conversation and `clear()` it when that content goes away.
817
+
801
818
  ---
802
819
 
803
820
  Made with ❤️ by [Bassim (AVGVSTVS96)](https://github.com/AVGVSTVS96)
package/dist/core.d.mts CHANGED
@@ -1,8 +1,7 @@
1
- import { a as EngineName, c as HighlighterOptionsFor, d as PreloadLanguage, f as Theme, i as Element, l as Language, m as UseShikiHighlighter, n as isInlineCode, o as HighlightResult, p as Themes, r as rehypeInlineCodeProperty, s as HighlighterOptions, t as ShikiHighlighterProps, u as OutputFormat } from "./component-DRPT3D6t.mjs";
1
+ import { a as EngineName, c as HighlighterOptions, d as OutputFormat, f as PreloadLanguage, h as UseShikiHighlighter, i as Element, l as HighlighterOptionsFor, m as Themes, n as rehypeInlineCodeProperty, o as HighlightCache, p as Theme, r as ShikiHighlighterProps, s as HighlightResult, t as isInlineCode, u as Language } from "./plugins-DdjjMnbY.mjs";
2
2
  import { LanguageRegistration, TokensResult, createHighlighterCore } from "shiki/core";
3
3
  import { createOnigurumaEngine } from "shiki/engine/oniguruma";
4
4
  import { createJavaScriptRawEngine, createJavaScriptRegexEngine } from "shiki/engine/javascript";
5
- //#region src/core.d.ts
6
5
  /**
7
6
  * Highlight code with shiki (core bundle)
8
7
  *
@@ -42,6 +41,4 @@ export declare const useShikiHighlighter: UseShikiHighlighter;
42
41
  * Requires a highlighter to be provided.
43
42
  */
44
43
  export declare const ShikiHighlighter: import("react").ForwardRefExoticComponent<ShikiHighlighterProps & import("react").RefAttributes<HTMLElement>>;
45
- //#endregion
46
- export { type Element, type EngineName, type HighlightResult, type HighlighterOptions, type HighlighterOptionsFor, type Language, type LanguageRegistration, type OutputFormat, type PreloadLanguage, ShikiHighlighter as default, type ShikiHighlighterProps, type Theme, type Themes, type TokensResult, type UseShikiHighlighter, createHighlighterCore, createJavaScriptRawEngine, createJavaScriptRegexEngine, createOnigurumaEngine, isInlineCode, rehypeInlineCodeProperty };
47
- //# sourceMappingURL=core.d.mts.map
44
+ export { type Element, type EngineName, type HighlightCache, type HighlightResult, type HighlighterOptions, type HighlighterOptionsFor, type Language, type LanguageRegistration, type OutputFormat, type PreloadLanguage, ShikiHighlighter as default, type ShikiHighlighterProps, type Theme, type Themes, type TokensResult, type UseShikiHighlighter, createHighlighterCore, createJavaScriptRawEngine, createJavaScriptRegexEngine, createOnigurumaEngine, isInlineCode, rehypeInlineCodeProperty };
package/dist/core.mjs CHANGED
@@ -1,8 +1,8 @@
1
- import { i as useHighlight, n as isInlineCode, r as rehypeInlineCodeProperty, t as createShikiHighlighterComponent } from "./component-Cf8Topxy.mjs";
1
+ import { i as useHighlight, n as rehypeInlineCodeProperty, r as createShikiHighlighterComponent, t as isInlineCode } from "./plugins-C0Uf67_P.mjs";
2
2
  import { createHighlighterCore } from "shiki/core";
3
3
  import { createOnigurumaEngine } from "shiki/engine/oniguruma";
4
4
  import { createJavaScriptRawEngine, createJavaScriptRegexEngine } from "shiki/engine/javascript";
5
- //#region src/bundles/core.ts
5
+ // src/bundles/core.ts
6
6
  /**
7
7
  * Validates that a highlighter is provided for the core bundle.
8
8
  * The core bundle requires users to provide their own highlighter instance.
@@ -11,8 +11,7 @@ function validateCoreHighlighter(highlighter) {
11
11
  if (!highlighter) throw new Error("react-shiki/core requires a custom highlighter. Use createHighlighterCore() from react-shiki or switch to react-shiki for plug-and-play usage.");
12
12
  return highlighter;
13
13
  }
14
- //#endregion
15
- //#region src/core.ts
14
+ // src/core.ts
16
15
  /**
17
16
  * Highlight code with shiki (core bundle)
18
17
  *
@@ -58,7 +57,6 @@ const useShikiHighlighter = (code, lang, themeInput, options = {}) => {
58
57
  * Requires a highlighter to be provided.
59
58
  */
60
59
  const ShikiHighlighter = /*#__PURE__*/ createShikiHighlighterComponent(useShikiHighlighter);
61
- //#endregion
62
60
  export { ShikiHighlighter, ShikiHighlighter as default, createHighlighterCore, createJavaScriptRawEngine, createJavaScriptRegexEngine, createOnigurumaEngine, isInlineCode, rehypeInlineCodeProperty, useShikiHighlighter };
63
61
 
64
62
  //# sourceMappingURL=core.mjs.map
package/dist/core.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"core.mjs","names":[],"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 { useHighlight } 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 PreloadLanguage,\n Theme,\n Themes,\n Element,\n EngineName,\n HighlighterOptions,\n HighlighterOptionsFor,\n HighlightResult,\n OutputFormat,\n} from './lib/types';\n\nexport { createHighlighterCore } from 'shiki/core';\nexport { createOnigurumaEngine } from 'shiki/engine/oniguruma';\nexport {\n createJavaScriptRegexEngine,\n createJavaScriptRawEngine,\n} from 'shiki/engine/javascript';\n\nexport type { LanguageRegistration, TokensResult } from 'shiki/core';\n\n/**\n * Highlight code with shiki (core bundle)\n *\n * @param code - Code to highlight\n * @param lang - Language (bundled or custom)\n * @param theme - Theme (bundled, multi-theme, or custom)\n * @param options - react-shiki options + shiki options\n * @returns Highlighted code as React elements (default), HTML string, or Shiki tokens, based on `outputFormat`\n *\n * @example\n * ```tsx\n * import { createHighlighterCore, createOnigurumaEngine } from 'react-shiki/core';\n *\n *\n * const highlighter = await createHighlighterCore({\n * themes: [import('@shikijs/themes/github-light'), import('@shikijs/themes/github-dark')],\n * langs: [import('@shikijs/langs/typescript')],\n * engine: createOnigurumaEngine(import('shiki/wasm'))\n * });\n *\n * const highlighted = useShikiHighlighter(\n * 'const x = 1;',\n * 'typescript',\n * {\n * light: 'github-light',\n * dark: 'github-dark'\n * },\n * { highlighter }\n * );\n * ```\n *\n * Core bundle (minimal). For plug-and-play: `react-shiki` or `react-shiki/web`\n */\nexport const useShikiHighlighter: UseShikiHighlighter = (\n code,\n lang,\n themeInput,\n options = {}\n) => {\n const highlighter = validateCoreHighlighter(options.highlighter);\n\n return useHighlight(\n code,\n lang,\n themeInput,\n {\n ...options,\n highlighter,\n },\n async () => highlighter\n );\n};\n\n/**\n * ShikiHighlighter component using a custom highlighter.\n * Requires a highlighter to be provided.\n */\nexport const ShikiHighlighter =\n /*#__PURE__*/ createShikiHighlighterComponent(useShikiHighlighter);\nexport default ShikiHighlighter;\n"],"mappings":";;;;;;;;;AAMA,SAAgB,wBACd,aACiB;CACjB,IAAI,CAAC,aACH,MAAM,IAAI,MACR,gJAEF;CAEF,OAAO;AACT;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACqDA,MAAa,uBACX,MACA,MACA,YACA,UAAU,CAAC,MACR;CACH,MAAM,cAAc,wBAAwB,QAAQ,WAAW;CAE/D,OAAO,aACL,MACA,MACA,YACA;EACE,GAAG;EACH;CACF,GACA,YAAY,WACd;AACF;;;;;AAMA,MAAa,mBACG,8CAAgC,mBAAmB"}
1
+ {"version":3,"file":"core.mjs","names":[],"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\n return highlighter;\n}\n","import { useHighlight } from './lib/hook';\nimport { validateCoreHighlighter } from './bundles/core';\nimport type { UseShikiHighlighter } from './lib/types';\nimport { createShikiHighlighterComponent } from './lib/component';\n// oxlint-disable-next-line unicorn/prefer-export-from -- `export type … from` here makes tsdown fold the core entry's declarations into the shared .d.mts chunk\nimport type { ShikiHighlighterProps } from './lib/component';\n\nexport { isInlineCode, rehypeInlineCodeProperty } from './lib/plugins';\n\nexport type { ShikiHighlighterProps };\n\nexport type {\n UseShikiHighlighter,\n Language,\n PreloadLanguage,\n Theme,\n Themes,\n Element,\n EngineName,\n HighlighterOptions,\n HighlighterOptionsFor,\n HighlightCache,\n HighlightResult,\n OutputFormat,\n} from './lib/types';\n\nexport { createHighlighterCore } from 'shiki/core';\n\nexport { createOnigurumaEngine } from 'shiki/engine/oniguruma';\n\nexport {\n createJavaScriptRegexEngine,\n createJavaScriptRawEngine,\n} from 'shiki/engine/javascript';\n\nexport type { LanguageRegistration, TokensResult } from 'shiki/core';\n\n/**\n * Highlight code with shiki (core bundle)\n *\n * @param code - Code to highlight\n * @param lang - Language (bundled or custom)\n * @param theme - Theme (bundled, multi-theme, or custom)\n * @param options - react-shiki options + shiki options\n * @returns Highlighted code as React elements (default), HTML string, or Shiki tokens, based on `outputFormat`\n *\n * @example\n * ```tsx\n * import { createHighlighterCore, createOnigurumaEngine } from 'react-shiki/core';\n *\n *\n * const highlighter = await createHighlighterCore({\n * themes: [import('@shikijs/themes/github-light'), import('@shikijs/themes/github-dark')],\n * langs: [import('@shikijs/langs/typescript')],\n * engine: createOnigurumaEngine(import('shiki/wasm'))\n * });\n *\n * const highlighted = useShikiHighlighter(\n * 'const x = 1;',\n * 'typescript',\n * {\n * light: 'github-light',\n * dark: 'github-dark'\n * },\n * { highlighter }\n * );\n * ```\n *\n * Core bundle (minimal). For plug-and-play: `react-shiki` or `react-shiki/web`\n */\nexport const useShikiHighlighter: UseShikiHighlighter = (\n code,\n lang,\n themeInput,\n options = {}\n) => {\n const highlighter = validateCoreHighlighter(options.highlighter);\n\n return useHighlight(\n code,\n lang,\n themeInput,\n {\n ...options,\n highlighter,\n },\n async () => highlighter\n );\n};\n\n/**\n * ShikiHighlighter component using a custom highlighter.\n * Requires a highlighter to be provided.\n */\nexport const ShikiHighlighter =\n // oxlint-disable-next-line eslint/no-inline-comments -- the #__PURE__ annotation lets bundlers drop the component when only the hook is used\n /*#__PURE__*/ createShikiHighlighterComponent(useShikiHighlighter);\n\nexport default ShikiHighlighter;\n"],"mappings":";;;;;;;;;AAMA,SAAgB,wBACd,aACiB;CACjB,IAAI,CAAC,aACH,MAAM,IAAI,MACR,gJAEF;CAGF,OAAO;AACT;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACqDA,MAAa,uBACX,MACA,MACA,YACA,UAAU,CAAC,MACR;CACH,MAAM,cAAc,wBAAwB,QAAQ,WAAW;CAE/D,OAAO,aACL,MACA,MACA,YACA;EACE,GAAG;EACH;CACF,GACA,YAAY,WACd;AACF;;;;;AAMA,MAAa,mBAEG,8CAAgC,mBAAmB"}
@@ -1,17 +1,16 @@
1
- import { a as isLoadableLanguage } from "./component-Cf8Topxy.mjs";
1
+ import { a as isLoadableLanguage } from "./plugins-C0Uf67_P.mjs";
2
2
  import { createOnigurumaEngine } from "shiki/engine/oniguruma";
3
- //#region src/bundles/factory.ts
3
+ // src/bundles/factory.ts
4
4
  /**
5
5
  * Builds a highlighter factory for a Shiki bundle (full or web).
6
6
  * Unloadable languages are filtered out so unknown ids fall back to
7
7
  * plaintext instead of throwing.
8
8
  */
9
- const bundledHighlighterFactory = (getSingletonHighlighter, bundledLanguages) => (langsToLoad, themesToLoad, engine) => getSingletonHighlighter({
9
+ const bundledHighlighterFactory = (getSingletonHighlighter, bundledLanguages) => async (langsToLoad, themesToLoad, engine) => await getSingletonHighlighter({
10
10
  langs: langsToLoad.filter((lang) => isLoadableLanguage(lang, bundledLanguages)),
11
11
  themes: themesToLoad,
12
12
  engine: engine ?? createOnigurumaEngine(import("shiki/wasm"))
13
13
  });
14
- //#endregion
15
14
  export { bundledHighlighterFactory as t };
16
15
 
17
- //# sourceMappingURL=factory-DySLHBEo.mjs.map
16
+ //# sourceMappingURL=factory-Ds5IsDw-.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"factory-DySLHBEo.mjs","names":[],"sources":["../src/bundles/factory.ts"],"sourcesContent":["import { createOnigurumaEngine } from 'shiki/engine/oniguruma';\nimport type {\n Awaitable,\n DynamicImportLanguageRegistration,\n HighlighterCore,\n RegexEngine,\n} from 'shiki/core';\nimport type {\n HighlighterFactory,\n PreloadLanguage,\n Theme,\n} from '../lib/types';\nimport { isLoadableLanguage } from '../lib/language';\n\ntype GetSingletonHighlighter = (options: {\n langs: NonNullable<PreloadLanguage>[];\n themes: Theme[];\n engine: Awaitable<RegexEngine>;\n}) => Promise<HighlighterCore>;\n\n/**\n * Builds a highlighter factory for a Shiki bundle (full or web).\n * Unloadable languages are filtered out so unknown ids fall back to\n * plaintext instead of throwing.\n */\nexport const bundledHighlighterFactory =\n (\n getSingletonHighlighter: GetSingletonHighlighter,\n bundledLanguages: Record<string, DynamicImportLanguageRegistration>\n ): HighlighterFactory =>\n (langsToLoad, themesToLoad, engine) =>\n getSingletonHighlighter({\n langs: langsToLoad.filter((lang) =>\n isLoadableLanguage(lang, bundledLanguages)\n ),\n themes: themesToLoad,\n engine: engine ?? createOnigurumaEngine(import('shiki/wasm')),\n });\n"],"mappings":";;;;;;;;AAyBA,MAAa,6BAET,yBACA,sBAED,aAAa,cAAc,WAC1B,wBAAwB;CACtB,OAAO,YAAY,QAAQ,SACzB,mBAAmB,MAAM,gBAAgB,CAC3C;CACA,QAAQ;CACR,QAAQ,UAAU,sBAAsB,OAAO,aAAa;AAC9D,CAAC"}
1
+ {"version":3,"file":"factory-Ds5IsDw-.mjs","names":[],"sources":["../src/bundles/factory.ts"],"sourcesContent":["import { createOnigurumaEngine } from 'shiki/engine/oniguruma';\nimport type {\n Awaitable,\n DynamicImportLanguageRegistration,\n HighlighterCore,\n RegexEngine,\n} from 'shiki/core';\nimport type {\n HighlighterFactory,\n PreloadLanguage,\n Theme,\n} from '../lib/types';\nimport { isLoadableLanguage } from '../lib/language';\n\ntype GetSingletonHighlighter = (options: {\n langs: NonNullable<PreloadLanguage>[];\n themes: Theme[];\n engine: Awaitable<RegexEngine>;\n}) => Promise<HighlighterCore>;\n\n/**\n * Builds a highlighter factory for a Shiki bundle (full or web).\n * Unloadable languages are filtered out so unknown ids fall back to\n * plaintext instead of throwing.\n */\nexport const bundledHighlighterFactory =\n (\n getSingletonHighlighter: GetSingletonHighlighter,\n bundledLanguages: Record<string, DynamicImportLanguageRegistration>\n ): HighlighterFactory =>\n async (langsToLoad, themesToLoad, engine) =>\n await getSingletonHighlighter({\n langs: langsToLoad.filter((lang) =>\n isLoadableLanguage(lang, bundledLanguages)\n ),\n themes: themesToLoad,\n engine: engine ?? createOnigurumaEngine(import('shiki/wasm')),\n });\n"],"mappings":";;;;;;;;AAyBA,MAAa,6BAET,yBACA,qBAEF,OAAO,aAAa,cAAc,WAChC,MAAM,wBAAwB;CAC5B,OAAO,YAAY,QAAQ,SACzB,mBAAmB,MAAM,gBAAgB,CAC3C;CACA,QAAQ;CACR,QAAQ,UAAU,sBAAsB,OAAO,aAAa;AAC9D,CAAC"}
package/dist/index.d.mts CHANGED
@@ -1,7 +1,6 @@
1
- import { a as EngineName, c as HighlighterOptionsFor, d as PreloadLanguage, f as Theme, i as Element, l as Language, m as UseShikiHighlighter, n as isInlineCode, o as HighlightResult, p as Themes, r as rehypeInlineCodeProperty, s as HighlighterOptions, t as ShikiHighlighterProps, u as OutputFormat } from "./component-DRPT3D6t.mjs";
1
+ import { a as EngineName, c as HighlighterOptions, d as OutputFormat, f as PreloadLanguage, h as UseShikiHighlighter, i as Element, l as HighlighterOptionsFor, m as Themes, n as rehypeInlineCodeProperty, o as HighlightCache, p as Theme, r as ShikiHighlighterProps, s as HighlightResult, t as isInlineCode, u as Language } from "./plugins-DdjjMnbY.mjs";
2
2
  import { LanguageRegistration, TokensResult } from "shiki";
3
3
  import { createJavaScriptRawEngine, createJavaScriptRegexEngine } from "shiki/engine/javascript";
4
- //#region src/index.d.ts
5
4
  /**
6
5
  * Highlight code with shiki (full bundle)
7
6
  *
@@ -31,6 +30,4 @@ export declare const useShikiHighlighter: UseShikiHighlighter;
31
30
  * Includes all languages and themes for maximum compatibility.
32
31
  */
33
32
  export declare const ShikiHighlighter: import("react").ForwardRefExoticComponent<ShikiHighlighterProps & import("react").RefAttributes<HTMLElement>>;
34
- //#endregion
35
- export { type Element, type EngineName, type HighlightResult, type HighlighterOptions, type HighlighterOptionsFor, type Language, type LanguageRegistration, type OutputFormat, type PreloadLanguage, ShikiHighlighter as default, type ShikiHighlighterProps, type Theme, type Themes, type TokensResult, type UseShikiHighlighter, createJavaScriptRawEngine, createJavaScriptRegexEngine, isInlineCode, rehypeInlineCodeProperty };
36
- //# sourceMappingURL=index.d.mts.map
33
+ export { type Element, type EngineName, type HighlightCache, type HighlightResult, type HighlighterOptions, type HighlighterOptionsFor, type Language, type LanguageRegistration, type OutputFormat, type PreloadLanguage, ShikiHighlighter as default, type ShikiHighlighterProps, type Theme, type Themes, type TokensResult, type UseShikiHighlighter, createJavaScriptRawEngine, createJavaScriptRegexEngine, isInlineCode, rehypeInlineCodeProperty };
package/dist/index.mjs CHANGED
@@ -1,15 +1,14 @@
1
- import { i as useHighlight, n as isInlineCode, r as rehypeInlineCodeProperty, t as createShikiHighlighterComponent } from "./component-Cf8Topxy.mjs";
2
- import { t as bundledHighlighterFactory } from "./factory-DySLHBEo.mjs";
1
+ import { i as useHighlight, n as rehypeInlineCodeProperty, r as createShikiHighlighterComponent, t as isInlineCode } from "./plugins-C0Uf67_P.mjs";
2
+ import { t as bundledHighlighterFactory } from "./factory-Ds5IsDw-.mjs";
3
3
  import { bundledLanguages, getSingletonHighlighter } from "shiki";
4
4
  import { createJavaScriptRawEngine, createJavaScriptRegexEngine } from "shiki/engine/javascript";
5
- //#region src/bundles/full.ts
5
+ // src/bundles/full.ts
6
6
  /**
7
7
  * Highlighter factory for the full Shiki bundle: every language and
8
8
  * theme, largest size, maximum compatibility.
9
9
  */
10
10
  const createFullHighlighter = bundledHighlighterFactory(getSingletonHighlighter, bundledLanguages);
11
- //#endregion
12
- //#region src/index.ts
11
+ // src/index.ts
13
12
  /**
14
13
  * Highlight code with shiki (full bundle)
15
14
  *
@@ -39,7 +38,6 @@ const useShikiHighlighter = (code, lang, themeInput, options = {}) => useHighlig
39
38
  * Includes all languages and themes for maximum compatibility.
40
39
  */
41
40
  const ShikiHighlighter = /*#__PURE__*/ createShikiHighlighterComponent(useShikiHighlighter);
42
- //#endregion
43
41
  export { ShikiHighlighter, ShikiHighlighter as default, createJavaScriptRawEngine, createJavaScriptRegexEngine, isInlineCode, rehypeInlineCodeProperty, useShikiHighlighter };
44
42
 
45
43
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/bundles/full.ts","../src/index.ts"],"sourcesContent":["import { getSingletonHighlighter, bundledLanguages } from 'shiki';\nimport { bundledHighlighterFactory } from './factory';\n\n/**\n * Highlighter factory for the full Shiki bundle: every language and\n * theme, largest size, maximum compatibility.\n */\nexport const createFullHighlighter = bundledHighlighterFactory(\n getSingletonHighlighter,\n bundledLanguages\n);\n","import { useHighlight } 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 PreloadLanguage,\n Theme,\n Themes,\n Element,\n EngineName,\n HighlighterOptions,\n HighlighterOptionsFor,\n HighlightResult,\n OutputFormat,\n} from './lib/types';\n\nexport {\n createJavaScriptRegexEngine,\n createJavaScriptRawEngine,\n} from 'shiki/engine/javascript';\n\nexport type { LanguageRegistration, TokensResult } from 'shiki';\n\n/**\n * Highlight code with shiki (full bundle)\n *\n * @param code - Code to highlight\n * @param lang - Language (bundled or custom)\n * @param theme - Theme (bundled, multi-theme, or custom)\n * @param options - react-shiki options + shiki options\n * @returns Highlighted code as React elements (default), HTML string, or Shiki tokens, based on `outputFormat`\n *\n * @example\n * ```tsx\n * const highlighted = useShikiHighlighter(\n * 'const x = 1;',\n * 'typescript',\n * {\n * light: 'github-light',\n * dark: 'github-dark'\n * }\n * );\n * ```\n *\n * Full bundle (~6.4MB minified, 1.2MB gzipped). For smaller bundles: `react-shiki/web` or `react-shiki/core`\n */\nexport const useShikiHighlighter: UseShikiHighlighter = (\n code,\n lang,\n themeInput,\n options = {}\n) => useHighlight(code, lang, themeInput, options, createFullHighlighter);\n\n/**\n * ShikiHighlighter component using the full bundle.\n * Includes all languages and themes for maximum compatibility.\n */\nexport const ShikiHighlighter =\n /*#__PURE__*/ createShikiHighlighterComponent(useShikiHighlighter);\nexport default ShikiHighlighter;\n"],"mappings":";;;;;;;;;AAOA,MAAa,wBAAwB,0BACnC,yBACA,gBACF;;;;;;;;;;;;;;;;;;;;;;;;;;AC+CA,MAAa,uBACX,MACA,MACA,YACA,UAAU,CAAC,MACR,aAAa,MAAM,MAAM,YAAY,SAAS,qBAAqB;;;;;AAMxE,MAAa,mBACG,8CAAgC,mBAAmB"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/bundles/full.ts","../src/index.ts"],"sourcesContent":["import { getSingletonHighlighter, bundledLanguages } from 'shiki';\nimport { bundledHighlighterFactory } from './factory';\n\n/**\n * Highlighter factory for the full Shiki bundle: every language and\n * theme, largest size, maximum compatibility.\n */\nexport const createFullHighlighter = bundledHighlighterFactory(\n getSingletonHighlighter,\n bundledLanguages\n);\n","import { useHighlight } from './lib/hook';\nimport { createFullHighlighter } from './bundles/full';\nimport type { UseShikiHighlighter } from './lib/types';\nimport { createShikiHighlighterComponent } from './lib/component';\n// oxlint-disable-next-line unicorn/prefer-export-from -- `export type … from` here makes tsdown fold the core entry's declarations into the shared .d.mts chunk\nimport type { ShikiHighlighterProps } from './lib/component';\n\nexport { isInlineCode, rehypeInlineCodeProperty } from './lib/plugins';\n\nexport type { ShikiHighlighterProps };\n\nexport type {\n UseShikiHighlighter,\n Language,\n PreloadLanguage,\n Theme,\n Themes,\n Element,\n EngineName,\n HighlighterOptions,\n HighlighterOptionsFor,\n HighlightCache,\n HighlightResult,\n OutputFormat,\n} from './lib/types';\n\nexport {\n createJavaScriptRegexEngine,\n createJavaScriptRawEngine,\n} from 'shiki/engine/javascript';\n\nexport type { LanguageRegistration, TokensResult } from 'shiki';\n\n/**\n * Highlight code with shiki (full bundle)\n *\n * @param code - Code to highlight\n * @param lang - Language (bundled or custom)\n * @param theme - Theme (bundled, multi-theme, or custom)\n * @param options - react-shiki options + shiki options\n * @returns Highlighted code as React elements (default), HTML string, or Shiki tokens, based on `outputFormat`\n *\n * @example\n * ```tsx\n * const highlighted = useShikiHighlighter(\n * 'const x = 1;',\n * 'typescript',\n * {\n * light: 'github-light',\n * dark: 'github-dark'\n * }\n * );\n * ```\n *\n * Full bundle (~6.4MB minified, 1.2MB gzipped). For smaller bundles: `react-shiki/web` or `react-shiki/core`\n */\nexport const useShikiHighlighter: UseShikiHighlighter = (\n code,\n lang,\n themeInput,\n options = {}\n) => useHighlight(code, lang, themeInput, options, createFullHighlighter);\n\n/**\n * ShikiHighlighter component using the full bundle.\n * Includes all languages and themes for maximum compatibility.\n */\nexport const ShikiHighlighter =\n // oxlint-disable-next-line eslint/no-inline-comments -- the #__PURE__ annotation lets bundlers drop the component when only the hook is used\n /*#__PURE__*/ createShikiHighlighterComponent(useShikiHighlighter);\n\nexport default ShikiHighlighter;\n"],"mappings":";;;;;;;;;AAOA,MAAa,wBAAwB,0BACnC,yBACA,gBACF;;;;;;;;;;;;;;;;;;;;;;;;;AC8CA,MAAa,uBACX,MACA,MACA,YACA,UAAU,CAAC,MACR,aAAa,MAAM,MAAM,YAAY,SAAS,qBAAqB;;;;;AAMxE,MAAa,mBAEG,8CAAgC,mBAAmB"}
@@ -4,9 +4,9 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
4
4
  import { toJsxRuntime } from "hast-util-to-jsx-runtime";
5
5
  import { dequal } from "dequal";
6
6
  import { guessEmbeddedLanguages, isSpecialLang } from "shiki/core";
7
- import { visit } from "unist-util-visit";
8
7
  import { clsx } from "clsx";
9
- //#region src/lib/utils.ts
8
+ import { visit } from "unist-util-visit";
9
+ // src/lib/utils.ts
10
10
  /**
11
11
  * Returns a referentially stable version of `value` that only updates when content changes.
12
12
  * Uses reference equality as a fast path and deep comparison to prevent unnecessary updates.
@@ -35,14 +35,11 @@ const throttleHighlighting = (performHighlight, timeoutControl, throttleMs) => {
35
35
  timeoutControl.current.nextAllowedTime = Date.now() + throttleMs;
36
36
  }, delay);
37
37
  };
38
- //#endregion
39
- //#region src/styles/component.css?inline
38
+ // src/styles/component.css?inline
40
39
  var component_default = ":where(.rs-root){position:relative}:where(.rs-root.rs-default-styles pre){border-radius:.5rem;padding-block:1.25rem;padding-inline:1.5rem;overflow:auto}:where(.rs-root .rs-language-label){z-index:1;letter-spacing:-.05em;color:#6b7280d9;font-family:monospace;font-size:.75rem;position:absolute;top:.5rem;right:.75rem}\n";
41
- //#endregion
42
- //#region src/styles/features.css?inline
40
+ // src/styles/features.css?inline
43
41
  var features_default = ":where(.rs-has-line-numbers){counter-reset:line-number calc(var(--line-start,1) - 1)}:where(.rs-has-line-numbers .rs-line-number):before{counter-increment:line-number;content:counter(line-number);box-sizing:content-box;min-width:var(--rs-line-numbers-width,2ch);padding-left:var(--rs-line-numbers-padding-left,0ch);padding-right:var(--rs-line-numbers-padding-right,2ch);color:var(--rs-line-numbers-foreground,#6b728080);font-size:var(--rs-line-numbers-font-size,inherit);font-weight:var(--rs-line-numbers-font-weight,inherit);line-height:var(--rs-line-numbers-line-height,inherit);font-family:var(--rs-line-numbers-font-family,inherit);opacity:var(--rs-line-numbers-opacity,1);user-select:none;pointer-events:none;justify-content:flex-end;align-items:flex-start;display:inline-flex}:where(.rs-has-highlighted-lines){width:fit-content;min-width:100%;display:block}:where(.rs-highlighted-line){--_rs-hl-background:var(--rs-highlighted-line-background,color-mix(in srgb, currentColor 10%, transparent));background-color:var(--_rs-hl-background);width:100%;min-height:1lh;display:inline-block}:where(.rs-default-styles .rs-highlighted-line){border-image:linear-gradient(var(--_rs-hl-background) 0 0) fill 0 / 0 / 0 100vw;background-color:#0000}:where(.rs-has-line-numbers .rs-highlighted-line):before{color:var(--rs-highlighted-line-number-foreground,color-mix(in srgb, currentColor 65%, transparent))}\n";
44
- //#endregion
45
- //#region src/lib/styles.ts
42
+ // src/lib/styles.ts
46
43
  const injected = /* @__PURE__ */ new Set();
47
44
  const useStyleInsertionEffect = React.useInsertionEffect ?? React.useLayoutEffect;
48
45
  const inject = (css, key) => {
@@ -71,10 +68,12 @@ const useFeatureStyles = (enabled) => {
71
68
  if (enabled) inject(features_default, "features");
72
69
  }, [enabled]);
73
70
  };
74
- //#endregion
75
- //#region src/lib/language.ts
71
+ // src/lib/language.ts
76
72
  const FALLBACK_LANGUAGE = "plaintext";
77
- const toArray = (value) => value == null ? [] : Array.isArray(value) ? value : [value];
73
+ const toArray = (value) => {
74
+ if (value == null) return [];
75
+ return Array.isArray(value) ? value : [value];
76
+ };
78
77
  const isDynamicLanguage = (lang) => typeof lang === "function" || lang instanceof Promise || Array.isArray(lang) || typeof lang === "object" && lang != null && "default" in lang;
79
78
  const isRegistration = (lang) => typeof lang === "object" && lang != null && !isDynamicLanguage(lang);
80
79
  /** Dedupe identity: strings and registrations by value, dynamic inputs by reference */
@@ -144,17 +143,15 @@ const getEmbeddedLanguages = (code, languageId, highlighter) => {
144
143
  const bundled = highlighter.getBundledLanguages();
145
144
  return guessEmbeddedLanguages(code, languageId).flatMap((language) => bundled[language] ?? []);
146
145
  };
147
- //#endregion
148
- //#region src/lib/theme.ts
146
+ // src/lib/theme.ts
149
147
  const DEFAULT_THEMES = {
150
148
  light: "github-light",
151
149
  dark: "github-dark"
152
150
  };
153
151
  const isTextmateTheme = (value) => {
154
152
  if (typeof value !== "object" || value === null) return false;
155
- const v = value;
156
- const hasTokens = Array.isArray(v.tokenColors) || Array.isArray(v.settings);
157
- const hasIdentity = typeof v.name === "string" || typeof v.type === "string";
153
+ const hasTokens = "tokenColors" in value && Array.isArray(value.tokenColors) || "settings" in value && Array.isArray(value.settings);
154
+ const hasIdentity = "name" in value && typeof value.name === "string" || "type" in value && typeof value.type === "string";
158
155
  return hasTokens && hasIdentity;
159
156
  };
160
157
  function resolveTheme(themeInput) {
@@ -179,12 +176,17 @@ function resolveTheme(themeInput) {
179
176
  themesToLoad: validEntries.map(([, value]) => value)
180
177
  };
181
178
  }
182
- //#endregion
183
- //#region src/lib/engine.ts
179
+ // src/lib/engine.ts
184
180
  const engines = /* @__PURE__ */ new Map();
185
181
  const loaders = {
186
- javascript: () => import("shiki/engine/javascript").then((m) => m.createJavaScriptRegexEngine()),
187
- oniguruma: () => import("shiki/engine/oniguruma").then((m) => m.createOnigurumaEngine(import("shiki/wasm")))
182
+ javascript: async () => {
183
+ const { createJavaScriptRegexEngine } = await import("shiki/engine/javascript");
184
+ return createJavaScriptRegexEngine();
185
+ },
186
+ oniguruma: async () => {
187
+ const { createOnigurumaEngine } = await import("shiki/engine/oniguruma");
188
+ return await createOnigurumaEngine(import("shiki/wasm"));
189
+ }
188
190
  };
189
191
  /**
190
192
  * Resolves a named engine to a lazily created, cached instance.
@@ -195,17 +197,15 @@ const loaders = {
195
197
  */
196
198
  const resolveEngine = (engine) => {
197
199
  if (typeof engine !== "string") return engine;
198
- const load = loaders[engine];
199
- if (!load) throw new Error(`[react-shiki] unknown engine '${engine}', expected 'javascript' or 'oniguruma'`);
200
+ if (!Object.hasOwn(loaders, engine)) throw new Error(`[react-shiki] unknown engine '${engine}', expected 'javascript' or 'oniguruma'`);
200
201
  const cached = engines.get(engine);
201
202
  if (cached) return cached;
202
- const instance = load();
203
+ const instance = loaders[engine]();
203
204
  instance.catch(() => engines.delete(engine));
204
205
  engines.set(engine, instance);
205
206
  return instance;
206
207
  };
207
- //#endregion
208
- //#region src/lib/transformers.ts
208
+ // src/lib/transformers.ts
209
209
  const toLineNumberSet = (lineNumbers) => new Set(lineNumbers.filter(Number.isInteger));
210
210
  /**
211
211
  * Creates a transformer that enables line numbers display
@@ -249,8 +249,7 @@ function highlightedLinesTransformer(lineNumbers, startLine = 1) {
249
249
  }
250
250
  };
251
251
  }
252
- //#endregion
253
- //#region src/lib/options.ts
252
+ // src/lib/options.ts
254
253
  const buildThemeOptions = (resolvedTheme, defaultColor, cssVariablePrefix) => {
255
254
  if (resolvedTheme.isMulti) return {
256
255
  themes: resolvedTheme.themes,
@@ -261,7 +260,7 @@ const buildThemeOptions = (resolvedTheme, defaultColor, cssVariablePrefix) => {
261
260
  };
262
261
  const buildShikiOptions = (languageId, resolvedTheme, options) => {
263
262
  const { delay, customLanguages, preloadLanguages, outputFormat, highlighter, langAlias, engine, defaultColor, cssVariablePrefix, showLineNumbers, startingLineNumber, highlightLineNumbers, transformers: userTransformers, ...shikiPassthrough } = options;
264
- const transformers = [...userTransformers || []];
263
+ const transformers = [...userTransformers ?? []];
265
264
  if (showLineNumbers) transformers.push(lineNumbersTransformer(startingLineNumber));
266
265
  if (highlightLineNumbers?.length) transformers.push(highlightedLinesTransformer(highlightLineNumbers, startingLineNumber));
267
266
  return {
@@ -271,8 +270,7 @@ const buildShikiOptions = (languageId, resolvedTheme, options) => {
271
270
  transformers
272
271
  };
273
272
  };
274
- //#endregion
275
- //#region src/lib/hook.ts
273
+ // src/lib/hook.ts
276
274
  async function highlight(code, resolved, opts, factory) {
277
275
  const { languageId, langsToLoad, themesToLoad, shikiOptions } = resolved;
278
276
  const format = opts.outputFormat ?? "react";
@@ -297,12 +295,19 @@ async function highlight(code, resolved, opts, factory) {
297
295
  };
298
296
  return (outputs[format] ?? outputs.react)();
299
297
  }
300
- const useHighlight = (code, lang, themeInput, options = {}, highlighterFactory) => {
301
- const [highlightedCode, setHighlightedCode] = useState(null);
302
- useFeatureStyles(Boolean(options.showLineNumbers || options.highlightLineNumbers?.length));
298
+ const readCache = (cache, key) => {
299
+ if (!key) return null;
300
+ const cached = cache?.get(key);
301
+ return cached === void 0 || cached === "" ? null : cached;
302
+ };
303
+ const useHighlight = (code, lang, themeInput, optionsInput, highlighterFactory) => {
304
+ const { cache, ...rest } = optionsInput ?? {};
305
+ const cacheRef = useRef(cache);
306
+ cacheRef.current = cache;
307
+ useFeatureStyles(Boolean(rest.showLineNumbers || rest.highlightLineNumbers?.length));
303
308
  const stableLang = useStableValue(lang);
304
309
  const stableTheme = useStableValue(themeInput);
305
- const stableOpts = useStableValue(options);
310
+ const stableOpts = useStableValue(rest);
306
311
  const resolved = useMemo(() => {
307
312
  const { languageId, langsToLoad } = resolveLanguage(stableLang, stableOpts.customLanguages, stableOpts.langAlias, stableOpts.preloadLanguages);
308
313
  const theme = resolveTheme(stableTheme);
@@ -318,6 +323,27 @@ const useHighlight = (code, lang, themeInput, options = {}, highlighterFactory)
318
323
  stableTheme,
319
324
  stableOpts
320
325
  ]);
326
+ const hasCache = Boolean(cache);
327
+ const cacheScope = useMemo(() => hasCache ? JSON.stringify([
328
+ stableOpts.outputFormat ?? "react",
329
+ resolved.shikiOptions,
330
+ stableOpts.startingLineNumber,
331
+ stableOpts.highlightLineNumbers
332
+ ]) : void 0, [
333
+ hasCache,
334
+ resolved,
335
+ stableOpts
336
+ ]);
337
+ const cacheKey = cacheScope && `${cacheScope}\n${code}`;
338
+ const [highlightedCode, setHighlightedCode] = useState(() => readCache(cache, cacheKey));
339
+ const shown = useRef({
340
+ key: cacheKey,
341
+ result: highlightedCode
342
+ });
343
+ useEffect(() => () => {
344
+ const { key, result } = shown.current;
345
+ if (key && result !== null && result !== "") cacheRef.current?.set(key, result);
346
+ }, []);
321
347
  const requestIdRef = useRef(0);
322
348
  const timeoutControl = useRef({
323
349
  nextAllowedTime: 0,
@@ -327,29 +353,85 @@ const useHighlight = (code, lang, themeInput, options = {}, highlighterFactory)
327
353
  highlighterFactoryRef.current = highlighterFactory;
328
354
  useEffect(() => {
329
355
  const requestId = ++requestIdRef.current;
330
- if (!resolved.languageId) return;
356
+ const timeouts = timeoutControl.current;
357
+ const show = (result) => {
358
+ shown.current = {
359
+ key: cacheKey,
360
+ result
361
+ };
362
+ setHighlightedCode(result);
363
+ };
331
364
  const run = async () => {
332
365
  try {
333
366
  const result = await highlight(code, resolved, stableOpts, highlighterFactoryRef.current);
334
- if (requestId === requestIdRef.current) setHighlightedCode(result);
367
+ if (requestId === requestIdRef.current) show(result);
335
368
  } catch (error) {
336
369
  console.error("[react-shiki] highlight failed", error);
337
370
  }
338
371
  };
339
- if (stableOpts.delay) throttleHighlighting(run, timeoutControl, stableOpts.delay);
372
+ const cached = readCache(cacheRef.current, cacheKey);
373
+ if (!resolved.languageId) return () => {
374
+ clearTimeout(timeouts.timeoutId);
375
+ };
376
+ if (cached !== null) {
377
+ if (shown.current.result !== cached) show(cached);
378
+ } else if (stableOpts.delay) throttleHighlighting(run, timeoutControl, stableOpts.delay);
340
379
  else run();
341
380
  return () => {
342
- clearTimeout(timeoutControl.current.timeoutId);
381
+ clearTimeout(timeouts.timeoutId);
343
382
  };
344
383
  }, [
345
384
  code,
385
+ cacheKey,
346
386
  resolved,
347
387
  stableOpts
348
388
  ]);
349
389
  return highlightedCode;
350
390
  };
351
- //#endregion
352
- //#region src/lib/plugins.ts
391
+ // src/lib/component.tsx
392
+ let warnedTokensOutputFormat = false;
393
+ /**
394
+ * The component's props exclude `outputFormat: 'tokens'`, but plain-JS
395
+ * callers can still pass it. Tokens are not renderable React children,
396
+ * so fall back to 'react' instead of crashing the render.
397
+ */
398
+ const resolveOutputFormat = (outputFormat) => {
399
+ if (outputFormat !== "tokens") return outputFormat;
400
+ if (!warnedTokensOutputFormat) {
401
+ warnedTokensOutputFormat = true;
402
+ console.warn("[react-shiki] outputFormat 'tokens' is only supported by the useShikiHighlighter hook, falling back to 'react'");
403
+ }
404
+ return "react";
405
+ };
406
+ /**
407
+ * Base ShikiHighlighter component factory.
408
+ * This creates a component that uses the provided hook implementation.
409
+ */
410
+ const createShikiHighlighterComponent = (useShikiHighlighterImpl) => forwardRef(({ language, theme, addDefaultStyles = true, style, langStyle, className, langClassName, showLanguage = true, children: code, as: Element = "div", outputFormat, ...options }, ref) => {
411
+ useComponentStyles();
412
+ const displayLanguageId = typeof language === "object" ? language.name || null : language?.trim() || null;
413
+ const highlightedCode = useShikiHighlighterImpl(code, language, theme, {
414
+ ...options,
415
+ outputFormat: resolveOutputFormat(outputFormat)
416
+ });
417
+ return /* @__PURE__ */ jsxs(Element, {
418
+ ref,
419
+ "data-testid": "shiki-container",
420
+ "data-slot": "container",
421
+ className: clsx("rs-root", "not-prose", addDefaultStyles && "rs-default-styles", className),
422
+ style,
423
+ children: [showLanguage && displayLanguageId ? /* @__PURE__ */ jsx("span", {
424
+ "data-slot": "language-label",
425
+ className: clsx("rs-language-label", langClassName),
426
+ style: langStyle,
427
+ children: displayLanguageId
428
+ }) : null, typeof highlightedCode === "string" ? /* @__PURE__ */ jsx("div", {
429
+ "data-slot": "content",
430
+ dangerouslySetInnerHTML: { __html: highlightedCode }
431
+ }) : highlightedCode]
432
+ });
433
+ });
434
+ // src/lib/plugins.ts
353
435
  /**
354
436
  * Rehype plugin to add an 'inline' property to <code> elements
355
437
  * Sets 'inline' property to true if the <code> is not within a <pre> tag
@@ -374,55 +456,8 @@ function rehypeInlineCodeProperty() {
374
456
  * const isInline = node && isInlineCode(node: Element)
375
457
  */
376
458
  const isInlineCode = (node) => {
377
- return !(node.children || []).filter((child) => child.type === "text").map((child) => child.value).join("").includes("\n");
378
- };
379
- //#endregion
380
- //#region src/lib/component.tsx
381
- let warnedTokensOutputFormat = false;
382
- /**
383
- * The component's props exclude `outputFormat: 'tokens'`, but plain-JS
384
- * callers can still pass it. Tokens are not renderable React children,
385
- * so fall back to 'react' instead of crashing the render.
386
- */
387
- const resolveOutputFormat = (outputFormat) => {
388
- if (outputFormat !== "tokens") return outputFormat;
389
- if (!warnedTokensOutputFormat) {
390
- warnedTokensOutputFormat = true;
391
- console.warn("[react-shiki] outputFormat 'tokens' is only supported by the useShikiHighlighter hook, falling back to 'react'");
392
- }
393
- return "react";
394
- };
395
- /**
396
- * Base ShikiHighlighter component factory.
397
- * This creates a component that uses the provided hook implementation.
398
- */
399
- const createShikiHighlighterComponent = (useShikiHighlighterImpl) => {
400
- return forwardRef(({ language, theme, addDefaultStyles = true, style, langStyle, className, langClassName, showLanguage = true, children: code, as: Element = "div", outputFormat, ...options }, ref) => {
401
- useComponentStyles();
402
- const displayLanguageId = typeof language === "object" ? language.name || null : language?.trim() || null;
403
- const highlightedCode = useShikiHighlighterImpl(code, language, theme, {
404
- ...options,
405
- outputFormat: resolveOutputFormat(outputFormat)
406
- });
407
- return /* @__PURE__ */ jsxs(Element, {
408
- ref,
409
- "data-testid": "shiki-container",
410
- "data-slot": "container",
411
- className: clsx("rs-root", "not-prose", addDefaultStyles && "rs-default-styles", className),
412
- style,
413
- children: [showLanguage && displayLanguageId ? /* @__PURE__ */ jsx("span", {
414
- "data-slot": "language-label",
415
- className: clsx("rs-language-label", langClassName),
416
- style: langStyle,
417
- children: displayLanguageId
418
- }) : null, typeof highlightedCode === "string" ? /* @__PURE__ */ jsx("div", {
419
- "data-slot": "content",
420
- dangerouslySetInnerHTML: { __html: highlightedCode }
421
- }) : highlightedCode]
422
- });
423
- });
459
+ return !(node.children ?? []).filter((child) => child.type === "text").map((child) => child.value).join("").includes("\n");
424
460
  };
425
- //#endregion
426
- export { isLoadableLanguage as a, useHighlight as i, isInlineCode as n, rehypeInlineCodeProperty as r, createShikiHighlighterComponent as t };
461
+ export { isLoadableLanguage as a, useHighlight as i, rehypeInlineCodeProperty as n, createShikiHighlighterComponent as r, isInlineCode as t };
427
462
 
428
- //# sourceMappingURL=component-Cf8Topxy.mjs.map
463
+ //# sourceMappingURL=plugins-C0Uf67_P.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugins-C0Uf67_P.mjs","names":["componentCss","featuresCss"],"sources":["../src/lib/utils.ts","../src/styles/component.css?inline","../src/styles/features.css?inline","../src/lib/styles.ts","../src/lib/language.ts","../src/lib/theme.ts","../src/lib/engine.ts","../src/lib/transformers.ts","../src/lib/options.ts","../src/lib/hook.ts","../src/lib/component.tsx","../src/lib/plugins.ts"],"sourcesContent":["import { useRef } from 'react';\nimport { dequal } from 'dequal';\n\nimport type { TimeoutState } from './types';\n\n/**\n * Returns a referentially stable version of `value` that only updates when content changes.\n * Uses reference equality as a fast path and deep comparison to prevent unnecessary updates.\n */\nexport const useStableValue = <T>(value: T): T => {\n const ref = useRef(value);\n\n if (value !== ref.current && !dequal(value, ref.current)) {\n ref.current = value;\n }\n\n return ref.current;\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: { current: TimeoutState },\n throttleMs: number\n) => {\n clearTimeout(timeoutControl.current.timeoutId);\n\n const delay = Math.max(\n 0,\n timeoutControl.current.nextAllowedTime - Date.now()\n );\n\n timeoutControl.current.timeoutId = setTimeout(() => {\n void performHighlight();\n timeoutControl.current.nextAllowedTime = Date.now() + throttleMs;\n }, delay);\n};\n","/* Unlayered :where() defaults: immune to layered resets, overridden by any user rule. */\n:where(.rs-root) {\n position: relative;\n}\n\n:where(.rs-root.rs-default-styles pre) {\n overflow: auto;\n border-radius: 0.5rem;\n padding-inline: 1.5rem;\n padding-block: 1.25rem;\n}\n\n:where(.rs-root .rs-language-label) {\n position: absolute;\n z-index: 1;\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",":where(.rs-has-line-numbers) {\n counter-reset: line-number calc(var(--line-start, 1) - 1);\n}\n\n:where(.rs-has-line-numbers .rs-line-number)::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(--rs-line-numbers-width, 2ch);\n padding-left: var(--rs-line-numbers-padding-left, 0ch);\n padding-right: var(--rs-line-numbers-padding-right, 2ch);\n color: var(--rs-line-numbers-foreground, rgba(107, 114, 128, 0.5));\n font-size: var(--rs-line-numbers-font-size, inherit);\n font-weight: var(--rs-line-numbers-font-weight, inherit);\n line-height: var(--rs-line-numbers-line-height, inherit);\n font-family: var(--rs-line-numbers-font-family, inherit);\n opacity: var(--rs-line-numbers-opacity, 1);\n user-select: none;\n pointer-events: none;\n}\n\n:where(.rs-has-highlighted-lines) {\n display: block;\n width: fit-content;\n min-width: 100%;\n}\n\n:where(.rs-highlighted-line) {\n --_rs-hl-background: var(\n --rs-highlighted-line-background,\n color-mix(in srgb, currentColor 10%, transparent)\n );\n display: inline-block;\n width: 100%;\n min-height: 1lh;\n background-color: var(--_rs-hl-background);\n}\n\n/* Full bleed: the 100vw outset paints into the pre's padding, clipped by its overflow. */\n:where(.rs-default-styles .rs-highlighted-line) {\n background-color: transparent;\n border-image:\n linear-gradient(var(--_rs-hl-background) 0 0) fill 0 / 0 / 0 100vw;\n}\n\n:where(.rs-has-line-numbers .rs-highlighted-line)::before {\n color: var(\n --rs-highlighted-line-number-foreground,\n color-mix(in srgb, currentColor 65%, transparent)\n );\n}\n","import componentCss from '../styles/component.css?inline';\nimport featuresCss from '../styles/features.css?inline';\nimport * as React from 'react';\n// Side-effect import so tsdown emits dist/style.css for the ./css export;\n// stripped from the built JS, guarded by check:package\nimport '../styles/index.css';\n\nconst injected = new Set<string>();\n\nconst useStyleInsertionEffect =\n React.useInsertionEffect ?? React.useLayoutEffect;\n\nconst inject = (css: string, key: string) => {\n if (injected.has(key) || typeof document === 'undefined') {\n return;\n }\n\n injected.add(key);\n\n try {\n // Not governed by CSP style-src in current browsers, unlike the <style> fallback\n const sheet = new CSSStyleSheet();\n sheet.replaceSync(css);\n document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];\n } catch {\n const style = document.createElement('style');\n style.dataset.reactShiki = key;\n style.textContent = css;\n document.head.append(style);\n }\n};\n\n/** Installs component styles before layout without triggering a render. */\nexport const useComponentStyles = () => {\n useStyleInsertionEffect(() => {\n inject(componentCss, 'component');\n }, []);\n};\n\n/** Installs line-number/highlight styles only when those options are used. */\nexport const useFeatureStyles = (enabled: boolean) => {\n useStyleInsertionEffect(() => {\n if (enabled) {\n inject(featuresCss, 'features');\n }\n }, [enabled]);\n};\n","import type { Language, PreloadLanguage } from './types';\nimport { guessEmbeddedLanguages, isSpecialLang } from 'shiki/core';\nimport type {\n DynamicImportLanguageRegistration,\n Highlighter,\n HighlighterCore,\n LanguageInput,\n LanguageRegistration,\n} from 'shiki';\n\nexport const FALLBACK_LANGUAGE = 'plaintext';\n\nconst toArray = <T>(value?: T | T[]): T[] => {\n if (value == null) {\n return [];\n }\n\n return Array.isArray(value) ? value : [value];\n};\n\n/** Deferred `LanguageInput` forms (getter, promise, module, array) that shiki resolves itself */\ntype DynamicLanguage = Exclude<LanguageInput, LanguageRegistration>;\n\nconst isDynamicLanguage = (\n lang: PreloadLanguage\n): lang is DynamicLanguage =>\n typeof lang === 'function' ||\n lang instanceof Promise ||\n Array.isArray(lang) ||\n (typeof lang === 'object' && lang != null && 'default' in lang);\n\nconst isRegistration = (\n lang: PreloadLanguage\n): lang is LanguageRegistration =>\n typeof lang === 'object' && lang != null && !isDynamicLanguage(lang);\n\n/** Dedupe identity: strings and registrations by value, dynamic inputs by reference */\nconst languageKey = (lang: PreloadLanguage): string | object | null => {\n if (lang == null) {\n return null;\n }\n\n if (typeof lang === 'string') {\n return `s:${lang}`;\n }\n\n if (isDynamicLanguage(lang)) {\n return lang;\n }\n\n return `o:${lang.name}::${lang.scopeName}`;\n};\n\nconst dedupeLanguages = (langs: PreloadLanguage[]): PreloadLanguage[] => {\n const seen = new Set<string | object>();\n\n return langs.filter((lang) => {\n const key = languageKey(lang);\n\n if (key == null || seen.has(key)) {\n return false;\n }\n\n seen.add(key);\n\n return true;\n });\n};\n\n/**\n * A registration matches a query by name, scopeName (full or its last\n * segment, e.g. \"source.tsx\" -> \"tsx\"), aliases, or fileTypes.\n */\nconst registrationMatches = (\n candidate: LanguageRegistration,\n matches: (str: string | undefined) => boolean\n): boolean =>\n matches(candidate.name) ||\n matches(candidate.scopeName) ||\n matches(candidate.scopeName?.split('.').pop()) ||\n !!candidate.aliases?.some(matches) ||\n !!candidate.fileTypes?.some(matches);\n\ninterface LanguageResult {\n languageId: string;\n langsToLoad: PreloadLanguage[];\n}\n\n/**\n * Resolves the language prop to a shiki language id plus everything the\n * factory should load (primary, preloaded, and custom grammars, deduped).\n */\nexport const resolveLanguage = (\n lang: Language,\n customLanguages?: LanguageRegistration | LanguageRegistration[],\n langAliases?: Record<string, string>,\n preloadLanguages?: PreloadLanguage | PreloadLanguage[]\n): LanguageResult => {\n const customLangs = toArray(customLanguages);\n const preloadLangs = toArray(preloadLanguages);\n\n const result = (\n languageId: string,\n primary?: Language\n ): LanguageResult => ({\n languageId,\n langsToLoad: dedupeLanguages([\n primary,\n ...preloadLangs,\n ...customLangs,\n ]),\n });\n\n if (lang == null || (typeof lang === 'string' && !lang.trim())) {\n return result(FALLBACK_LANGUAGE);\n }\n\n if (typeof lang === 'object') {\n return result(lang.name, lang);\n }\n\n const normalized = lang.trim();\n const query = normalized.toLowerCase();\n\n const matches = (str: string | undefined): boolean =>\n str?.toLowerCase() === query;\n\n // Custom registrations (from both customLanguages and preloadLanguages)\n const customMatch = [...customLangs, ...preloadLangs]\n .filter(isRegistration)\n .find((candidate) => registrationMatches(candidate, matches));\n\n if (customMatch) {\n return result(customMatch.name || normalized, customMatch);\n }\n\n const alias = Object.entries(langAliases ?? {}).find(([name]) =>\n matches(name)\n )?.[1];\n\n if (alias) {\n return result(alias, alias);\n }\n\n // Any other string passes through to the factory\n return result(normalized, normalized);\n};\n\n/**\n * Used in factories to check if language is supported.\n * Objects are validated as grammar registrations (name + scopeName);\n * dynamic imports pass through for shiki to resolve.\n */\nexport const isLoadableLanguage = <T extends string>(\n lang: PreloadLanguage,\n bundledLanguages: Record<T, DynamicImportLanguageRegistration>\n): lang is NonNullable<PreloadLanguage> => {\n if (lang == null) {\n return false;\n }\n\n if (typeof lang === 'string') {\n return lang in bundledLanguages;\n }\n\n if (isDynamicLanguage(lang)) {\n return true;\n }\n\n return (\n typeof lang.name === 'string' && typeof lang.scopeName === 'string'\n );\n};\n\n/**\n * Used in hook to resolve loaded language for highlighting.\n * Falls back to \"plaintext\" if not supported.\n */\nexport const resolveLoadedLanguage = (\n languageId: string,\n loadedLanguages: string[]\n): string =>\n isSpecialLang(languageId) || loadedLanguages.includes(languageId)\n ? languageId\n : FALLBACK_LANGUAGE;\n\nexport const getEmbeddedLanguages = (\n code: string,\n languageId: string,\n highlighter: Highlighter | HighlighterCore\n): LanguageInput[] => {\n const bundled: Record<string, LanguageInput> =\n highlighter.getBundledLanguages();\n\n return guessEmbeddedLanguages(code, languageId).flatMap(\n // oxlint-disable-next-line typescript/promise-function-async -- returns shiki's LanguageInput, which can itself be a promise\n (language) => bundled[language] ?? []\n );\n};\n","import type { Theme, Themes } from './types';\nimport type { ThemeRegistrationAny } from 'shiki/core';\n\nexport const DEFAULT_THEMES: Themes = {\n light: 'github-light',\n dark: 'github-dark',\n};\n\nexport type ResolvedTheme =\n | {\n isMulti: true;\n themes: Record<string, Theme>;\n themesToLoad: Theme[];\n }\n | { isMulti: false; theme: Theme; themesToLoad: Theme[] };\n\nconst isTextmateTheme = (\n value: Theme | Themes | undefined\n): value is ThemeRegistrationAny => {\n if (typeof value !== 'object' || value === null) {\n return false;\n }\n\n const hasTokens =\n ('tokenColors' in value && Array.isArray(value.tokenColors)) ||\n ('settings' in value && Array.isArray(value.settings));\n\n const hasIdentity =\n ('name' in value && typeof value.name === 'string') ||\n ('type' in value && typeof value.type === 'string');\n\n return hasTokens && hasIdentity;\n};\n\nexport function resolveTheme(themeInput: Theme | Themes): ResolvedTheme {\n if (\n typeof themeInput !== 'object' ||\n themeInput === null ||\n isTextmateTheme(themeInput)\n ) {\n return {\n isMulti: false,\n theme: themeInput,\n themesToLoad: [themeInput],\n };\n }\n\n const validEntries = Object.entries(themeInput).filter(\n ([key, value]) =>\n key.trim() !== '' &&\n ((typeof value === 'string' && value.trim() !== '') ||\n isTextmateTheme(value))\n );\n\n if (validEntries.length === 0) {\n console.warn(\n '[react-shiki] invalid multi-theme config, falling back to defaults'\n );\n\n return {\n isMulti: true,\n themes: DEFAULT_THEMES,\n themesToLoad: Object.values(DEFAULT_THEMES),\n };\n }\n\n if (validEntries.length !== Object.keys(themeInput).length) {\n console.warn(\n '[react-shiki] multi-theme config contained invalid entries; they were dropped'\n );\n }\n\n const themes = Object.fromEntries(validEntries);\n\n return {\n isMulti: true,\n themes,\n themesToLoad: validEntries.map(([, value]) => value),\n };\n}\n","import type { Awaitable, RegexEngine } from 'shiki/core';\nimport type { EngineName } from './types';\n\nconst engines = new Map<EngineName, Promise<RegexEngine>>();\n\n// Dynamic imports keep unused engines out of the loaded graph: naming an\n// engine loads only that engine, and 'javascript' never fetches the wasm.\nconst loaders: Record<EngineName, () => Promise<RegexEngine>> = {\n javascript: async () => {\n const { createJavaScriptRegexEngine } =\n await import('shiki/engine/javascript');\n\n return createJavaScriptRegexEngine();\n },\n oniguruma: async () => {\n const { createOnigurumaEngine } =\n await import('shiki/engine/oniguruma');\n\n return await createOnigurumaEngine(import('shiki/wasm'));\n },\n};\n\n/**\n * Resolves a named engine to a lazily created, cached instance.\n * Engine instances and undefined pass through untouched.\n *\n * The cache makes named engines referentially stable across renders and\n * call sites, unlike inline `createJavaScriptRegexEngine()` calls.\n */\nexport const resolveEngine = (\n engine: EngineName | Awaitable<RegexEngine> | undefined\n): Awaitable<RegexEngine> | undefined => {\n if (typeof engine !== 'string') {\n return engine;\n }\n\n if (!Object.hasOwn(loaders, engine)) {\n throw new Error(\n `[react-shiki] unknown engine '${engine}', expected 'javascript' or 'oniguruma'`\n );\n }\n\n const cached = engines.get(engine);\n\n if (cached) {\n return cached;\n }\n\n const instance = loaders[engine]();\n // Evict rejected loads (e.g. transient wasm fetch failure) so the\n // next highlight retries instead of reusing the cached rejection.\n instance.catch(() => engines.delete(engine));\n engines.set(engine, instance);\n\n return instance;\n};\n","import type { ShikiTransformer } from 'shiki/core';\n\nconst toLineNumberSet = (lineNumbers: readonly number[]) =>\n new Set(lineNumbers.filter(Number.isInteger));\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, 'rs-has-line-numbers');\n\n if (startLine !== 1) {\n const existing = node.properties?.style;\n node.properties = {\n ...node.properties,\n style: [existing, `--line-start: ${startLine}`]\n .filter(Boolean)\n .join('; '),\n };\n }\n },\n line(node) {\n this.addClassToHast(node, 'rs-line-number');\n\n return node;\n },\n };\n}\n\n/**\n * Creates a transformer that marks selected displayed line numbers.\n * @param lineNumbers - Displayed line numbers to highlight\n * @param startLine - The starting line number (defaults to 1)\n */\nexport function highlightedLinesTransformer(\n lineNumbers: readonly number[],\n startLine = 1\n): ShikiTransformer {\n const highlightedLines = toLineNumberSet(lineNumbers);\n\n return {\n name: 'react-shiki:highlight-lines',\n code(node) {\n this.addClassToHast(node, 'rs-has-highlighted-lines');\n },\n line(node, line) {\n const displayedLine = startLine + line - 1;\n\n if (highlightedLines.has(displayedLine)) {\n this.addClassToHast(node, 'rs-highlighted-line');\n }\n\n return node;\n },\n };\n}\n","import type {\n BundledTheme,\n CodeOptionsMultipleThemes,\n CodeOptionsSingleTheme,\n CodeToHastOptions,\n} from 'shiki';\n\nimport type {\n HighlighterOptions,\n HighlighterOptionsFor,\n OutputFormat,\n} from './types';\nimport type { ResolvedTheme } from './theme';\nimport {\n highlightedLinesTransformer,\n lineNumbersTransformer,\n} from './transformers';\n\nconst buildThemeOptions = (\n resolvedTheme: ResolvedTheme,\n defaultColor: HighlighterOptions['defaultColor'],\n cssVariablePrefix: HighlighterOptions['cssVariablePrefix']\n):\n | CodeOptionsMultipleThemes<BundledTheme>\n | CodeOptionsSingleTheme<BundledTheme> => {\n if (resolvedTheme.isMulti) {\n return {\n themes: resolvedTheme.themes,\n defaultColor,\n cssVariablePrefix,\n };\n }\n\n return {\n theme: resolvedTheme.theme,\n };\n};\n\nexport const buildShikiOptions = (\n languageId: string,\n resolvedTheme: ResolvedTheme,\n options: HighlighterOptionsFor<OutputFormat>\n): CodeToHastOptions => {\n const {\n delay,\n // oxlint-disable-next-line typescript/no-deprecated -- omitted from the options passed through to shiki\n customLanguages,\n preloadLanguages,\n outputFormat,\n highlighter,\n langAlias,\n engine,\n defaultColor,\n cssVariablePrefix,\n showLineNumbers,\n startingLineNumber,\n highlightLineNumbers,\n transformers: userTransformers,\n ...shikiPassthrough\n } = options;\n\n const transformers = [...(userTransformers ?? [])];\n\n if (showLineNumbers) {\n transformers.push(lineNumbersTransformer(startingLineNumber));\n }\n\n if (highlightLineNumbers?.length) {\n transformers.push(\n highlightedLinesTransformer(\n highlightLineNumbers,\n startingLineNumber\n )\n );\n }\n\n return {\n lang: languageId,\n ...buildThemeOptions(resolvedTheme, defaultColor, cssVariablePrefix),\n ...shikiPassthrough,\n transformers,\n };\n};\n","import { useEffect, useMemo, useRef, useState } from 'react';\nimport type { ReactElement } from 'react';\nimport { jsx, jsxs, Fragment } from 'react/jsx-runtime';\nimport { toJsxRuntime } from 'hast-util-to-jsx-runtime';\nimport type { CodeToHastOptions } from 'shiki';\n\nimport type {\n HighlightCache,\n HighlighterFactory,\n HighlighterOptions,\n HighlighterOptionsFor,\n HighlightResult,\n HighlightResultMap,\n Language,\n OutputFormat,\n PreloadLanguage,\n Theme,\n Themes,\n TimeoutState,\n} from './types';\n\nimport { throttleHighlighting, useStableValue } from './utils';\nimport { useFeatureStyles } from './styles';\nimport {\n getEmbeddedLanguages,\n resolveLanguage,\n resolveLoadedLanguage,\n} from './language';\nimport { resolveTheme } from './theme';\nimport { resolveEngine } from './engine';\nimport { buildShikiOptions } from './options';\n\nexport async function highlight<F extends OutputFormat = 'react'>(\n code: string,\n resolved: {\n languageId: string;\n langsToLoad: PreloadLanguage[];\n themesToLoad: Theme[];\n shikiOptions: CodeToHastOptions;\n },\n opts: Pick<HighlighterOptions, 'highlighter' | 'engine'> & {\n outputFormat?: F;\n },\n factory: HighlighterFactory\n): Promise<HighlightResultMap[F]> {\n const { languageId, langsToLoad, themesToLoad, shikiOptions } =\n resolved;\n\n const format: OutputFormat = opts.outputFormat ?? 'react';\n\n const highlighter =\n opts.highlighter ??\n (await (async () => {\n const hl = await factory(\n langsToLoad,\n themesToLoad,\n resolveEngine(opts.engine)\n );\n\n await hl.loadLanguage(\n ...getEmbeddedLanguages(code, languageId, hl)\n );\n\n return hl;\n })());\n\n const language = resolveLoadedLanguage(\n languageId,\n highlighter.getLoadedLanguages()\n );\n\n const options = { ...shikiOptions, lang: language };\n\n const outputs = {\n react: (): ReactElement =>\n // oxlint-disable-next-line typescript/no-unsafe-return -- toJsxRuntime returns the global JSX.Element, which React 19's types no longer declare\n toJsxRuntime(highlighter.codeToHast(code, options), {\n jsx,\n jsxs,\n Fragment,\n }),\n html: () => highlighter.codeToHtml(code, options),\n tokens: () => highlighter.codeToTokens(code, options),\n } satisfies { [K in OutputFormat]: () => HighlightResultMap[K] };\n\n // Untyped call sites can pass formats outside OutputFormat; degrade to\n // react rendering like the pre-tokens ternary did instead of throwing.\n // oxlint-disable-next-line typescript/no-unsafe-type-assertion, anti-slop/require-safety-comment-for-type-assertion -- outputs[format] returns HighlightResultMap[format], and TS can't tie format to the generic F\n return (outputs[format] ?? outputs.react)() as HighlightResultMap[F];\n}\n\nconst readCache = <F extends OutputFormat>(\n cache: HighlightCache | undefined,\n key: string | undefined\n): HighlightResult<F> => {\n if (!key) {\n return null;\n }\n\n // oxlint-disable-next-line typescript/no-unsafe-type-assertion, anti-slop/require-safety-comment-for-type-assertion -- the key includes outputFormat, so an entry under it is HighlightResultMap[F]\n const cached = cache?.get(key) as HighlightResultMap[F] | undefined;\n\n return cached === undefined || cached === '' ? null : cached;\n};\n\nexport const useHighlight = <F extends OutputFormat = 'react'>(\n code: string,\n lang: Language,\n themeInput: Theme | Themes,\n optionsInput: HighlighterOptionsFor<F> | undefined,\n highlighterFactory: HighlighterFactory\n): HighlightResult<F> => {\n const { cache, ...rest } = optionsInput ?? {};\n const cacheRef = useRef(cache);\n cacheRef.current = cache;\n\n // This hook owns the line-number/highlight output, so it also owns the\n // corresponding styles. The primitive boolean limits effect reruns.\n useFeatureStyles(\n Boolean(rest.showLineNumbers || rest.highlightLineNumbers?.length)\n );\n\n const stableLang = useStableValue(lang);\n const stableTheme = useStableValue(themeInput);\n const stableOpts = useStableValue(rest);\n\n const resolved = useMemo(() => {\n const { languageId, langsToLoad } = resolveLanguage(\n stableLang,\n // oxlint-disable-next-line typescript/no-deprecated -- the hook still honors its own deprecated prop\n stableOpts.customLanguages,\n stableOpts.langAlias,\n stableOpts.preloadLanguages\n );\n\n const theme = resolveTheme(stableTheme);\n\n const shikiOptions = buildShikiOptions(languageId, theme, stableOpts);\n\n return {\n languageId,\n langsToLoad,\n themesToLoad: theme.themesToLoad,\n shikiOptions,\n };\n }, [stableLang, stableTheme, stableOpts]);\n\n const hasCache = Boolean(cache);\n\n const cacheScope = useMemo(\n () =>\n hasCache\n ? JSON.stringify([\n stableOpts.outputFormat ?? 'react',\n resolved.shikiOptions,\n stableOpts.startingLineNumber,\n stableOpts.highlightLineNumbers,\n ])\n : undefined,\n [hasCache, resolved, stableOpts]\n );\n\n const cacheKey = cacheScope && `${cacheScope}\\n${code}`;\n\n const [highlightedCode, setHighlightedCode] = useState(() =>\n readCache<F>(cache, cacheKey)\n );\n\n const shown = useRef({ key: cacheKey, result: highlightedCode });\n\n useEffect(\n () => () => {\n const { key, result } = shown.current;\n\n if (key && result !== null && result !== '') {\n cacheRef.current?.set(key, result);\n }\n },\n []\n );\n\n const requestIdRef = useRef(0);\n\n const timeoutControl = useRef<TimeoutState>({\n nextAllowedTime: 0,\n timeoutId: undefined,\n });\n\n // Factory is invoked imperatively; its identity is not semantic, so it must\n // not invalidate the effect on every render. See issue #161.\n const highlighterFactoryRef = useRef(highlighterFactory);\n highlighterFactoryRef.current = highlighterFactory;\n\n useEffect(() => {\n const requestId = ++requestIdRef.current;\n const timeouts = timeoutControl.current;\n\n const show = (result: HighlightResult<F>) => {\n shown.current = { key: cacheKey, result };\n setHighlightedCode(result);\n };\n\n const run = async () => {\n try {\n const result = await highlight<F>(\n code,\n resolved,\n stableOpts,\n highlighterFactoryRef.current\n );\n\n if (requestId === requestIdRef.current) {\n show(result);\n }\n } catch (error) {\n console.error('[react-shiki] highlight failed', error);\n }\n };\n\n const cached = readCache<F>(cacheRef.current, cacheKey);\n\n if (!resolved.languageId) {\n return () => {\n clearTimeout(timeouts.timeoutId);\n };\n }\n\n if (cached !== null) {\n if (shown.current.result !== cached) {\n show(cached);\n }\n } else if (stableOpts.delay) {\n throttleHighlighting(run, timeoutControl, stableOpts.delay);\n } else {\n void run();\n }\n\n return () => {\n clearTimeout(timeouts.timeoutId);\n };\n }, [code, cacheKey, resolved, stableOpts]);\n\n return highlightedCode;\n};\n","import { clsx } from 'clsx';\n\nimport type {\n HighlighterOptions,\n Language,\n OutputFormat,\n Theme,\n Themes,\n UseShikiHighlighter,\n} from './types';\nimport { forwardRef } from 'react';\nimport { useComponentStyles } from './styles';\n\nlet warnedTokensOutputFormat = false;\n\n/**\n * The component's props exclude `outputFormat: 'tokens'`, but plain-JS\n * callers can still pass it. Tokens are not renderable React children,\n * so fall back to 'react' instead of crashing the render.\n */\nconst resolveOutputFormat = (\n outputFormat: OutputFormat | undefined\n): HighlighterOptions['outputFormat'] => {\n if (outputFormat !== 'tokens') {\n return outputFormat;\n }\n\n if (!warnedTokensOutputFormat) {\n warnedTokensOutputFormat = true;\n console.warn(\n \"[react-shiki] outputFormat 'tokens' is only supported by the useShikiHighlighter hook, falling back to 'react'\"\n );\n }\n\n return 'react';\n};\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 * The HTML element that wraps the generated code block.\n * @default 'div'\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 forwardRef<HTMLElement, ShikiHighlighterProps>(\n // oxlint-disable-next-line react/display-name -- naming the render function would change the name React DevTools shows for the published component\n (\n {\n language,\n theme,\n addDefaultStyles = true,\n style,\n langStyle,\n className,\n langClassName,\n showLanguage = true,\n children: code,\n as: Element = 'div',\n outputFormat,\n ...options\n },\n ref\n ) => {\n // Kept inside the component so hook-only bundles can tree-shake it\n // together with the component CSS.\n useComponentStyles();\n\n const displayLanguageId =\n typeof language === 'object'\n ? language.name || null\n : language?.trim() || null;\n\n const highlightedCode = useShikiHighlighterImpl(\n code,\n language,\n theme,\n {\n ...options,\n outputFormat: resolveOutputFormat(outputFormat),\n }\n );\n\n return (\n <Element\n ref={ref}\n data-testid=\"shiki-container\"\n data-slot=\"container\"\n className={clsx(\n 'rs-root',\n 'not-prose',\n addDefaultStyles && 'rs-default-styles',\n className\n )}\n style={style}\n >\n {showLanguage && displayLanguageId ? (\n <span\n data-slot=\"language-label\"\n className={clsx('rs-language-label', langClassName)}\n style={langStyle}\n >\n {displayLanguageId}\n </span>\n ) : null}\n {typeof highlightedCode === 'string' ? (\n <div\n data-slot=\"content\"\n // oxlint-disable-next-line react/no-danger -- outputFormat 'html' renders shiki's escaped HTML by design\n dangerouslySetInnerHTML={{ __html: highlightedCode }}\n />\n ) : (\n highlightedCode\n )}\n </Element>\n );\n }\n );\n","import { visit } from 'unist-util-visit';\nimport type { Root } from 'hast';\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: Root): undefined => {\n visit(tree, 'element', (node: Element, _index, parent) => {\n if (\n node.tagName === 'code' &&\n parent?.type === 'element' &&\n parent.tagName !== 'pre'\n ) {\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"],"mappings":"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"}
@@ -1,6 +1,5 @@
1
1
  import { ReactElement } from "react";
2
2
  import { Awaitable, BundledHighlighterOptions, BundledLanguage, BundledTheme, CodeOptionsMultipleThemes, CodeToHastOptions, Highlighter, HighlighterCore, LanguageInput, LanguageRegistration, RegexEngine, SpecialLanguage, StringLiteralUnion, ThemeRegistrationAny, TokensResult } from "shiki";
3
- //#region ../node_modules/.pnpm/@types+unist@3.0.3/node_modules/@types/unist/index.d.ts
4
3
  // ## Interfaces
5
4
  /**
6
5
  * Info associated with nodes by the ecosystem.
@@ -81,8 +80,6 @@ interface Node$1 {
81
80
  */
82
81
  position?: Position | undefined;
83
82
  }
84
- //#endregion
85
- //#region ../node_modules/.pnpm/@types+hast@3.0.5/node_modules/@types/hast/index.d.ts
86
83
  // ## Interfaces
87
84
  /**
88
85
  * Info associated with hast nodes by the ecosystem.
@@ -947,8 +944,6 @@ interface Text extends Literal {
947
944
  * Info associated with hast texts by the ecosystem.
948
945
  */
949
946
  interface TextData extends Data {}
950
- //#endregion
951
- //#region src/lib/types.d.ts
952
947
  /**
953
948
  * HTML Element, use to type `node` from react-markdown
954
949
  */
@@ -1070,6 +1065,21 @@ interface ReactShikiOptions {
1070
1065
  * });
1071
1066
  */
1072
1067
  highlighter?: Highlighter | HighlighterCore;
1068
+ /**
1069
+ * Keeps highlighted output across unmounts, so remounting unchanged code
1070
+ * paints on its first render without highlighting again. Pass a `Map` you
1071
+ * own, shared by every highlighter that should reuse results, and
1072
+ * `clear()` it when the content it holds goes away. Output is stored on
1073
+ * unmount, so partial code from streaming never lands in it.
1074
+ *
1075
+ * @example
1076
+ * const cache = new Map();
1077
+ *
1078
+ * <ShikiHighlighter cache={cache} language="ts" theme="nord">
1079
+ * {code}
1080
+ * </ShikiHighlighter>
1081
+ */
1082
+ cache?: HighlightCache;
1073
1083
  /**
1074
1084
  * Whether to show line numbers
1075
1085
  * @default false
@@ -1099,6 +1109,10 @@ interface HighlightResultMap {
1099
1109
  html: string;
1100
1110
  tokens: TokensResult;
1101
1111
  }
1112
+ /**
1113
+ * Where highlighted output is kept between mounts; a `Map` satisfies it.
1114
+ */
1115
+ type HighlightCache = Pick<Map<string, HighlightResultMap[OutputFormat]>, 'get' | 'set'>;
1102
1116
  type HighlightResult<F extends OutputFormat = 'react'> = HighlightResultMap[F] | null;
1103
1117
  /**
1104
1118
  * Per-call options shape: every highlighter option except `outputFormat`,
@@ -1113,28 +1127,6 @@ type HighlighterOptionsFor<F extends OutputFormat> = Omit<HighlighterOptions, 'o
1113
1127
  outputFormat?: F;
1114
1128
  };
1115
1129
  type UseShikiHighlighter = <F extends OutputFormat = 'react'>(code: string, lang: Language, themeInput: Theme | Themes, options?: HighlighterOptionsFor<F>) => HighlightResult<F>;
1116
- //#endregion
1117
- //#region src/lib/plugins.d.ts
1118
- /**
1119
- * Rehype plugin to add an 'inline' property to <code> elements
1120
- * Sets 'inline' property to true if the <code> is not within a <pre> tag
1121
- *
1122
- * Pass this plugin to the `rehypePlugins` prop of react-markdown
1123
- * You can then access `inline` as a prop in components passed to react-markdown
1124
- *
1125
- * @example
1126
- * <ReactMarkdown rehypePlugins={[rehypeInlineCodeProperty]} />
1127
- */
1128
- declare function rehypeInlineCodeProperty(): (tree: Root) => undefined;
1129
- /**
1130
- * Function to determine if code is inline based on the presence of line breaks
1131
- *
1132
- * @example
1133
- * const isInline = node && isInlineCode(node: Element)
1134
- */
1135
- declare const isInlineCode: (node: Element) => boolean;
1136
- //#endregion
1137
- //#region src/lib/component.d.ts
1138
1130
  /**
1139
1131
  * Props for the ShikiHighlighter component
1140
1132
  */
@@ -1195,6 +1187,22 @@ interface ShikiHighlighterProps extends HighlighterOptions {
1195
1187
  */
1196
1188
  as?: React.ElementType;
1197
1189
  }
1198
- //#endregion
1199
- export { EngineName as a, HighlighterOptionsFor as c, PreloadLanguage as d, Theme as f, Element as i, Language as l, UseShikiHighlighter as m, isInlineCode as n, HighlightResult as o, Themes as p, rehypeInlineCodeProperty as r, HighlighterOptions as s, ShikiHighlighterProps as t, OutputFormat as u };
1200
- //# sourceMappingURL=component-DRPT3D6t.d.mts.map
1190
+ /**
1191
+ * Rehype plugin to add an 'inline' property to <code> elements
1192
+ * Sets 'inline' property to true if the <code> is not within a <pre> tag
1193
+ *
1194
+ * Pass this plugin to the `rehypePlugins` prop of react-markdown
1195
+ * You can then access `inline` as a prop in components passed to react-markdown
1196
+ *
1197
+ * @example
1198
+ * <ReactMarkdown rehypePlugins={[rehypeInlineCodeProperty]} />
1199
+ */
1200
+ declare function rehypeInlineCodeProperty(): (tree: Root) => undefined;
1201
+ /**
1202
+ * Function to determine if code is inline based on the presence of line breaks
1203
+ *
1204
+ * @example
1205
+ * const isInline = node && isInlineCode(node: Element)
1206
+ */
1207
+ declare const isInlineCode: (node: Element) => boolean;
1208
+ export { EngineName as a, HighlighterOptions as c, OutputFormat as d, PreloadLanguage as f, UseShikiHighlighter as h, Element as i, HighlighterOptionsFor as l, Themes as m, rehypeInlineCodeProperty as n, HighlightCache as o, Theme as p, ShikiHighlighterProps as r, HighlightResult as s, isInlineCode as t, Language as u };
package/dist/web.d.mts CHANGED
@@ -1,7 +1,6 @@
1
- import { a as EngineName, c as HighlighterOptionsFor, d as PreloadLanguage, f as Theme, i as Element, l as Language, m as UseShikiHighlighter, n as isInlineCode, o as HighlightResult, p as Themes, r as rehypeInlineCodeProperty, s as HighlighterOptions, t as ShikiHighlighterProps, u as OutputFormat } from "./component-DRPT3D6t.mjs";
1
+ import { a as EngineName, c as HighlighterOptions, d as OutputFormat, f as PreloadLanguage, h as UseShikiHighlighter, i as Element, l as HighlighterOptionsFor, m as Themes, n as rehypeInlineCodeProperty, o as HighlightCache, p as Theme, r as ShikiHighlighterProps, s as HighlightResult, t as isInlineCode, u as Language } from "./plugins-DdjjMnbY.mjs";
2
2
  import { createJavaScriptRawEngine, createJavaScriptRegexEngine } from "shiki/engine/javascript";
3
3
  import { LanguageRegistration, TokensResult } from "shiki/bundle/web";
4
- //#region src/web.d.ts
5
4
  /**
6
5
  * Highlight code with shiki (web bundle)
7
6
  *
@@ -31,6 +30,4 @@ export declare const useShikiHighlighter: UseShikiHighlighter;
31
30
  * Includes web-focused languages for balanced size and functionality.
32
31
  */
33
32
  export declare const ShikiHighlighter: import("react").ForwardRefExoticComponent<ShikiHighlighterProps & import("react").RefAttributes<HTMLElement>>;
34
- //#endregion
35
- export { type Element, type EngineName, type HighlightResult, type HighlighterOptions, type HighlighterOptionsFor, type Language, type LanguageRegistration, type OutputFormat, type PreloadLanguage, ShikiHighlighter as default, type ShikiHighlighterProps, type Theme, type Themes, type TokensResult, type UseShikiHighlighter, createJavaScriptRawEngine, createJavaScriptRegexEngine, isInlineCode, rehypeInlineCodeProperty };
36
- //# sourceMappingURL=web.d.mts.map
33
+ export { type Element, type EngineName, type HighlightCache, type HighlightResult, type HighlighterOptions, type HighlighterOptionsFor, type Language, type LanguageRegistration, type OutputFormat, type PreloadLanguage, ShikiHighlighter as default, type ShikiHighlighterProps, type Theme, type Themes, type TokensResult, type UseShikiHighlighter, createJavaScriptRawEngine, createJavaScriptRegexEngine, isInlineCode, rehypeInlineCodeProperty };
package/dist/web.mjs CHANGED
@@ -1,16 +1,15 @@
1
- import { i as useHighlight, n as isInlineCode, r as rehypeInlineCodeProperty, t as createShikiHighlighterComponent } from "./component-Cf8Topxy.mjs";
2
- import { t as bundledHighlighterFactory } from "./factory-DySLHBEo.mjs";
1
+ import { i as useHighlight, n as rehypeInlineCodeProperty, r as createShikiHighlighterComponent, t as isInlineCode } from "./plugins-C0Uf67_P.mjs";
2
+ import { t as bundledHighlighterFactory } from "./factory-Ds5IsDw-.mjs";
3
3
  import { createJavaScriptRawEngine, createJavaScriptRegexEngine } from "shiki/engine/javascript";
4
4
  import { bundledLanguages, getSingletonHighlighter } from "shiki/bundle/web";
5
- //#region src/bundles/web.ts
5
+ // src/bundles/web.ts
6
6
  /**
7
7
  * Highlighter factory for the web Shiki bundle: web-focused languages
8
8
  * (HTML, CSS, JS, TS, JSON, Markdown, Vue, JSX, Svelte, etc.) at a
9
9
  * fraction of the full bundle's size.
10
10
  */
11
11
  const createWebHighlighter = bundledHighlighterFactory(getSingletonHighlighter, bundledLanguages);
12
- //#endregion
13
- //#region src/web.ts
12
+ // src/web.ts
14
13
  /**
15
14
  * Highlight code with shiki (web bundle)
16
15
  *
@@ -40,7 +39,6 @@ const useShikiHighlighter = (code, lang, themeInput, options = {}) => useHighlig
40
39
  * Includes web-focused languages for balanced size and functionality.
41
40
  */
42
41
  const ShikiHighlighter = /*#__PURE__*/ createShikiHighlighterComponent(useShikiHighlighter);
43
- //#endregion
44
42
  export { ShikiHighlighter, ShikiHighlighter as default, createJavaScriptRawEngine, createJavaScriptRegexEngine, isInlineCode, rehypeInlineCodeProperty, useShikiHighlighter };
45
43
 
46
44
  //# sourceMappingURL=web.mjs.map
package/dist/web.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"web.mjs","names":[],"sources":["../src/bundles/web.ts","../src/web.ts"],"sourcesContent":["import {\n getSingletonHighlighter,\n bundledLanguages,\n} from 'shiki/bundle/web';\nimport { bundledHighlighterFactory } from './factory';\n\n/**\n * Highlighter factory for the web Shiki bundle: web-focused languages\n * (HTML, CSS, JS, TS, JSON, Markdown, Vue, JSX, Svelte, etc.) at a\n * fraction of the full bundle's size.\n */\nexport const createWebHighlighter = bundledHighlighterFactory(\n getSingletonHighlighter,\n bundledLanguages\n);\n","import { useHighlight } 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 PreloadLanguage,\n Theme,\n Themes,\n Element,\n EngineName,\n HighlighterOptions,\n HighlighterOptionsFor,\n HighlightResult,\n OutputFormat,\n} from './lib/types';\n\nexport {\n createJavaScriptRegexEngine,\n createJavaScriptRawEngine,\n} from 'shiki/engine/javascript';\n\nexport type {\n LanguageRegistration,\n TokensResult,\n} from 'shiki/bundle/web';\n\n/**\n * Highlight code with shiki (web bundle)\n *\n * @param code - Code to highlight\n * @param lang - Language (bundled or custom)\n * @param theme - Theme (bundled, multi-theme, or custom)\n * @param options - react-shiki options + shiki options\n * @returns Highlighted code as React elements (default), HTML string, or Shiki tokens, based on `outputFormat`\n *\n * @example\n * ```tsx\n * const highlighted = useShikiHighlighter(\n * 'const x = 1;',\n * 'typescript',\n * {\n * light: 'github-light',\n * dark: 'github-dark'\n * }\n * );\n * ```\n *\n * Web bundle (~3.8MB minified, 695KB gzipped). For other bundles: `react-shiki` or `react-shiki/core`\n */\nexport const useShikiHighlighter: UseShikiHighlighter = (\n code,\n lang,\n themeInput,\n options = {}\n) => useHighlight(code, lang, themeInput, options, createWebHighlighter);\n\n/**\n * ShikiHighlighter component using the web bundle.\n * Includes web-focused languages for balanced size and functionality.\n */\nexport const ShikiHighlighter =\n /*#__PURE__*/ createShikiHighlighterComponent(useShikiHighlighter);\nexport default ShikiHighlighter;\n"],"mappings":";;;;;;;;;;AAWA,MAAa,uBAAuB,0BAClC,yBACA,gBACF;;;;;;;;;;;;;;;;;;;;;;;;;;AC8CA,MAAa,uBACX,MACA,MACA,YACA,UAAU,CAAC,MACR,aAAa,MAAM,MAAM,YAAY,SAAS,oBAAoB;;;;;AAMvE,MAAa,mBACG,8CAAgC,mBAAmB"}
1
+ {"version":3,"file":"web.mjs","names":[],"sources":["../src/bundles/web.ts","../src/web.ts"],"sourcesContent":["import {\n getSingletonHighlighter,\n bundledLanguages,\n} from 'shiki/bundle/web';\nimport { bundledHighlighterFactory } from './factory';\n\n/**\n * Highlighter factory for the web Shiki bundle: web-focused languages\n * (HTML, CSS, JS, TS, JSON, Markdown, Vue, JSX, Svelte, etc.) at a\n * fraction of the full bundle's size.\n */\nexport const createWebHighlighter = bundledHighlighterFactory(\n getSingletonHighlighter,\n bundledLanguages\n);\n","import { useHighlight } from './lib/hook';\nimport { createWebHighlighter } from './bundles/web';\nimport type { UseShikiHighlighter } from './lib/types';\nimport { createShikiHighlighterComponent } from './lib/component';\n// oxlint-disable-next-line unicorn/prefer-export-from -- `export type … from` here makes tsdown fold the core entry's declarations into the shared .d.mts chunk\nimport type { ShikiHighlighterProps } from './lib/component';\n\nexport { isInlineCode, rehypeInlineCodeProperty } from './lib/plugins';\n\nexport type { ShikiHighlighterProps };\n\nexport type {\n UseShikiHighlighter,\n Language,\n PreloadLanguage,\n Theme,\n Themes,\n Element,\n EngineName,\n HighlighterOptions,\n HighlighterOptionsFor,\n HighlightCache,\n HighlightResult,\n OutputFormat,\n} from './lib/types';\n\nexport {\n createJavaScriptRegexEngine,\n createJavaScriptRawEngine,\n} from 'shiki/engine/javascript';\n\nexport type {\n LanguageRegistration,\n TokensResult,\n} from 'shiki/bundle/web';\n\n/**\n * Highlight code with shiki (web bundle)\n *\n * @param code - Code to highlight\n * @param lang - Language (bundled or custom)\n * @param theme - Theme (bundled, multi-theme, or custom)\n * @param options - react-shiki options + shiki options\n * @returns Highlighted code as React elements (default), HTML string, or Shiki tokens, based on `outputFormat`\n *\n * @example\n * ```tsx\n * const highlighted = useShikiHighlighter(\n * 'const x = 1;',\n * 'typescript',\n * {\n * light: 'github-light',\n * dark: 'github-dark'\n * }\n * );\n * ```\n *\n * Web bundle (~3.8MB minified, 695KB gzipped). For other bundles: `react-shiki` or `react-shiki/core`\n */\nexport const useShikiHighlighter: UseShikiHighlighter = (\n code,\n lang,\n themeInput,\n options = {}\n) => useHighlight(code, lang, themeInput, options, createWebHighlighter);\n\n/**\n * ShikiHighlighter component using the web bundle.\n * Includes web-focused languages for balanced size and functionality.\n */\nexport const ShikiHighlighter =\n // oxlint-disable-next-line eslint/no-inline-comments -- the #__PURE__ annotation lets bundlers drop the component when only the hook is used\n /*#__PURE__*/ createShikiHighlighterComponent(useShikiHighlighter);\n\nexport default ShikiHighlighter;\n"],"mappings":";;;;;;;;;;AAWA,MAAa,uBAAuB,0BAClC,yBACA,gBACF;;;;;;;;;;;;;;;;;;;;;;;;;AC6CA,MAAa,uBACX,MACA,MACA,YACA,UAAU,CAAC,MACR,aAAa,MAAM,MAAM,YAAY,SAAS,oBAAoB;;;;;AAMvE,MAAa,mBAEG,8CAAgC,mBAAmB"}
package/package.json CHANGED
@@ -1,7 +1,17 @@
1
1
  {
2
2
  "name": "react-shiki",
3
+ "version": "0.11.3",
3
4
  "description": "Syntax highlighter component for react using shiki",
4
- "version": "0.11.2",
5
+ "keywords": [
6
+ "code",
7
+ "highlighter",
8
+ "react",
9
+ "react-syntax-highlighter",
10
+ "shiki",
11
+ "syntax",
12
+ "syntax-highlighter"
13
+ ],
14
+ "homepage": "https://react-shiki.vercel.app/",
5
15
  "license": "MIT",
6
16
  "author": {
7
17
  "name": "Bassim Shahidy",
@@ -10,33 +20,19 @@
10
20
  },
11
21
  "repository": {
12
22
  "type": "git",
13
- "directory": "package",
14
- "url": "git+https://github.com/AVGVSTVS96/react-shiki.git"
23
+ "url": "git+https://github.com/AVGVSTVS96/react-shiki.git",
24
+ "directory": "packages/react-shiki"
15
25
  },
16
- "homepage": "https://react-shiki.vercel.app/",
17
- "keywords": [
18
- "react",
19
- "shiki",
20
- "code",
21
- "syntax",
22
- "highlighter",
23
- "syntax-highlighter",
24
- "react-syntax-highlighter"
25
- ],
26
- "type": "module",
27
- "main": "./dist/index.mjs",
28
- "types": "./dist/index.d.mts",
29
26
  "files": [
30
27
  "dist",
31
28
  "src/styles/css.d.ts"
32
29
  ],
30
+ "type": "module",
33
31
  "sideEffects": [
34
32
  "**/*.css"
35
33
  ],
36
- "publishConfig": {
37
- "access": "public",
38
- "provenance": true
39
- },
34
+ "main": "./dist/index.mjs",
35
+ "types": "./dist/index.d.mts",
40
36
  "exports": {
41
37
  ".": {
42
38
  "types": "./dist/index.d.mts",
@@ -55,19 +51,9 @@
55
51
  "default": "./dist/style.css"
56
52
  }
57
53
  },
58
- "peerDependencies": {
59
- "@types/react": ">=16.14.0",
60
- "@types/react-dom": ">=16.14.0",
61
- "react": ">=16.14.0",
62
- "react-dom": ">=16.14.0"
63
- },
64
- "peerDependenciesMeta": {
65
- "@types/react": {
66
- "optional": true
67
- },
68
- "@types/react-dom": {
69
- "optional": true
70
- }
54
+ "publishConfig": {
55
+ "access": "public",
56
+ "provenance": true
71
57
  },
72
58
  "dependencies": {
73
59
  "clsx": "^2.1.1",
@@ -78,34 +64,44 @@
78
64
  },
79
65
  "devDependencies": {
80
66
  "@arethetypeswrong/core": "^0.18.5",
67
+ "@testing-library/dom": "^10.4.2",
81
68
  "@testing-library/jest-dom": "^7.0.1",
82
69
  "@testing-library/react": "^16.3.3",
83
- "@tsdown/css": "^0.23.0",
70
+ "@tsdown/css": "^0.23.7",
84
71
  "@types/hast": "^3.0.5",
85
- "@types/jest": "^30.0.0",
86
72
  "@types/node": "25.9.9",
87
73
  "@types/react": "^19.3.0",
88
- "@vitejs/plugin-react": "^6.1.2",
89
- "html-react-parser": "^6.1.8",
90
- "jsdom": "^30.1.2",
91
- "publint": "^0.3.25",
92
- "rolldown": "^1.2.12",
93
- "tsdown": "^0.23.0",
94
- "vitest": "^5.0.3",
95
- "react": "^19.3.0",
96
- "react-dom": "^19.3.0",
97
74
  "@types/react-dom": "^19.3.0",
98
- "@testing-library/dom": "^10.4.2"
75
+ "@vitejs/plugin-react": "^6.2.1",
76
+ "html-react-parser": "^6.1.9",
77
+ "jsdom": "^31.0.0",
78
+ "publint": "^0.3.25",
79
+ "react": "^19.3.1",
80
+ "react-dom": "^19.3.1",
81
+ "rolldown": "^1.3.0",
82
+ "tsdown": "^0.23.7",
83
+ "vitest": "^5.1.0"
84
+ },
85
+ "peerDependencies": {
86
+ "@types/react": ">=16.14.0",
87
+ "@types/react-dom": ">=16.14.0",
88
+ "react": ">=16.14.0",
89
+ "react-dom": ">=16.14.0"
90
+ },
91
+ "peerDependenciesMeta": {
92
+ "@types/react": {
93
+ "optional": true
94
+ },
95
+ "@types/react-dom": {
96
+ "optional": true
97
+ }
99
98
  },
100
99
  "scripts": {
101
100
  "dev": "tsdown --watch",
102
101
  "build": "tsdown",
103
- "check:package": "node scripts/check-package.mjs",
104
- "test": "vitest",
102
+ "check:package": "node scripts/check-package.ts",
103
+ "test": "vitest run",
105
104
  "bench": "vitest bench --typecheck.enabled=false",
106
- "lint": "biome lint .",
107
- "lint:fix": "biome lint --write .",
108
- "format": "biome format --write .",
109
- "check": "tsc && biome check ."
105
+ "typecheck": "tsc"
110
106
  }
111
107
  }
@@ -1 +0,0 @@
1
- {"version":3,"file":"component-Cf8Topxy.mjs","names":["componentCss","featuresCss"],"sources":["../src/lib/utils.ts","../src/styles/component.css?inline","../src/styles/features.css?inline","../src/lib/styles.ts","../src/lib/language.ts","../src/lib/theme.ts","../src/lib/engine.ts","../src/lib/transformers.ts","../src/lib/options.ts","../src/lib/hook.ts","../src/lib/plugins.ts","../src/lib/component.tsx"],"sourcesContent":["import { useRef } from 'react';\nimport { dequal } from 'dequal';\n\nimport type { TimeoutState } from './types';\n\n/**\n * Returns a referentially stable version of `value` that only updates when content changes.\n * Uses reference equality as a fast path and deep comparison to prevent unnecessary updates.\n */\nexport const useStableValue = <T>(value: T): T => {\n const ref = useRef(value);\n\n if (value !== ref.current && !dequal(value, ref.current)) {\n ref.current = value;\n }\n\n return ref.current;\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: { current: TimeoutState },\n throttleMs: number\n) => {\n clearTimeout(timeoutControl.current.timeoutId);\n\n const delay = Math.max(\n 0,\n timeoutControl.current.nextAllowedTime - Date.now()\n );\n timeoutControl.current.timeoutId = setTimeout(() => {\n performHighlight();\n timeoutControl.current.nextAllowedTime = Date.now() + throttleMs;\n }, delay);\n};\n","/* Unlayered :where() defaults: immune to layered resets, overridden by any user rule. */\n:where(.rs-root) {\n position: relative;\n}\n\n:where(.rs-root.rs-default-styles pre) {\n overflow: auto;\n border-radius: 0.5rem;\n padding-inline: 1.5rem;\n padding-block: 1.25rem;\n}\n\n:where(.rs-root .rs-language-label) {\n position: absolute;\n z-index: 1;\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",":where(.rs-has-line-numbers) {\n counter-reset: line-number calc(var(--line-start, 1) - 1);\n}\n\n:where(.rs-has-line-numbers .rs-line-number)::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(--rs-line-numbers-width, 2ch);\n padding-left: var(--rs-line-numbers-padding-left, 0ch);\n padding-right: var(--rs-line-numbers-padding-right, 2ch);\n color: var(--rs-line-numbers-foreground, rgba(107, 114, 128, 0.5));\n font-size: var(--rs-line-numbers-font-size, inherit);\n font-weight: var(--rs-line-numbers-font-weight, inherit);\n line-height: var(--rs-line-numbers-line-height, inherit);\n font-family: var(--rs-line-numbers-font-family, inherit);\n opacity: var(--rs-line-numbers-opacity, 1);\n user-select: none;\n pointer-events: none;\n}\n\n:where(.rs-has-highlighted-lines) {\n display: block;\n width: fit-content;\n min-width: 100%;\n}\n\n:where(.rs-highlighted-line) {\n --_rs-hl-background: var(\n --rs-highlighted-line-background,\n color-mix(in srgb, currentColor 10%, transparent)\n );\n display: inline-block;\n width: 100%;\n min-height: 1lh;\n background-color: var(--_rs-hl-background);\n}\n\n/* Full bleed: the 100vw outset paints into the pre's padding, clipped by its overflow. */\n:where(.rs-default-styles .rs-highlighted-line) {\n background-color: transparent;\n border-image:\n linear-gradient(var(--_rs-hl-background) 0 0) fill 0 / 0 / 0 100vw;\n}\n\n:where(.rs-has-line-numbers .rs-highlighted-line)::before {\n color: var(\n --rs-highlighted-line-number-foreground,\n color-mix(in srgb, currentColor 65%, transparent)\n );\n}\n","import componentCss from '../styles/component.css?inline';\nimport featuresCss from '../styles/features.css?inline';\nimport * as React from 'react';\n// Side-effect import so tsdown emits dist/style.css for the ./css export;\n// stripped from the built JS, guarded by check:package\nimport '../styles/index.css';\n\nconst injected = new Set<string>();\nconst useStyleInsertionEffect =\n React.useInsertionEffect ?? React.useLayoutEffect;\n\nconst inject = (css: string, key: string) => {\n if (injected.has(key) || typeof document === 'undefined') return;\n injected.add(key);\n try {\n // Not governed by CSP style-src in current browsers, unlike the <style> fallback\n const sheet = new CSSStyleSheet();\n sheet.replaceSync(css);\n document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];\n } catch {\n const style = document.createElement('style');\n style.dataset.reactShiki = key;\n style.textContent = css;\n document.head.append(style);\n }\n};\n\n/** Installs component styles before layout without triggering a render. */\nexport const useComponentStyles = () => {\n useStyleInsertionEffect(() => {\n inject(componentCss, 'component');\n }, []);\n};\n\n/** Installs line-number/highlight styles only when those options are used. */\nexport const useFeatureStyles = (enabled: boolean) => {\n useStyleInsertionEffect(() => {\n if (enabled) inject(featuresCss, 'features');\n }, [enabled]);\n};\n","import type { Language, PreloadLanguage } from './types';\nimport { guessEmbeddedLanguages, isSpecialLang } from 'shiki/core';\nimport type {\n DynamicImportLanguageRegistration,\n Highlighter,\n HighlighterCore,\n LanguageInput,\n LanguageRegistration,\n} from 'shiki';\n\nexport const FALLBACK_LANGUAGE = 'plaintext';\n\nconst toArray = <T>(value?: T | T[]): T[] =>\n value == null ? [] : Array.isArray(value) ? value : [value];\n\n/** Deferred `LanguageInput` forms (getter, promise, module, array) that shiki resolves itself */\ntype DynamicLanguage = Exclude<LanguageInput, LanguageRegistration>;\n\nconst isDynamicLanguage = (\n lang: PreloadLanguage\n): lang is DynamicLanguage =>\n typeof lang === 'function' ||\n lang instanceof Promise ||\n Array.isArray(lang) ||\n (typeof lang === 'object' && lang != null && 'default' in lang);\n\nconst isRegistration = (\n lang: PreloadLanguage\n): lang is LanguageRegistration =>\n typeof lang === 'object' && lang != null && !isDynamicLanguage(lang);\n\n/** Dedupe identity: strings and registrations by value, dynamic inputs by reference */\nconst languageKey = (lang: PreloadLanguage): string | object | null => {\n if (lang == null) return null;\n if (typeof lang === 'string') return `s:${lang}`;\n if (isDynamicLanguage(lang)) return lang;\n return `o:${lang.name}::${lang.scopeName}`;\n};\n\nconst dedupeLanguages = (langs: PreloadLanguage[]): PreloadLanguage[] => {\n const seen = new Set<string | object>();\n return langs.filter((lang) => {\n const key = languageKey(lang);\n if (key == null || seen.has(key)) return false;\n seen.add(key);\n return true;\n });\n};\n\n/**\n * A registration matches a query by name, scopeName (full or its last\n * segment, e.g. \"source.tsx\" -> \"tsx\"), aliases, or fileTypes.\n */\nconst registrationMatches = (\n candidate: LanguageRegistration,\n matches: (str: string | undefined) => boolean\n): boolean =>\n matches(candidate.name) ||\n matches(candidate.scopeName) ||\n matches(candidate.scopeName?.split('.').pop()) ||\n !!candidate.aliases?.some(matches) ||\n !!candidate.fileTypes?.some(matches);\n\ntype LanguageResult = {\n languageId: string;\n langsToLoad: PreloadLanguage[];\n};\n\n/**\n * Resolves the language prop to a shiki language id plus everything the\n * factory should load (primary, preloaded, and custom grammars, deduped).\n */\nexport const resolveLanguage = (\n lang: Language,\n customLanguages?: LanguageRegistration | LanguageRegistration[],\n langAliases?: Record<string, string>,\n preloadLanguages?: PreloadLanguage | PreloadLanguage[]\n): LanguageResult => {\n const customLangs = toArray(customLanguages);\n const preloadLangs = toArray(preloadLanguages);\n\n const result = (\n languageId: string,\n primary?: Language\n ): LanguageResult => ({\n languageId,\n langsToLoad: dedupeLanguages([\n primary,\n ...preloadLangs,\n ...customLangs,\n ]),\n });\n\n if (lang == null || (typeof lang === 'string' && !lang.trim())) {\n return result(FALLBACK_LANGUAGE);\n }\n\n if (typeof lang === 'object') {\n return result(lang.name, lang);\n }\n\n const normalized = lang.trim();\n const query = normalized.toLowerCase();\n const matches = (str: string | undefined): boolean =>\n str?.toLowerCase() === query;\n\n // Custom registrations (from both customLanguages and preloadLanguages)\n const customMatch = [...customLangs, ...preloadLangs]\n .filter(isRegistration)\n .find((candidate) => registrationMatches(candidate, matches));\n if (customMatch) {\n return result(customMatch.name || normalized, customMatch);\n }\n\n const alias = Object.entries(langAliases ?? {}).find(([name]) =>\n matches(name)\n )?.[1];\n if (alias) {\n return result(alias, alias);\n }\n\n // Any other string passes through to the factory\n return result(normalized, normalized);\n};\n\n/**\n * Used in factories to check if language is supported.\n * Objects are validated as grammar registrations (name + scopeName);\n * dynamic imports pass through for shiki to resolve.\n */\nexport const isLoadableLanguage = <T extends string>(\n lang: PreloadLanguage,\n bundledLanguages: Record<T, DynamicImportLanguageRegistration>\n): lang is NonNullable<PreloadLanguage> => {\n if (lang == null) return false;\n if (typeof lang === 'string') return lang in bundledLanguages;\n if (isDynamicLanguage(lang)) return true;\n return (\n typeof lang.name === 'string' && typeof lang.scopeName === 'string'\n );\n};\n\n/**\n * Used in hook to resolve loaded language for highlighting.\n * Falls back to \"plaintext\" if not supported.\n */\nexport const resolveLoadedLanguage = (\n languageId: string,\n loadedLanguages: string[]\n): string =>\n isSpecialLang(languageId) || loadedLanguages.includes(languageId)\n ? languageId\n : FALLBACK_LANGUAGE;\n\nexport const getEmbeddedLanguages = (\n code: string,\n languageId: string,\n highlighter: Highlighter | HighlighterCore\n): LanguageInput[] => {\n const bundled: Record<string, LanguageInput> =\n highlighter.getBundledLanguages();\n return guessEmbeddedLanguages(code, languageId).flatMap(\n (language) => bundled[language] ?? []\n );\n};\n","import type { Theme, Themes } from './types';\nimport type { ThemeRegistrationAny } from 'shiki/core';\n\nexport const DEFAULT_THEMES: Themes = {\n light: 'github-light',\n dark: 'github-dark',\n};\n\nexport type ResolvedTheme =\n | { isMulti: true; themes: Themes; themesToLoad: Theme[] }\n | { isMulti: false; theme: Theme; themesToLoad: Theme[] };\n\nconst isTextmateTheme = (\n value: unknown\n): value is ThemeRegistrationAny => {\n if (typeof value !== 'object' || value === null) return false;\n const v = value as Partial<ThemeRegistrationAny> & {\n settings?: unknown;\n };\n const hasTokens =\n Array.isArray(v.tokenColors) || Array.isArray(v.settings);\n const hasIdentity =\n typeof v.name === 'string' || typeof v.type === 'string';\n return hasTokens && hasIdentity;\n};\n\nexport function resolveTheme(themeInput: Theme | Themes): ResolvedTheme {\n if (\n typeof themeInput !== 'object' ||\n themeInput === null ||\n isTextmateTheme(themeInput)\n ) {\n return {\n isMulti: false,\n theme: themeInput,\n themesToLoad: [themeInput],\n };\n }\n\n const validEntries = Object.entries(themeInput).filter(\n ([key, value]) =>\n key.trim() !== '' &&\n ((typeof value === 'string' && value.trim() !== '') ||\n isTextmateTheme(value))\n );\n\n if (validEntries.length === 0) {\n console.warn(\n '[react-shiki] invalid multi-theme config, falling back to defaults'\n );\n return {\n isMulti: true,\n themes: DEFAULT_THEMES,\n themesToLoad: Object.values(DEFAULT_THEMES),\n };\n }\n\n if (validEntries.length !== Object.keys(themeInput).length) {\n console.warn(\n '[react-shiki] multi-theme config contained invalid entries; they were dropped'\n );\n }\n\n const themes = Object.fromEntries(validEntries) as Themes;\n return {\n isMulti: true,\n themes,\n themesToLoad: validEntries.map(([, value]) => value as Theme),\n };\n}\n","import type { Awaitable, RegexEngine } from 'shiki/core';\nimport type { EngineName } from './types';\n\nconst engines = new Map<EngineName, Promise<RegexEngine>>();\n\n// Dynamic imports keep unused engines out of the loaded graph: naming an\n// engine loads only that engine, and 'javascript' never fetches the wasm.\nconst loaders: Record<EngineName, () => Promise<RegexEngine>> = {\n javascript: () =>\n import('shiki/engine/javascript').then((m) =>\n m.createJavaScriptRegexEngine()\n ),\n oniguruma: () =>\n import('shiki/engine/oniguruma').then((m) =>\n m.createOnigurumaEngine(import('shiki/wasm'))\n ),\n};\n\n/**\n * Resolves a named engine to a lazily created, cached instance.\n * Engine instances and undefined pass through untouched.\n *\n * The cache makes named engines referentially stable across renders and\n * call sites, unlike inline `createJavaScriptRegexEngine()` calls.\n */\nexport const resolveEngine = (\n engine: EngineName | Awaitable<RegexEngine> | undefined\n): Awaitable<RegexEngine> | undefined => {\n if (typeof engine !== 'string') return engine;\n\n const load = loaders[engine];\n if (!load) {\n throw new Error(\n `[react-shiki] unknown engine '${engine}', expected 'javascript' or 'oniguruma'`\n );\n }\n\n const cached = engines.get(engine);\n if (cached) return cached;\n\n const instance = load();\n // Evict rejected loads (e.g. transient wasm fetch failure) so the\n // next highlight retries instead of reusing the cached rejection.\n instance.catch(() => engines.delete(engine));\n engines.set(engine, instance);\n return instance;\n};\n","import type { ShikiTransformer } from 'shiki/core';\n\nconst toLineNumberSet = (lineNumbers: readonly number[]) =>\n new Set(lineNumbers.filter(Number.isInteger));\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, 'rs-has-line-numbers');\n if (startLine !== 1) {\n const existing = node.properties?.style as string | undefined;\n node.properties = {\n ...node.properties,\n style: [existing, `--line-start: ${startLine}`]\n .filter(Boolean)\n .join('; '),\n };\n }\n },\n line(node) {\n this.addClassToHast(node, 'rs-line-number');\n return node;\n },\n };\n}\n\n/**\n * Creates a transformer that marks selected displayed line numbers.\n * @param lineNumbers - Displayed line numbers to highlight\n * @param startLine - The starting line number (defaults to 1)\n */\nexport function highlightedLinesTransformer(\n lineNumbers: readonly number[],\n startLine = 1\n): ShikiTransformer {\n const highlightedLines = toLineNumberSet(lineNumbers);\n\n return {\n name: 'react-shiki:highlight-lines',\n code(node) {\n this.addClassToHast(node, 'rs-has-highlighted-lines');\n },\n line(node, line) {\n const displayedLine = startLine + line - 1;\n\n if (highlightedLines.has(displayedLine)) {\n this.addClassToHast(node, 'rs-highlighted-line');\n }\n\n return node;\n },\n };\n}\n","import type {\n BundledTheme,\n CodeOptionsMultipleThemes,\n CodeOptionsSingleTheme,\n CodeToHastOptions,\n} from 'shiki';\n\nimport type {\n HighlighterOptions,\n HighlighterOptionsFor,\n OutputFormat,\n} from './types';\nimport type { ResolvedTheme } from './theme';\nimport {\n highlightedLinesTransformer,\n lineNumbersTransformer,\n} from './transformers';\n\nconst buildThemeOptions = (\n resolvedTheme: ResolvedTheme,\n defaultColor: HighlighterOptions['defaultColor'],\n cssVariablePrefix: HighlighterOptions['cssVariablePrefix']\n):\n | CodeOptionsMultipleThemes<BundledTheme>\n | CodeOptionsSingleTheme<BundledTheme> => {\n if (resolvedTheme.isMulti) {\n return {\n themes: resolvedTheme.themes,\n defaultColor,\n cssVariablePrefix,\n };\n }\n\n return {\n theme: resolvedTheme.theme,\n };\n};\n\nexport const buildShikiOptions = (\n languageId: string,\n resolvedTheme: ResolvedTheme,\n options: HighlighterOptionsFor<OutputFormat>\n): CodeToHastOptions => {\n const {\n delay,\n customLanguages,\n preloadLanguages,\n outputFormat,\n highlighter,\n langAlias,\n engine,\n defaultColor,\n cssVariablePrefix,\n showLineNumbers,\n startingLineNumber,\n highlightLineNumbers,\n transformers: userTransformers,\n ...shikiPassthrough\n } = options;\n\n const transformers = [...(userTransformers || [])];\n if (showLineNumbers) {\n transformers.push(lineNumbersTransformer(startingLineNumber));\n }\n if (highlightLineNumbers?.length) {\n transformers.push(\n highlightedLinesTransformer(\n highlightLineNumbers,\n startingLineNumber\n )\n );\n }\n\n return {\n lang: languageId,\n ...buildThemeOptions(resolvedTheme, defaultColor, cssVariablePrefix),\n ...shikiPassthrough,\n transformers,\n };\n};\n","import { useEffect, useMemo, useRef, useState } from 'react';\nimport { jsx, jsxs, Fragment } from 'react/jsx-runtime';\nimport { toJsxRuntime } from 'hast-util-to-jsx-runtime';\nimport type { CodeToHastOptions } from 'shiki';\n\nimport type {\n HighlighterFactory,\n HighlighterOptions,\n HighlighterOptionsFor,\n HighlightResult,\n HighlightResultMap,\n Language,\n OutputFormat,\n PreloadLanguage,\n Theme,\n Themes,\n TimeoutState,\n} from './types';\n\nimport { throttleHighlighting, useStableValue } from './utils';\nimport { useFeatureStyles } from './styles';\nimport {\n getEmbeddedLanguages,\n resolveLanguage,\n resolveLoadedLanguage,\n} from './language';\nimport { resolveTheme } from './theme';\nimport { resolveEngine } from './engine';\nimport { buildShikiOptions } from './options';\n\nexport async function highlight<F extends OutputFormat = 'react'>(\n code: string,\n resolved: {\n languageId: string;\n langsToLoad: PreloadLanguage[];\n themesToLoad: Theme[];\n shikiOptions: CodeToHastOptions;\n },\n opts: Pick<HighlighterOptions, 'highlighter' | 'engine'> & {\n outputFormat?: F;\n },\n factory: HighlighterFactory\n): Promise<HighlightResultMap[F]> {\n const { languageId, langsToLoad, themesToLoad, shikiOptions } =\n resolved;\n const format: OutputFormat = opts.outputFormat ?? 'react';\n\n const highlighter =\n opts.highlighter ??\n (await (async () => {\n const hl = await factory(\n langsToLoad,\n themesToLoad,\n resolveEngine(opts.engine)\n );\n await hl.loadLanguage(\n ...getEmbeddedLanguages(code, languageId, hl)\n );\n return hl;\n })());\n\n const language = resolveLoadedLanguage(\n languageId,\n highlighter.getLoadedLanguages()\n );\n const options = { ...shikiOptions, lang: language };\n\n const outputs: { [K in OutputFormat]: () => HighlightResultMap[K] } = {\n react: () =>\n toJsxRuntime(highlighter.codeToHast(code, options), {\n jsx,\n jsxs,\n Fragment,\n }),\n html: () => highlighter.codeToHtml(code, options),\n tokens: () => highlighter.codeToTokens(code, options),\n };\n\n // Untyped call sites can pass formats outside OutputFormat; degrade to\n // react rendering like the pre-tokens ternary did instead of throwing.\n return (outputs[format] ?? outputs.react)() as HighlightResultMap[F];\n}\n\nexport const useHighlight = <F extends OutputFormat = 'react'>(\n code: string,\n lang: Language,\n themeInput: Theme | Themes,\n options: HighlighterOptionsFor<F> = {},\n highlighterFactory: HighlighterFactory\n): HighlightResult<F> => {\n const [highlightedCode, setHighlightedCode] =\n useState<HighlightResult<F>>(null);\n\n // This hook owns the line-number/highlight output, so it also owns the\n // corresponding styles. The primitive boolean limits effect reruns.\n useFeatureStyles(\n Boolean(\n options.showLineNumbers || options.highlightLineNumbers?.length\n )\n );\n\n const stableLang = useStableValue(lang);\n const stableTheme = useStableValue(themeInput);\n const stableOpts = useStableValue(options);\n\n const resolved = useMemo(() => {\n const { languageId, langsToLoad } = resolveLanguage(\n stableLang,\n stableOpts.customLanguages,\n stableOpts.langAlias,\n stableOpts.preloadLanguages\n );\n\n const theme = resolveTheme(stableTheme);\n\n const shikiOptions = buildShikiOptions(languageId, theme, stableOpts);\n return {\n languageId,\n langsToLoad,\n themesToLoad: theme.themesToLoad,\n shikiOptions,\n };\n }, [stableLang, stableTheme, stableOpts]);\n\n const requestIdRef = useRef(0);\n\n const timeoutControl = useRef<TimeoutState>({\n nextAllowedTime: 0,\n timeoutId: undefined,\n });\n\n // Factory is invoked imperatively; its identity is not semantic, so it must\n // not invalidate the effect on every render. See issue #161.\n const highlighterFactoryRef = useRef(highlighterFactory);\n highlighterFactoryRef.current = highlighterFactory;\n\n useEffect(() => {\n const requestId = ++requestIdRef.current;\n\n if (!resolved.languageId) return;\n\n const run = async () => {\n try {\n const result = await highlight<F>(\n code,\n resolved,\n stableOpts,\n highlighterFactoryRef.current\n );\n if (requestId === requestIdRef.current) {\n setHighlightedCode(result);\n }\n } catch (error) {\n console.error('[react-shiki] highlight failed', error);\n }\n };\n\n if (stableOpts.delay) {\n throttleHighlighting(run, timeoutControl, stableOpts.delay);\n } else {\n run();\n }\n\n return () => {\n clearTimeout(timeoutControl.current.timeoutId);\n };\n }, [code, resolved, stableOpts]);\n\n return highlightedCode;\n};\n","import { visit } from 'unist-util-visit';\nimport type { Root } from 'hast';\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: Root): undefined => {\n visit(tree, 'element', (node: Element, _index, parent) => {\n if (\n node.tagName === 'code' &&\n parent?.type === 'element' &&\n parent.tagName !== 'pre'\n ) {\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","import { clsx } from 'clsx';\n\nimport type {\n HighlighterOptions,\n Language,\n OutputFormat,\n Theme,\n Themes,\n UseShikiHighlighter,\n} from './types';\nimport { forwardRef } from 'react';\nimport { useComponentStyles } from './styles';\n\nlet warnedTokensOutputFormat = false;\n\n/**\n * The component's props exclude `outputFormat: 'tokens'`, but plain-JS\n * callers can still pass it. Tokens are not renderable React children,\n * so fall back to 'react' instead of crashing the render.\n */\nconst resolveOutputFormat = (\n outputFormat: HighlighterOptions['outputFormat']\n): HighlighterOptions['outputFormat'] => {\n if ((outputFormat as OutputFormat) !== 'tokens') return outputFormat;\n\n if (!warnedTokensOutputFormat) {\n warnedTokensOutputFormat = true;\n console.warn(\n \"[react-shiki] outputFormat 'tokens' is only supported by the useShikiHighlighter hook, falling back to 'react'\"\n );\n }\n return 'react';\n};\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 * The HTML element that wraps the generated code block.\n * @default 'div'\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 addDefaultStyles = true,\n style,\n langStyle,\n className,\n langClassName,\n showLanguage = true,\n children: code,\n as: Element = 'div',\n outputFormat,\n ...options\n },\n ref\n ) => {\n // Kept inside the component so hook-only bundles can tree-shake it\n // together with the component CSS.\n useComponentStyles();\n\n const displayLanguageId =\n typeof language === 'object'\n ? language.name || null\n : language?.trim() || null;\n\n const highlightedCode = useShikiHighlighterImpl(\n code,\n language,\n theme,\n {\n ...options,\n outputFormat: resolveOutputFormat(outputFormat),\n }\n );\n\n return (\n <Element\n ref={ref}\n data-testid=\"shiki-container\"\n data-slot=\"container\"\n className={clsx(\n 'rs-root',\n 'not-prose',\n addDefaultStyles && 'rs-default-styles',\n className\n )}\n style={style}\n >\n {showLanguage && displayLanguageId ? (\n <span\n data-slot=\"language-label\"\n className={clsx('rs-language-label', langClassName)}\n style={langStyle}\n >\n {displayLanguageId}\n </span>\n ) : null}\n {typeof highlightedCode === 'string' ? (\n <div\n data-slot=\"content\"\n dangerouslySetInnerHTML={{ __html: highlightedCode }}\n />\n ) : (\n highlightedCode\n )}\n </Element>\n );\n }\n );\n};\n"],"mappings":"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"}