react-shiki 0.7.0 → 0.7.2
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 +98 -8
- package/dist/{chunk-XAMWR6XF.js → chunk-SDEA5YEL.js} +59 -7
- package/dist/chunk-SDEA5YEL.js.map +1 -0
- package/dist/{component-D73l5tW-.d.ts → component-B4tYZaD8.d.ts} +22 -2
- package/dist/core.d.ts +3 -3
- package/dist/core.js +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -1
- package/dist/web.d.ts +3 -3
- package/dist/web.js +1 -1
- package/package.json +5 -3
- package/src/lib/styles.css +55 -0
- package/dist/chunk-XAMWR6XF.js.map +0 -1
package/README.md
CHANGED
|
@@ -23,6 +23,7 @@ A performant client-side syntax highlighting component and hook for React, built
|
|
|
23
23
|
- [Custom Languages](#custom-languages)
|
|
24
24
|
- [Preloading Custom Languages](#preloading-custom-languages)
|
|
25
25
|
- [Custom Transformers](#custom-transformers)
|
|
26
|
+
- [Line Numbers](#line-numbers)
|
|
26
27
|
- [Integration](#integration)
|
|
27
28
|
- [Integration with react-markdown](#integration-with-react-markdown)
|
|
28
29
|
- [Handling Inline Code](#handling-inline-code)
|
|
@@ -44,6 +45,7 @@ A performant client-side syntax highlighting component and hook for React, built
|
|
|
44
45
|
- 🖥️ `ShikiHighlighter` component displays a language label for each code block
|
|
45
46
|
when `showLanguage` is set to `true` (default)
|
|
46
47
|
- 🎨 Customizable styling of generated code blocks and language labels
|
|
48
|
+
- 📏 Optional line numbers with customizable starting number and styling
|
|
47
49
|
|
|
48
50
|
## Installation
|
|
49
51
|
|
|
@@ -155,6 +157,9 @@ See [Shiki - RegExp Engines](https://shiki.style/guide/regex-engines) for more i
|
|
|
155
157
|
| `theme` | `string \| object` | `'github-dark'` | Single or multi-theme configuration, built-in or custom textmate theme object |
|
|
156
158
|
| `delay` | `number` | `0` | Delay between highlights (in milliseconds) |
|
|
157
159
|
| `customLanguages` | `array` | `[]` | Array of custom languages to preload |
|
|
160
|
+
| `langAlias` | `object` | `{}` | Map of language aliases |
|
|
161
|
+
| `showLineNumbers` | `boolean` | `false` | Display line numbers alongside code |
|
|
162
|
+
| `startingLineNumber` | `number` | `1` | Starting line number when line numbers are enabled |
|
|
158
163
|
| `transformers` | `array` | `[]` | Custom Shiki transformers for modifying the highlighting output |
|
|
159
164
|
| `cssVariablePrefix` | `string` | `'--shiki'` | Prefix for CSS variables storing theme colors |
|
|
160
165
|
| `defaultColor` | `string \| false` | `'light'` | Default theme mode when using multiple themes, can also disable default theme |
|
|
@@ -221,12 +226,12 @@ Custom themes can be passed as a TextMate theme in JavaScript object. For exampl
|
|
|
221
226
|
```tsx
|
|
222
227
|
import tokyoNight from "../styles/tokyo-night.json";
|
|
223
228
|
|
|
224
|
-
//
|
|
229
|
+
// Component
|
|
225
230
|
<ShikiHighlighter language="tsx" theme={tokyoNight}>
|
|
226
231
|
{code.trim()}
|
|
227
232
|
</ShikiHighlighter>
|
|
228
233
|
|
|
229
|
-
//
|
|
234
|
+
// Hook
|
|
230
235
|
const highlightedCode = useShikiHighlighter(code, "tsx", tokyoNight);
|
|
231
236
|
```
|
|
232
237
|
|
|
@@ -237,12 +242,12 @@ Custom languages should be passed as a TextMate grammar in JavaScript object. Fo
|
|
|
237
242
|
```tsx
|
|
238
243
|
import mcfunction from "../langs/mcfunction.tmLanguage.json";
|
|
239
244
|
|
|
240
|
-
//
|
|
245
|
+
// Component
|
|
241
246
|
<ShikiHighlighter language={mcfunction} theme="github-dark">
|
|
242
247
|
{code.trim()}
|
|
243
248
|
</ShikiHighlighter>
|
|
244
249
|
|
|
245
|
-
//
|
|
250
|
+
// Hook
|
|
246
251
|
const highlightedCode = useShikiHighlighter(code, mcfunction, "github-dark");
|
|
247
252
|
```
|
|
248
253
|
|
|
@@ -254,7 +259,7 @@ For dynamic highlighting scenarios where language selection happens at runtime:
|
|
|
254
259
|
import mcfunction from "../langs/mcfunction.tmLanguage.json";
|
|
255
260
|
import bosque from "../langs/bosque.tmLanguage.json";
|
|
256
261
|
|
|
257
|
-
//
|
|
262
|
+
// Component
|
|
258
263
|
<ShikiHighlighter
|
|
259
264
|
language="typescript"
|
|
260
265
|
theme="github-dark"
|
|
@@ -263,28 +268,113 @@ import bosque from "../langs/bosque.tmLanguage.json";
|
|
|
263
268
|
{code.trim()}
|
|
264
269
|
</ShikiHighlighter>
|
|
265
270
|
|
|
266
|
-
//
|
|
271
|
+
// Hook
|
|
267
272
|
const highlightedCode = useShikiHighlighter(code, "typescript", "github-dark", {
|
|
268
273
|
customLanguages: [mcfunction, bosque],
|
|
269
274
|
});
|
|
270
275
|
```
|
|
271
276
|
|
|
277
|
+
### Language Aliases
|
|
278
|
+
|
|
279
|
+
You can define custom aliases for languages using the `langAlias` option. This is useful when you want to use alternative names for languages:
|
|
280
|
+
|
|
281
|
+
```tsx
|
|
282
|
+
// Component
|
|
283
|
+
<ShikiHighlighter
|
|
284
|
+
language="indents"
|
|
285
|
+
theme="github-dark"
|
|
286
|
+
langAlias={{ indents: "python" }}
|
|
287
|
+
>
|
|
288
|
+
{code.trim()}
|
|
289
|
+
</ShikiHighlighter>
|
|
290
|
+
|
|
291
|
+
// Hook
|
|
292
|
+
const highlightedCode = useShikiHighlighter(code, "indents", "github-dark", {
|
|
293
|
+
langAlias: { indents: "python" },
|
|
294
|
+
});
|
|
295
|
+
```
|
|
296
|
+
|
|
272
297
|
### Custom Transformers
|
|
273
298
|
|
|
274
299
|
```tsx
|
|
275
300
|
import { customTransformer } from "../utils/shikiTransformers";
|
|
276
301
|
|
|
277
|
-
//
|
|
302
|
+
// Component
|
|
278
303
|
<ShikiHighlighter language="tsx" transformers={[customTransformer]}>
|
|
279
304
|
{code.trim()}
|
|
280
305
|
</ShikiHighlighter>
|
|
281
306
|
|
|
282
|
-
//
|
|
307
|
+
// Hook
|
|
283
308
|
const highlightedCode = useShikiHighlighter(code, "tsx", "github-dark", {
|
|
284
309
|
transformers: [customTransformer],
|
|
285
310
|
});
|
|
286
311
|
```
|
|
287
312
|
|
|
313
|
+
### Line Numbers
|
|
314
|
+
|
|
315
|
+
Display line numbers alongside your code, these are CSS-based
|
|
316
|
+
and can be customized with CSS variables:
|
|
317
|
+
|
|
318
|
+
```tsx
|
|
319
|
+
// Component
|
|
320
|
+
<ShikiHighlighter
|
|
321
|
+
language="javascript"
|
|
322
|
+
theme="github-dark"
|
|
323
|
+
showLineNumbers,
|
|
324
|
+
startingLineNumber={0} // default is 1
|
|
325
|
+
>
|
|
326
|
+
{code}
|
|
327
|
+
</ShikiHighlighter>
|
|
328
|
+
|
|
329
|
+
<ShikiHighlighter
|
|
330
|
+
language="python"
|
|
331
|
+
theme="github-dark"
|
|
332
|
+
showLineNumbers
|
|
333
|
+
startingLineNumber={0}
|
|
334
|
+
>
|
|
335
|
+
{code}
|
|
336
|
+
</ShikiHighlighter>
|
|
337
|
+
|
|
338
|
+
// Hook (import 'react-shiki/css' for line numbers to work)
|
|
339
|
+
const highlightedCode = useShikiHighlighter(code, "javascript", "github-dark", {
|
|
340
|
+
showLineNumbers: true,
|
|
341
|
+
startingLineNumber: 0,
|
|
342
|
+
});
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
> [!NOTE]
|
|
346
|
+
> When using the hook with line numbers, import the CSS file for the line numbers to work:
|
|
347
|
+
> ```tsx
|
|
348
|
+
> import 'react-shiki/css';
|
|
349
|
+
> ```
|
|
350
|
+
> Or provide your own CSS counter implementation and styles for `.line-numbers` (line `span`) and `.has-line-numbers` (container `code` element)
|
|
351
|
+
|
|
352
|
+
Available CSS variables for customization:
|
|
353
|
+
```css
|
|
354
|
+
--line-numbers-foreground: rgba(107, 114, 128, 0.5);
|
|
355
|
+
--line-numbers-width: 2ch;
|
|
356
|
+
--line-numbers-padding-left: 0ch;
|
|
357
|
+
--line-numbers-padding-right: 2ch;
|
|
358
|
+
--line-numbers-font-size: inherit;
|
|
359
|
+
--line-numbers-font-weight: inherit;
|
|
360
|
+
--line-numbers-opacity: 1;
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
You can customize them in your own CSS or by using the style prop on the component:
|
|
364
|
+
```tsx
|
|
365
|
+
<ShikiHighlighter
|
|
366
|
+
language="javascript"
|
|
367
|
+
theme="github-dark"
|
|
368
|
+
showLineNumbers
|
|
369
|
+
style={{
|
|
370
|
+
'--line-numbers-foreground': '#60a5fa',
|
|
371
|
+
'--line-numbers-width': '3ch'
|
|
372
|
+
}}
|
|
373
|
+
>
|
|
374
|
+
{code}
|
|
375
|
+
</ShikiHighlighter>
|
|
376
|
+
```
|
|
377
|
+
|
|
288
378
|
## Integration
|
|
289
379
|
|
|
290
380
|
### Integration with react-markdown
|
|
@@ -35,7 +35,7 @@ import { jsx, jsxs, Fragment } from "react/jsx-runtime";
|
|
|
35
35
|
import { toJsxRuntime } from "hast-util-to-jsx-runtime";
|
|
36
36
|
|
|
37
37
|
// src/lib/resolvers.ts
|
|
38
|
-
var resolveLanguage = (lang, customLanguages) => {
|
|
38
|
+
var resolveLanguage = (lang, customLanguages, langAliases) => {
|
|
39
39
|
const normalizedCustomLangs = customLanguages ? Array.isArray(customLanguages) ? customLanguages : [customLanguages] : [];
|
|
40
40
|
if (lang == null || typeof lang === "string" && !lang.trim()) {
|
|
41
41
|
return {
|
|
@@ -63,6 +63,13 @@ var resolveLanguage = (lang, customLanguages) => {
|
|
|
63
63
|
langsToLoad: customMatch
|
|
64
64
|
};
|
|
65
65
|
}
|
|
66
|
+
if (langAliases?.[lang]) {
|
|
67
|
+
return {
|
|
68
|
+
languageId: langAliases[lang],
|
|
69
|
+
displayLanguageId: lang,
|
|
70
|
+
langsToLoad: langAliases[lang]
|
|
71
|
+
};
|
|
72
|
+
}
|
|
66
73
|
return {
|
|
67
74
|
languageId: lang,
|
|
68
75
|
displayLanguageId: lang,
|
|
@@ -94,6 +101,28 @@ function resolveTheme(themeInput) {
|
|
|
94
101
|
};
|
|
95
102
|
}
|
|
96
103
|
|
|
104
|
+
// src/lib/transformers.ts
|
|
105
|
+
function lineNumbersTransformer(startLine = 1) {
|
|
106
|
+
return {
|
|
107
|
+
name: "react-shiki:line-numbers",
|
|
108
|
+
code(node) {
|
|
109
|
+
this.addClassToHast(node, "has-line-numbers");
|
|
110
|
+
if (startLine !== 1) {
|
|
111
|
+
const existingStyle = node.properties?.style || "";
|
|
112
|
+
const newStyle = existingStyle ? `${existingStyle}; --line-start: ${startLine}` : `--line-start: ${startLine}`;
|
|
113
|
+
node.properties = {
|
|
114
|
+
...node.properties,
|
|
115
|
+
style: newStyle
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
line(node) {
|
|
120
|
+
this.addClassToHast(node, "line-numbers");
|
|
121
|
+
return node;
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
97
126
|
// src/lib/hook.ts
|
|
98
127
|
var DEFAULT_THEMES = {
|
|
99
128
|
light: "github-light",
|
|
@@ -123,8 +152,12 @@ var useShikiHighlighter = (code, lang, themeInput, createHighlighter, options =
|
|
|
123
152
|
const [stableTheme, themeRev] = useStableOptions(themeInput);
|
|
124
153
|
const [stableOpts, optsRev] = useStableOptions(options);
|
|
125
154
|
const { languageId, langsToLoad } = useMemo(
|
|
126
|
-
() => resolveLanguage(
|
|
127
|
-
|
|
155
|
+
() => resolveLanguage(
|
|
156
|
+
stableLang,
|
|
157
|
+
stableOpts.customLanguages,
|
|
158
|
+
stableOpts.langAlias
|
|
159
|
+
),
|
|
160
|
+
[stableLang, stableOpts.customLanguages, stableOpts.langAlias]
|
|
128
161
|
);
|
|
129
162
|
const { isMultiTheme, themeId, multiTheme, singleTheme, themesToLoad } = useMemo(() => resolveTheme(stableTheme), [stableTheme]);
|
|
130
163
|
const timeoutControl = useRef({
|
|
@@ -132,8 +165,14 @@ var useShikiHighlighter = (code, lang, themeInput, createHighlighter, options =
|
|
|
132
165
|
timeoutId: void 0
|
|
133
166
|
});
|
|
134
167
|
const shikiOptions = useMemo(() => {
|
|
135
|
-
const { defaultColor, cssVariablePrefix, ...restOptions } = stableOpts;
|
|
136
168
|
const languageOption = { lang: languageId };
|
|
169
|
+
const {
|
|
170
|
+
defaultColor,
|
|
171
|
+
cssVariablePrefix,
|
|
172
|
+
showLineNumbers,
|
|
173
|
+
startingLineNumber,
|
|
174
|
+
...restOptions
|
|
175
|
+
} = stableOpts;
|
|
137
176
|
const themeOptions = isMultiTheme ? {
|
|
138
177
|
themes: multiTheme || DEFAULT_THEMES,
|
|
139
178
|
defaultColor,
|
|
@@ -141,7 +180,16 @@ var useShikiHighlighter = (code, lang, themeInput, createHighlighter, options =
|
|
|
141
180
|
} : {
|
|
142
181
|
theme: singleTheme || DEFAULT_THEMES.dark
|
|
143
182
|
};
|
|
144
|
-
|
|
183
|
+
const transformers = restOptions.transformers || [];
|
|
184
|
+
if (showLineNumbers) {
|
|
185
|
+
transformers.push(lineNumbersTransformer(startingLineNumber));
|
|
186
|
+
}
|
|
187
|
+
return {
|
|
188
|
+
...languageOption,
|
|
189
|
+
...themeOptions,
|
|
190
|
+
...restOptions,
|
|
191
|
+
transformers
|
|
192
|
+
};
|
|
145
193
|
}, [languageId, themeId, langRev, themeRev, optsRev]);
|
|
146
194
|
useEffect(() => {
|
|
147
195
|
let isMounted = true;
|
|
@@ -203,7 +251,7 @@ function styleInject(css, { insertAt } = {}) {
|
|
|
203
251
|
}
|
|
204
252
|
|
|
205
253
|
// src/lib/styles.css
|
|
206
|
-
styleInject(".relative {\n position: relative;\n}\n.defaultStyles pre {\n overflow: auto;\n border-radius: 0.5rem;\n padding-left: 1.5rem;\n padding-right: 1.5rem;\n padding-top: 1.25rem;\n padding-bottom: 1.25rem;\n}\n.languageLabel {\n position: absolute;\n right: 0.75rem;\n top: 0.5rem;\n font-family: monospace;\n font-size: 0.75rem;\n letter-spacing: -0.05em;\n color: rgba(107, 114, 128, 0.85);\n}\n");
|
|
254
|
+
styleInject(".relative {\n position: relative;\n}\n.defaultStyles pre {\n overflow: auto;\n border-radius: 0.5rem;\n padding-left: 1.5rem;\n padding-right: 1.5rem;\n padding-top: 1.25rem;\n padding-bottom: 1.25rem;\n}\n.languageLabel {\n position: absolute;\n right: 0.75rem;\n top: 0.5rem;\n font-family: monospace;\n font-size: 0.75rem;\n letter-spacing: -0.05em;\n color: rgba(107, 114, 128, 0.85);\n}\n.line-numbers::before {\n counter-increment: line-number;\n content: counter(line-number);\n display: inline-flex;\n justify-content: flex-end;\n align-items: flex-start;\n box-sizing: content-box;\n min-width: var(--line-numbers-width, 2ch);\n padding-left: var(--line-numbers-padding-left, 2ch);\n padding-right: var(--line-numbers-padding-right, 2ch);\n color: var(--line-numbers-foreground, rgba(107, 114, 128, 0.6));\n font-size: var(--line-numbers-font-size, inherit);\n font-weight: var(--line-numbers-font-weight, inherit);\n line-height: var(--line-numbers-line-height, inherit);\n font-family: var(--line-numbers-font-family, inherit);\n opacity: var(--line-numbers-opacity, 1);\n user-select: none;\n pointer-events: none;\n}\n.has-line-numbers {\n counter-reset: line-number calc(var(--line-start, 1) - 1);\n --line-numbers-foreground: rgba(107, 114, 128, 0.5);\n --line-numbers-width: 2ch;\n --line-numbers-padding-left: 0ch;\n --line-numbers-padding-right: 2ch;\n --line-numbers-font-size: inherit;\n --line-numbers-font-weight: inherit;\n --line-numbers-line-height: inherit;\n --line-numbers-font-family: inherit;\n --line-numbers-opacity: 1;\n}\n");
|
|
207
255
|
|
|
208
256
|
// src/lib/component.tsx
|
|
209
257
|
import { clsx } from "clsx";
|
|
@@ -222,6 +270,8 @@ var createShikiHighlighterComponent = (useShikiHighlighterImpl) => {
|
|
|
222
270
|
className,
|
|
223
271
|
langClassName,
|
|
224
272
|
showLanguage = true,
|
|
273
|
+
showLineNumbers = false,
|
|
274
|
+
startingLineNumber = 1,
|
|
225
275
|
children: code,
|
|
226
276
|
as: Element = "pre",
|
|
227
277
|
customLanguages,
|
|
@@ -233,6 +283,8 @@ var createShikiHighlighterComponent = (useShikiHighlighterImpl) => {
|
|
|
233
283
|
customLanguages,
|
|
234
284
|
defaultColor,
|
|
235
285
|
cssVariablePrefix,
|
|
286
|
+
showLineNumbers,
|
|
287
|
+
startingLineNumber,
|
|
236
288
|
...shikiOptions
|
|
237
289
|
};
|
|
238
290
|
const { displayLanguageId } = resolveLanguage(
|
|
@@ -280,4 +332,4 @@ export {
|
|
|
280
332
|
useShikiHighlighter,
|
|
281
333
|
createShikiHighlighterComponent
|
|
282
334
|
};
|
|
283
|
-
//# sourceMappingURL=chunk-
|
|
335
|
+
//# sourceMappingURL=chunk-SDEA5YEL.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/utils.ts","../src/lib/hook.ts","../src/lib/resolvers.ts","../src/lib/transformers.ts","#style-inject:#style-inject","../src/lib/styles.css","../src/lib/component.tsx"],"sourcesContent":["import { visit } from 'unist-util-visit';\n\nimport type { TimeoutState, Element } from './types';\n\n/**\n * Rehype plugin to add an 'inline' property to <code> elements\n * Sets 'inline' property to true if the <code> is not within a <pre> tag\n *\n * Pass this plugin to the `rehypePlugins` prop of react-markdown\n * You can then access `inline` as a prop in components passed to react-markdown\n *\n * @example\n * <ReactMarkdown rehypePlugins={[rehypeInlineCodeProperty]} />\n */\nexport function rehypeInlineCodeProperty() {\n return (tree: any): undefined => {\n visit(tree, 'element', (node: Element, _index, parent: Element) => {\n if (node.tagName === 'code' && parent.tagName !== 'pre') {\n node.properties.inline = true;\n }\n });\n };\n}\n\n/**\n * Function to determine if code is inline based on the presence of line breaks\n *\n * @example\n * const isInline = node && isInlineCode(node: Element)\n */\nexport const isInlineCode = (node: Element): boolean => {\n const textContent = (node.children || [])\n .filter((child) => child.type === 'text')\n .map((child) => child.value)\n .join('');\n\n return !textContent.includes('\\n');\n};\n\n/**\n * Optionally throttles rapid sequential highlighting operations\n * Exported for testing in __tests__/throttleHighlighting.test.ts\n *\n * @example\n * const timeoutControl = useRef<TimeoutState>({\n * nextAllowedTime: 0,\n * timeoutId: undefined\n * });\n *\n * throttleHighlighting(highlightCode, timeoutControl, 1000);\n */\nexport const throttleHighlighting = (\n performHighlight: () => Promise<void>,\n timeoutControl: React.RefObject<TimeoutState>,\n throttleMs: number\n) => {\n const now = Date.now();\n clearTimeout(timeoutControl.current.timeoutId);\n\n const delay = Math.max(0, timeoutControl.current.nextAllowedTime - now);\n timeoutControl.current.timeoutId = setTimeout(() => {\n performHighlight().catch(console.error);\n timeoutControl.current.nextAllowedTime = now + throttleMs;\n }, delay);\n};\n","import {\n useEffect,\n useMemo,\n useRef,\n useState,\n type ReactNode,\n} from 'react';\n\nimport { dequal } from 'dequal/lite';\n\nimport { jsx, jsxs, Fragment } from 'react/jsx-runtime';\nimport { toJsxRuntime } from 'hast-util-to-jsx-runtime';\n\nimport type {\n CodeToHastOptions,\n CodeOptionsSingleTheme,\n CodeOptionsMultipleThemes,\n Highlighter,\n HighlighterCore,\n} from 'shiki';\n\nimport type { ShikiLanguageRegistration } from './extended-types';\n\nimport type {\n Language,\n Theme,\n HighlighterOptions,\n TimeoutState,\n Themes,\n} from './types';\n\nimport { throttleHighlighting } from './utils';\nimport { resolveLanguage, resolveTheme } from './resolvers';\nimport { lineNumbersTransformer } from './transformers';\n\nconst DEFAULT_THEMES: Themes = {\n light: 'github-light',\n dark: 'github-dark',\n};\n\n/**\n * Returns a deep-stable reference and a version counter that only changes when content changes.\n * Includes optimizations for primitive values and reference equality.\n */\nconst useStableOptions = <T>(value: T) => {\n const ref = useRef(value);\n const revision = useRef(0);\n\n // Fast-path for primitive values\n if (typeof value !== 'object' || value === null) {\n if (value !== ref.current) {\n ref.current = value;\n revision.current += 1;\n }\n return [value, revision.current] as const;\n }\n\n // Reference equality check before expensive deep comparison\n if (value !== ref.current) {\n if (!dequal(value, ref.current)) {\n ref.current = value;\n revision.current += 1;\n }\n }\n\n return [ref.current, revision.current] as const;\n};\n\n/**\n * Base hook for syntax highlighting using Shiki.\n * This is the core implementation used by all entry points.\n *\n * @param code - The code to highlight\n * @param lang - Language for highlighting\n * @param themeInput - Theme or themes to use\n * @param options - Highlighting options\n * @param createHighlighter - Factory function to create highlighter (internal use)\n */\nexport const useShikiHighlighter = (\n code: string,\n lang: Language,\n themeInput: Theme | Themes,\n createHighlighter: (\n langsToLoad: ShikiLanguageRegistration,\n themesToLoad: Theme[]\n ) => Promise<Highlighter | HighlighterCore>,\n options: HighlighterOptions = {}\n) => {\n const [highlightedCode, setHighlightedCode] =\n useState<ReactNode | null>(null);\n\n // Stabilize options, language and theme inputs to prevent unnecessary\n // re-renders or recalculations when object references change\n const [stableLang, langRev] = useStableOptions(lang);\n const [stableTheme, themeRev] = useStableOptions(themeInput);\n const [stableOpts, optsRev] = useStableOptions(options);\n\n const { languageId, langsToLoad } = useMemo(\n () =>\n resolveLanguage(\n stableLang,\n stableOpts.customLanguages,\n stableOpts.langAlias\n ),\n [stableLang, stableOpts.customLanguages, stableOpts.langAlias]\n );\n\n const { isMultiTheme, themeId, multiTheme, singleTheme, themesToLoad } =\n useMemo(() => resolveTheme(stableTheme), [stableTheme]);\n\n const timeoutControl = useRef<TimeoutState>({\n nextAllowedTime: 0,\n timeoutId: undefined,\n });\n\n const shikiOptions = useMemo<CodeToHastOptions>(() => {\n const languageOption = { lang: languageId };\n const {\n defaultColor,\n cssVariablePrefix,\n showLineNumbers,\n startingLineNumber,\n ...restOptions\n } = stableOpts;\n\n const themeOptions = isMultiTheme\n ? ({\n themes: multiTheme || DEFAULT_THEMES,\n defaultColor,\n cssVariablePrefix,\n } as CodeOptionsMultipleThemes)\n : ({\n theme: singleTheme || DEFAULT_THEMES.dark,\n } as CodeOptionsSingleTheme);\n\n const transformers = restOptions.transformers || [];\n if (showLineNumbers) {\n transformers.push(lineNumbersTransformer(startingLineNumber));\n }\n\n return {\n ...languageOption,\n ...themeOptions,\n ...restOptions,\n transformers,\n };\n }, [languageId, themeId, langRev, themeRev, optsRev]);\n\n useEffect(() => {\n let isMounted = true;\n\n const highlightCode = async () => {\n if (!languageId) return;\n\n const highlighter = stableOpts.highlighter\n ? stableOpts.highlighter\n : await createHighlighter(\n langsToLoad as ShikiLanguageRegistration,\n themesToLoad\n );\n\n const loadedLanguages = highlighter.getLoadedLanguages();\n const langToUse = loadedLanguages.includes(languageId)\n ? languageId\n : 'plaintext';\n const finalOptions = { ...shikiOptions, lang: langToUse };\n\n const hast = highlighter.codeToHast(code, finalOptions);\n\n if (isMounted) {\n setHighlightedCode(toJsxRuntime(hast, { jsx, jsxs, Fragment }));\n }\n };\n\n const { delay } = stableOpts;\n\n if (delay) {\n throttleHighlighting(highlightCode, timeoutControl, delay);\n } else {\n highlightCode().catch(console.error);\n }\n\n return () => {\n isMounted = false;\n clearTimeout(timeoutControl.current.timeoutId);\n };\n }, [\n code,\n shikiOptions,\n stableOpts.delay,\n stableOpts.highlighter,\n langsToLoad,\n themesToLoad,\n ]);\n\n return highlightedCode;\n};\n","import type { Language, Theme, Themes } from './types';\nimport type { ThemeRegistrationAny } from 'shiki/core';\nimport type { LanguageRegistration } from './extended-types';\n\n/**\n * Resolved languages and metadata\n */\ntype LanguageResult = {\n languageId: string;\n displayLanguageId: string | null;\n langsToLoad: Language;\n};\n\n/**\n * Resolves the language input to standardized IDs and objects for Shiki and UI display\n * @param lang The language input from props\n * @param customLanguages An array of custom textmate grammar objects or a single grammar object\n * @returns A LanguageResult object containing:\n * - languageId: The resolved language ID\n * - displayLanguageId: The display language ID\n * - langToLoad: The language object or string id to load\n */\nexport const resolveLanguage = (\n lang: Language,\n customLanguages?: LanguageRegistration | LanguageRegistration[],\n langAliases?: Record<string, string>\n): LanguageResult => {\n const normalizedCustomLangs = customLanguages\n ? Array.isArray(customLanguages)\n ? customLanguages\n : [customLanguages]\n : [];\n\n // Language is null or empty string\n if (lang == null || (typeof lang === 'string' && !lang.trim())) {\n return {\n languageId: 'plaintext',\n displayLanguageId: 'plaintext',\n langsToLoad: undefined,\n };\n }\n\n // Language is custom\n if (typeof lang === 'object') {\n return {\n languageId: lang.name,\n displayLanguageId: lang.name || null,\n langsToLoad: lang,\n };\n }\n\n // Language is string\n const lowerLang = lang.toLowerCase();\n const matches = (str: string | undefined): boolean =>\n str?.toLowerCase() === lowerLang;\n\n // Check if the string identifies a provided custom language\n const customMatch = normalizedCustomLangs.find(\n (cl) =>\n matches(cl.name) ||\n matches(cl.scopeName) ||\n matches(cl.scopeName?.split('.').pop()) ||\n cl.aliases?.some(matches) ||\n cl.fileTypes?.some(matches)\n );\n\n if (customMatch) {\n return {\n languageId: customMatch.name || lang,\n displayLanguageId: lang,\n langsToLoad: customMatch,\n };\n }\n\n // Check if language is aliased\n if (langAliases?.[lang]) {\n return {\n languageId: langAliases[lang],\n displayLanguageId: lang,\n langsToLoad: langAliases[lang],\n };\n }\n\n // For any other string, pass it through,\n // fallback is handled in highlighter factories\n return {\n languageId: lang,\n displayLanguageId: lang,\n langsToLoad: lang,\n };\n};\n\n/**\n * Resolved themes and metadata\n */\ninterface ThemeResult {\n isMultiTheme: boolean;\n themeId: Theme;\n multiTheme?: Themes | ThemeRegistrationAny | null;\n singleTheme?: Theme | undefined;\n themesToLoad: Theme[];\n}\n\n/**\n * Determines theme configuration and returns the resolved theme with metadata\n * @param themeInput - The theme input, either as a string name or theme object\n * @returns Object containing:\n * - isMultiTheme: If theme input is a multi-theme configuration\n * - themeId: Theme reference identifier\n * - multiTheme: The multi-theme config if it exists\n * - singleTheme: The single theme if it exists\n * - themesToLoad: The themes to load when creating the highlighter\n */\nexport function resolveTheme(themeInput: Theme | Themes): ThemeResult {\n const isTextmateTheme =\n typeof themeInput === 'object' &&\n 'tokenColors' in themeInput &&\n Array.isArray(themeInput.tokenColors);\n\n // Assume non textmate objects are multi theme configs\n const isMultiThemeConfig =\n typeof themeInput === 'object' &&\n themeInput !== null &&\n !isTextmateTheme;\n\n const validMultiThemeObj =\n typeof themeInput === 'object' &&\n themeInput !== null &&\n !isTextmateTheme &&\n Object.entries(themeInput).some(\n ([key, value]) =>\n key &&\n value &&\n key.trim() !== '' &&\n value !== '' &&\n (typeof value === 'string' || isTextmateTheme)\n );\n\n if (isMultiThemeConfig) {\n const themeId = validMultiThemeObj\n ? `multi-${Object.values(themeInput)\n .map(\n (theme) =>\n (typeof theme === 'string' ? theme : theme?.name) ||\n 'custom'\n )\n .sort()\n .join('-')}`\n : 'multi-default';\n\n // If config is invalid, return null to handle fallback in `buildShikiOptions()`\n return {\n isMultiTheme: true,\n themeId,\n multiTheme: validMultiThemeObj ? themeInput : null,\n themesToLoad: validMultiThemeObj ? Object.values(themeInput) : [],\n };\n }\n\n return {\n isMultiTheme: false,\n themeId:\n typeof themeInput === 'string'\n ? themeInput\n : themeInput?.name || 'custom',\n singleTheme: themeInput,\n themesToLoad: [themeInput],\n };\n}\n","import type { ShikiTransformer } from 'shiki/core';\n\n/**\n * Creates a transformer that enables line numbers display\n * @param startLine - The starting line number (defaults to 1)\n */\nexport function lineNumbersTransformer(startLine = 1): ShikiTransformer {\n return {\n name: 'react-shiki:line-numbers',\n code(node) {\n this.addClassToHast(node, 'has-line-numbers');\n if (startLine !== 1) {\n const existingStyle = (node.properties?.style as string) || '';\n const newStyle = existingStyle\n ? `${existingStyle}; --line-start: ${startLine}`\n : `--line-start: ${startLine}`;\n node.properties = {\n ...node.properties,\n style: newStyle,\n };\n }\n },\n line(node) {\n this.addClassToHast(node, 'line-numbers');\n return node;\n },\n };\n}\n","\n export default function styleInject(css, { insertAt } = {}) {\n if (!css || typeof document === 'undefined') return\n \n const head = document.head || document.getElementsByTagName('head')[0]\n const style = document.createElement('style')\n style.type = 'text/css'\n \n if (insertAt === 'top') {\n if (head.firstChild) {\n head.insertBefore(style, head.firstChild)\n } else {\n head.appendChild(style)\n }\n } else {\n head.appendChild(style)\n }\n \n if (style.styleSheet) {\n style.styleSheet.cssText = css\n } else {\n style.appendChild(document.createTextNode(css))\n }\n }\n ","import styleInject from '#style-inject';styleInject(\".relative {\\n position: relative;\\n}\\n.defaultStyles pre {\\n overflow: auto;\\n border-radius: 0.5rem;\\n padding-left: 1.5rem;\\n padding-right: 1.5rem;\\n padding-top: 1.25rem;\\n padding-bottom: 1.25rem;\\n}\\n.languageLabel {\\n position: absolute;\\n right: 0.75rem;\\n top: 0.5rem;\\n font-family: monospace;\\n font-size: 0.75rem;\\n letter-spacing: -0.05em;\\n color: rgba(107, 114, 128, 0.85);\\n}\\n.line-numbers::before {\\n counter-increment: line-number;\\n content: counter(line-number);\\n display: inline-flex;\\n justify-content: flex-end;\\n align-items: flex-start;\\n box-sizing: content-box;\\n min-width: var(--line-numbers-width, 2ch);\\n padding-left: var(--line-numbers-padding-left, 2ch);\\n padding-right: var(--line-numbers-padding-right, 2ch);\\n color: var(--line-numbers-foreground, rgba(107, 114, 128, 0.6));\\n font-size: var(--line-numbers-font-size, inherit);\\n font-weight: var(--line-numbers-font-weight, inherit);\\n line-height: var(--line-numbers-line-height, inherit);\\n font-family: var(--line-numbers-font-family, inherit);\\n opacity: var(--line-numbers-opacity, 1);\\n user-select: none;\\n pointer-events: none;\\n}\\n.has-line-numbers {\\n counter-reset: line-number calc(var(--line-start, 1) - 1);\\n --line-numbers-foreground: rgba(107, 114, 128, 0.5);\\n --line-numbers-width: 2ch;\\n --line-numbers-padding-left: 0ch;\\n --line-numbers-padding-right: 2ch;\\n --line-numbers-font-size: inherit;\\n --line-numbers-font-weight: inherit;\\n --line-numbers-line-height: inherit;\\n --line-numbers-font-family: inherit;\\n --line-numbers-opacity: 1;\\n}\\n\")","import './styles.css';\nimport { clsx } from 'clsx';\nimport { resolveLanguage } from './resolvers';\n\nimport type {\n HighlighterOptions,\n Language,\n Theme,\n Themes,\n} from './types';\n\n/**\n * Props for the ShikiHighlighter component\n */\nexport interface ShikiHighlighterProps extends HighlighterOptions {\n /**\n * The programming language for syntax highlighting\n * Supports custom textmate grammar objects in addition to Shiki's bundled languages\n * @see https://shiki.style/languages\n */\n language: Language;\n\n /**\n * The code to be highlighted\n */\n children: string;\n\n /**\n * The color theme or themes for syntax highlighting\n * Supports single, dual, or multiple themes\n * Supports custom textmate theme objects in addition to Shiki's bundled themes\n *\n * @example\n * theme='github-dark' // single theme\n * theme={{ light: 'github-light', dark: 'github-dark' }} // multi-theme\n *\n * @see https://shiki.style/themes\n */\n theme: Theme | Themes;\n\n /**\n * Controls the application of default styles to the generated code blocks\n *\n * Default styles include padding, overflow handling, border radius, language label styling, and font settings\n * @default true\n */\n addDefaultStyles?: boolean;\n\n /**\n * Add custom inline styles to the generated code block\n */\n style?: React.CSSProperties;\n\n /**\n * Add custom inline styles to the language label\n */\n langStyle?: React.CSSProperties;\n\n /**\n * Add custom CSS class names to the generated code block\n */\n className?: string;\n\n /**\n * Add custom CSS class names to the language label\n */\n langClassName?: string;\n\n /**\n * Whether to show the language label\n * @default true\n */\n showLanguage?: boolean;\n\n /**\n * Whether to show line numbers\n * @default false\n */\n showLineNumbers?: boolean;\n\n /**\n * Starting line number (when showLineNumbers is true)\n * @default 1\n */\n startingLineNumber?: number;\n\n /**\n * The HTML element that wraps the generated code block.\n * @default 'pre'\n */\n as?: React.ElementType;\n}\n\n/**\n * Base ShikiHighlighter component factory.\n * This creates a component that uses the provided hook implementation.\n */\nexport const createShikiHighlighterComponent = (\n useShikiHighlighterImpl: (\n code: string,\n lang: Language,\n themeInput: Theme | Themes,\n options?: HighlighterOptions\n ) => React.ReactNode\n) => {\n return ({\n language,\n theme,\n delay,\n transformers,\n defaultColor,\n cssVariablePrefix,\n addDefaultStyles = true,\n style,\n langStyle,\n className,\n langClassName,\n showLanguage = true,\n showLineNumbers = false,\n startingLineNumber = 1,\n children: code,\n as: Element = 'pre',\n customLanguages,\n ...shikiOptions\n }: ShikiHighlighterProps): React.ReactElement => {\n const options: HighlighterOptions = {\n delay,\n transformers,\n customLanguages,\n defaultColor,\n cssVariablePrefix,\n showLineNumbers,\n startingLineNumber,\n ...shikiOptions,\n };\n\n // Use resolveLanguage to get displayLanguageId directly\n const { displayLanguageId } = resolveLanguage(\n language,\n customLanguages\n );\n\n const highlightedCode = useShikiHighlighterImpl(\n code,\n language,\n theme,\n options\n );\n\n return (\n <Element\n data-testid=\"shiki-container\"\n className={clsx(\n 'relative',\n 'not-prose',\n addDefaultStyles && 'defaultStyles',\n className\n )}\n style={style}\n id=\"shiki-container\"\n >\n {showLanguage && displayLanguageId ? (\n <span\n className={clsx('languageLabel', langClassName)}\n style={langStyle}\n id=\"language-label\"\n >\n {displayLanguageId}\n </span>\n ) : null}\n {highlightedCode}\n </Element>\n );\n };\n};\n"],"mappings":"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","names":["jsx","jsxs"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Highlighter, HighlighterCore, CodeOptionsMultipleThemes, BundledTheme, CodeToHastOptions, StringLiteralUnion, BundledLanguage, SpecialLanguage, ThemeRegistrationAny } from 'shiki';
|
|
1
|
+
import { Highlighter, HighlighterCore, CodeOptionsMultipleThemes, BundledTheme, CodeToHastOptions, BundledHighlighterOptions, StringLiteralUnion, BundledLanguage, SpecialLanguage, ThemeRegistrationAny } from 'shiki';
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
3
|
import { LanguageRegistration as LanguageRegistration$1 } from 'shiki/core';
|
|
4
4
|
import { Element as Element$1 } from 'hast';
|
|
@@ -113,12 +113,22 @@ interface ReactShikiOptions {
|
|
|
113
113
|
* });
|
|
114
114
|
*/
|
|
115
115
|
highlighter?: Highlighter | HighlighterCore;
|
|
116
|
+
/**
|
|
117
|
+
* Whether to show line numbers
|
|
118
|
+
* @default false
|
|
119
|
+
*/
|
|
120
|
+
showLineNumbers?: boolean;
|
|
121
|
+
/**
|
|
122
|
+
* Starting line number (when showLineNumbers is true)
|
|
123
|
+
* @default 1
|
|
124
|
+
*/
|
|
125
|
+
startingLineNumber?: number;
|
|
116
126
|
}
|
|
117
127
|
/**
|
|
118
128
|
* Configuration options for the syntax highlighter
|
|
119
129
|
* Extends CodeToHastOptions to allow passing any Shiki options directly
|
|
120
130
|
*/
|
|
121
|
-
interface HighlighterOptions extends ReactShikiOptions, Pick<CodeOptionsMultipleThemes<BundledTheme>, 'defaultColor' | 'cssVariablePrefix'>, Omit<CodeToHastOptions, 'lang' | 'theme' | 'themes'> {
|
|
131
|
+
interface HighlighterOptions extends ReactShikiOptions, Pick<CodeOptionsMultipleThemes<BundledTheme>, 'defaultColor' | 'cssVariablePrefix'>, Omit<CodeToHastOptions, 'lang' | 'theme' | 'themes'>, Pick<BundledHighlighterOptions<string, string>, 'langAlias'> {
|
|
122
132
|
}
|
|
123
133
|
/**
|
|
124
134
|
* Public API signature for the useShikiHighlighter hook.
|
|
@@ -199,6 +209,16 @@ interface ShikiHighlighterProps extends HighlighterOptions {
|
|
|
199
209
|
* @default true
|
|
200
210
|
*/
|
|
201
211
|
showLanguage?: boolean;
|
|
212
|
+
/**
|
|
213
|
+
* Whether to show line numbers
|
|
214
|
+
* @default false
|
|
215
|
+
*/
|
|
216
|
+
showLineNumbers?: boolean;
|
|
217
|
+
/**
|
|
218
|
+
* Starting line number (when showLineNumbers is true)
|
|
219
|
+
* @default 1
|
|
220
|
+
*/
|
|
221
|
+
startingLineNumber?: number;
|
|
202
222
|
/**
|
|
203
223
|
* The HTML element that wraps the generated code block.
|
|
204
224
|
* @default 'pre'
|
package/dist/core.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { S as ShikiHighlighterProps, U as UseShikiHighlighter } from './component-
|
|
2
|
-
export { E as Element, H as HighlighterOptions, L as Language, T as Theme, a as Themes, i as isInlineCode, r as rehypeInlineCodeProperty } from './component-
|
|
1
|
+
import { S as ShikiHighlighterProps, U as UseShikiHighlighter } from './component-B4tYZaD8.js';
|
|
2
|
+
export { E as Element, H as HighlighterOptions, L as Language, T as Theme, a as Themes, i as isInlineCode, r as rehypeInlineCodeProperty } from './component-B4tYZaD8.js';
|
|
3
3
|
export { createHighlighterCore } from 'shiki/core';
|
|
4
4
|
export { createOnigurumaEngine } from 'shiki/engine/oniguruma';
|
|
5
5
|
export { createJavaScriptRegexEngine } from 'shiki/engine/javascript';
|
|
@@ -33,6 +33,6 @@ declare const useShikiHighlighter: UseShikiHighlighter;
|
|
|
33
33
|
* ShikiHighlighter component using a custom highlighter.
|
|
34
34
|
* Requires a highlighter to be provided.
|
|
35
35
|
*/
|
|
36
|
-
declare const ShikiHighlighter: ({ language, theme, delay, transformers, defaultColor, cssVariablePrefix, addDefaultStyles, style, langStyle, className, langClassName, showLanguage, children: code, as: Element, customLanguages, ...shikiOptions }: ShikiHighlighterProps) => React.ReactElement;
|
|
36
|
+
declare const ShikiHighlighter: ({ language, theme, delay, transformers, defaultColor, cssVariablePrefix, addDefaultStyles, style, langStyle, className, langClassName, showLanguage, showLineNumbers, startingLineNumber, children: code, as: Element, customLanguages, ...shikiOptions }: ShikiHighlighterProps) => React.ReactElement;
|
|
37
37
|
|
|
38
38
|
export { ShikiHighlighter, ShikiHighlighterProps, UseShikiHighlighter, ShikiHighlighter as default, useShikiHighlighter };
|
package/dist/core.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { S as ShikiHighlighterProps, U as UseShikiHighlighter } from './component-
|
|
2
|
-
export { E as Element, H as HighlighterOptions, L as Language, T as Theme, a as Themes, i as isInlineCode, r as rehypeInlineCodeProperty } from './component-
|
|
1
|
+
import { S as ShikiHighlighterProps, U as UseShikiHighlighter } from './component-B4tYZaD8.js';
|
|
2
|
+
export { E as Element, H as HighlighterOptions, L as Language, T as Theme, a as Themes, i as isInlineCode, r as rehypeInlineCodeProperty } from './component-B4tYZaD8.js';
|
|
3
3
|
import 'shiki';
|
|
4
4
|
import 'react';
|
|
5
5
|
import 'shiki/core';
|
|
@@ -20,6 +20,6 @@ declare const useShikiHighlighter: UseShikiHighlighter;
|
|
|
20
20
|
* ShikiHighlighter component using the full bundle.
|
|
21
21
|
* Includes all languages and themes for maximum compatibility.
|
|
22
22
|
*/
|
|
23
|
-
declare const ShikiHighlighter: ({ language, theme, delay, transformers, defaultColor, cssVariablePrefix, addDefaultStyles, style, langStyle, className, langClassName, showLanguage, children: code, as: Element, customLanguages, ...shikiOptions }: ShikiHighlighterProps) => React.ReactElement;
|
|
23
|
+
declare const ShikiHighlighter: ({ language, theme, delay, transformers, defaultColor, cssVariablePrefix, addDefaultStyles, style, langStyle, className, langClassName, showLanguage, showLineNumbers, startingLineNumber, children: code, as: Element, customLanguages, ...shikiOptions }: ShikiHighlighterProps) => React.ReactElement;
|
|
24
24
|
|
|
25
25
|
export { ShikiHighlighter, ShikiHighlighterProps, UseShikiHighlighter, ShikiHighlighter as default, useShikiHighlighter };
|
package/dist/index.js
CHANGED
package/dist/web.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { S as ShikiHighlighterProps, U as UseShikiHighlighter } from './component-
|
|
2
|
-
export { E as Element, H as HighlighterOptions, L as Language, T as Theme, a as Themes, i as isInlineCode, r as rehypeInlineCodeProperty } from './component-
|
|
1
|
+
import { S as ShikiHighlighterProps, U as UseShikiHighlighter } from './component-B4tYZaD8.js';
|
|
2
|
+
export { E as Element, H as HighlighterOptions, L as Language, T as Theme, a as Themes, i as isInlineCode, r as rehypeInlineCodeProperty } from './component-B4tYZaD8.js';
|
|
3
3
|
import 'shiki';
|
|
4
4
|
import 'react';
|
|
5
5
|
import 'shiki/core';
|
|
@@ -20,6 +20,6 @@ declare const useShikiHighlighter: UseShikiHighlighter;
|
|
|
20
20
|
* ShikiHighlighter component using the web bundle.
|
|
21
21
|
* Includes web-focused languages for balanced size and functionality.
|
|
22
22
|
*/
|
|
23
|
-
declare const ShikiHighlighter: ({ language, theme, delay, transformers, defaultColor, cssVariablePrefix, addDefaultStyles, style, langStyle, className, langClassName, showLanguage, children: code, as: Element, customLanguages, ...shikiOptions }: ShikiHighlighterProps) => React.ReactElement;
|
|
23
|
+
declare const ShikiHighlighter: ({ language, theme, delay, transformers, defaultColor, cssVariablePrefix, addDefaultStyles, style, langStyle, className, langClassName, showLanguage, showLineNumbers, startingLineNumber, children: code, as: Element, customLanguages, ...shikiOptions }: ShikiHighlighterProps) => React.ReactElement;
|
|
24
24
|
|
|
25
25
|
export { ShikiHighlighter, ShikiHighlighterProps, UseShikiHighlighter, ShikiHighlighter as default, useShikiHighlighter };
|
package/dist/web.js
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-shiki",
|
|
3
3
|
"description": "Syntax highlighter component for react using shiki",
|
|
4
|
-
"version": "0.7.
|
|
4
|
+
"version": "0.7.2",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Bassim Shahidy",
|
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
"main": "./dist/index.js",
|
|
28
28
|
"types": "./dist/index.d.ts",
|
|
29
29
|
"files": [
|
|
30
|
-
"dist"
|
|
30
|
+
"dist",
|
|
31
|
+
"src/lib/styles.css"
|
|
31
32
|
],
|
|
32
33
|
"exports": {
|
|
33
34
|
".": {
|
|
@@ -41,7 +42,8 @@
|
|
|
41
42
|
"./core": {
|
|
42
43
|
"types": "./dist/core.d.ts",
|
|
43
44
|
"default": "./dist/core.js"
|
|
44
|
-
}
|
|
45
|
+
},
|
|
46
|
+
"./css": "./src/lib/styles.css"
|
|
45
47
|
},
|
|
46
48
|
"peerDependencies": {
|
|
47
49
|
"react": ">= 16.8.0",
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
.relative {
|
|
2
|
+
position: relative;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.defaultStyles pre {
|
|
6
|
+
overflow: auto;
|
|
7
|
+
border-radius: 0.5rem;
|
|
8
|
+
padding-left: 1.5rem;
|
|
9
|
+
padding-right: 1.5rem;
|
|
10
|
+
padding-top: 1.25rem;
|
|
11
|
+
padding-bottom: 1.25rem;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.languageLabel {
|
|
15
|
+
position: absolute;
|
|
16
|
+
right: 0.75rem;
|
|
17
|
+
top: 0.5rem;
|
|
18
|
+
font-family: monospace;
|
|
19
|
+
font-size: 0.75rem;
|
|
20
|
+
letter-spacing: -0.05em;
|
|
21
|
+
color: rgba(107, 114, 128, 0.85);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.line-numbers::before {
|
|
25
|
+
counter-increment: line-number;
|
|
26
|
+
content: counter(line-number);
|
|
27
|
+
display: inline-flex;
|
|
28
|
+
justify-content: flex-end;
|
|
29
|
+
align-items: flex-start;
|
|
30
|
+
box-sizing: content-box;
|
|
31
|
+
min-width: var(--line-numbers-width, 2ch);
|
|
32
|
+
padding-left: var(--line-numbers-padding-left, 2ch);
|
|
33
|
+
padding-right: var(--line-numbers-padding-right, 2ch);
|
|
34
|
+
color: var(--line-numbers-foreground, rgba(107, 114, 128, 0.6));
|
|
35
|
+
font-size: var(--line-numbers-font-size, inherit);
|
|
36
|
+
font-weight: var(--line-numbers-font-weight, inherit);
|
|
37
|
+
line-height: var(--line-numbers-line-height, inherit);
|
|
38
|
+
font-family: var(--line-numbers-font-family, inherit);
|
|
39
|
+
opacity: var(--line-numbers-opacity, 1);
|
|
40
|
+
user-select: none;
|
|
41
|
+
pointer-events: none;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.has-line-numbers {
|
|
45
|
+
counter-reset: line-number calc(var(--line-start, 1) - 1);
|
|
46
|
+
--line-numbers-foreground: rgba(107, 114, 128, 0.5);
|
|
47
|
+
--line-numbers-width: 2ch;
|
|
48
|
+
--line-numbers-padding-left: 0ch;
|
|
49
|
+
--line-numbers-padding-right: 2ch;
|
|
50
|
+
--line-numbers-font-size: inherit;
|
|
51
|
+
--line-numbers-font-weight: inherit;
|
|
52
|
+
--line-numbers-line-height: inherit;
|
|
53
|
+
--line-numbers-font-family: inherit;
|
|
54
|
+
--line-numbers-opacity: 1;
|
|
55
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/lib/utils.ts","../src/lib/hook.ts","../src/lib/resolvers.ts","#style-inject:#style-inject","../src/lib/styles.css","../src/lib/component.tsx"],"sourcesContent":["import { visit } from 'unist-util-visit';\n\nimport type { TimeoutState, Element } from './types';\n\n/**\n * Rehype plugin to add an 'inline' property to <code> elements\n * Sets 'inline' property to true if the <code> is not within a <pre> tag\n *\n * Pass this plugin to the `rehypePlugins` prop of react-markdown\n * You can then access `inline` as a prop in components passed to react-markdown\n *\n * @example\n * <ReactMarkdown rehypePlugins={[rehypeInlineCodeProperty]} />\n */\nexport function rehypeInlineCodeProperty() {\n return (tree: any): undefined => {\n visit(tree, 'element', (node: Element, _index, parent: Element) => {\n if (node.tagName === 'code' && parent.tagName !== 'pre') {\n node.properties.inline = true;\n }\n });\n };\n}\n\n/**\n * Function to determine if code is inline based on the presence of line breaks\n *\n * @example\n * const isInline = node && isInlineCode(node: Element)\n */\nexport const isInlineCode = (node: Element): boolean => {\n const textContent = (node.children || [])\n .filter((child) => child.type === 'text')\n .map((child) => child.value)\n .join('');\n\n return !textContent.includes('\\n');\n};\n\n/**\n * Optionally throttles rapid sequential highlighting operations\n * Exported for testing in __tests__/throttleHighlighting.test.ts\n *\n * @example\n * const timeoutControl = useRef<TimeoutState>({\n * nextAllowedTime: 0,\n * timeoutId: undefined\n * });\n *\n * throttleHighlighting(highlightCode, timeoutControl, 1000);\n */\nexport const throttleHighlighting = (\n performHighlight: () => Promise<void>,\n timeoutControl: React.RefObject<TimeoutState>,\n throttleMs: number\n) => {\n const now = Date.now();\n clearTimeout(timeoutControl.current.timeoutId);\n\n const delay = Math.max(0, timeoutControl.current.nextAllowedTime - now);\n timeoutControl.current.timeoutId = setTimeout(() => {\n performHighlight().catch(console.error);\n timeoutControl.current.nextAllowedTime = now + throttleMs;\n }, delay);\n};\n","import {\n useEffect,\n useMemo,\n useRef,\n useState,\n type ReactNode,\n} from 'react';\n\nimport { dequal } from 'dequal/lite';\n\nimport { jsx, jsxs, Fragment } from 'react/jsx-runtime';\nimport { toJsxRuntime } from 'hast-util-to-jsx-runtime';\n\nimport type {\n CodeToHastOptions,\n CodeOptionsSingleTheme,\n CodeOptionsMultipleThemes,\n Highlighter,\n HighlighterCore,\n} from 'shiki';\n\nimport type { ShikiLanguageRegistration } from './extended-types';\n\nimport type {\n Language,\n Theme,\n HighlighterOptions,\n TimeoutState,\n Themes,\n} from './types';\n\nimport { throttleHighlighting } from './utils';\nimport { resolveLanguage, resolveTheme } from './resolvers';\n\nconst DEFAULT_THEMES: Themes = {\n light: 'github-light',\n dark: 'github-dark',\n};\n\n/**\n * Returns a deep-stable reference and a version counter that only changes when content changes.\n * Includes optimizations for primitive values and reference equality.\n */\nconst useStableOptions = <T>(value: T) => {\n const ref = useRef(value);\n const revision = useRef(0);\n\n // Fast-path for primitive values\n if (typeof value !== 'object' || value === null) {\n if (value !== ref.current) {\n ref.current = value;\n revision.current += 1;\n }\n return [value, revision.current] as const;\n }\n\n // Reference equality check before expensive deep comparison\n if (value !== ref.current) {\n if (!dequal(value, ref.current)) {\n ref.current = value;\n revision.current += 1;\n }\n }\n\n return [ref.current, revision.current] as const;\n};\n\n/**\n * Base hook for syntax highlighting using Shiki.\n * This is the core implementation used by all entry points.\n *\n * @param code - The code to highlight\n * @param lang - Language for highlighting\n * @param themeInput - Theme or themes to use\n * @param options - Highlighting options\n * @param createHighlighter - Factory function to create highlighter (internal use)\n */\nexport const useShikiHighlighter = (\n code: string,\n lang: Language,\n themeInput: Theme | Themes,\n createHighlighter: (\n langsToLoad: ShikiLanguageRegistration,\n themesToLoad: Theme[]\n ) => Promise<Highlighter | HighlighterCore>,\n options: HighlighterOptions = {}\n) => {\n const [highlightedCode, setHighlightedCode] =\n useState<ReactNode | null>(null);\n\n // Stabilize options, language and theme inputs to prevent unnecessary\n // re-renders or recalculations when object references change\n const [stableLang, langRev] = useStableOptions(lang);\n const [stableTheme, themeRev] = useStableOptions(themeInput);\n const [stableOpts, optsRev] = useStableOptions(options);\n\n const { languageId, langsToLoad } = useMemo(\n () => resolveLanguage(stableLang, stableOpts.customLanguages),\n [stableLang, stableOpts.customLanguages]\n );\n\n const { isMultiTheme, themeId, multiTheme, singleTheme, themesToLoad } =\n useMemo(() => resolveTheme(stableTheme), [stableTheme]);\n\n const timeoutControl = useRef<TimeoutState>({\n nextAllowedTime: 0,\n timeoutId: undefined,\n });\n\n const shikiOptions = useMemo<CodeToHastOptions>(() => {\n const { defaultColor, cssVariablePrefix, ...restOptions } =\n stableOpts;\n const languageOption = { lang: languageId };\n\n const themeOptions = isMultiTheme\n ? ({\n themes: multiTheme || DEFAULT_THEMES,\n defaultColor,\n cssVariablePrefix,\n } as CodeOptionsMultipleThemes)\n : ({\n theme: singleTheme || DEFAULT_THEMES.dark,\n } as CodeOptionsSingleTheme);\n\n return { ...languageOption, ...themeOptions, ...restOptions };\n }, [languageId, themeId, langRev, themeRev, optsRev]);\n\n useEffect(() => {\n let isMounted = true;\n\n const highlightCode = async () => {\n if (!languageId) return;\n\n // Use provided custom highlighter or create one using the factory\n const highlighter = stableOpts.highlighter\n ? stableOpts.highlighter\n : await createHighlighter(\n langsToLoad as ShikiLanguageRegistration,\n themesToLoad\n );\n\n // Check if language is loaded, fallback to plaintext if not\n const loadedLanguages = highlighter.getLoadedLanguages();\n const langToUse = loadedLanguages.includes(languageId) ? languageId : 'plaintext';\n const finalOptions = { ...shikiOptions, lang: langToUse };\n \n const hast = highlighter.codeToHast(code, finalOptions);\n\n if (isMounted) {\n setHighlightedCode(toJsxRuntime(hast, { jsx, jsxs, Fragment }));\n }\n };\n\n const { delay } = stableOpts;\n\n if (delay) {\n throttleHighlighting(highlightCode, timeoutControl, delay);\n } else {\n highlightCode().catch(console.error);\n }\n\n return () => {\n isMounted = false;\n clearTimeout(timeoutControl.current.timeoutId);\n };\n }, [\n code,\n shikiOptions,\n stableOpts.delay,\n stableOpts.highlighter,\n langsToLoad,\n themesToLoad,\n ]);\n\n return highlightedCode;\n};\n","import type { Language, Theme, Themes } from './types';\nimport type { ThemeRegistrationAny } from 'shiki/core';\nimport type { LanguageRegistration } from './extended-types';\n\n/**\n * Resolved languages and metadata\n */\ntype LanguageResult = {\n languageId: string;\n displayLanguageId: string | null;\n langsToLoad: Language;\n};\n\n/**\n * Resolves the language input to standardized IDs and objects for Shiki and UI display\n * @param lang The language input from props\n * @param customLanguages An array of custom textmate grammar objects or a single grammar object\n * @returns A LanguageResult object containing:\n * - languageId: The resolved language ID\n * - displayLanguageId: The display language ID\n * - langToLoad: The language object or string id to load\n */\nexport const resolveLanguage = (\n lang: Language,\n customLanguages?: LanguageRegistration | LanguageRegistration[]\n): LanguageResult => {\n const normalizedCustomLangs = customLanguages\n ? Array.isArray(customLanguages)\n ? customLanguages\n : [customLanguages]\n : [];\n\n // Language is null or empty string\n if (lang == null || (typeof lang === 'string' && !lang.trim())) {\n return {\n languageId: 'plaintext',\n displayLanguageId: 'plaintext',\n langsToLoad: undefined,\n };\n }\n\n // Language is custom\n if (typeof lang === 'object') {\n return {\n languageId: lang.name,\n displayLanguageId: lang.name || null,\n langsToLoad: lang,\n };\n }\n\n // Language is string\n const lowerLang = lang.toLowerCase();\n const matches = (str: string | undefined): boolean =>\n str?.toLowerCase() === lowerLang;\n\n // Check if the string identifies a provided custom language\n const customMatch = normalizedCustomLangs.find(\n (cl) =>\n matches(cl.name) ||\n matches(cl.scopeName) ||\n matches(cl.scopeName?.split('.').pop()) ||\n cl.aliases?.some(matches) ||\n cl.fileTypes?.some(matches)\n );\n\n if (customMatch) {\n return {\n languageId: customMatch.name || lang,\n displayLanguageId: lang,\n langsToLoad: customMatch,\n };\n }\n\n // For any other string, pass it through, \n // fallback is handled in highlighter factories\n return {\n languageId: lang,\n displayLanguageId: lang,\n langsToLoad: lang,\n };\n};\n\n/**\n * Resolved themes and metadata\n */\ninterface ThemeResult {\n isMultiTheme: boolean;\n themeId: Theme;\n multiTheme?: Themes | ThemeRegistrationAny | null;\n singleTheme?: Theme | undefined;\n themesToLoad: Theme[];\n}\n\n/**\n * Determines theme configuration and returns the resolved theme with metadata\n * @param themeInput - The theme input, either as a string name or theme object\n * @returns Object containing:\n * - isMultiTheme: If theme input is a multi-theme configuration\n * - themeId: Theme reference identifier\n * - multiTheme: The multi-theme config if it exists\n * - singleTheme: The single theme if it exists\n * - themesToLoad: The themes to load when creating the highlighter\n */\nexport function resolveTheme(themeInput: Theme | Themes): ThemeResult {\n const isTextmateTheme =\n typeof themeInput === 'object' &&\n 'tokenColors' in themeInput &&\n Array.isArray(themeInput.tokenColors);\n\n // Assume non textmate objects are multi theme configs\n const isMultiThemeConfig =\n typeof themeInput === 'object' &&\n themeInput !== null &&\n !isTextmateTheme;\n\n const validMultiThemeObj =\n typeof themeInput === 'object' &&\n themeInput !== null &&\n !isTextmateTheme &&\n Object.entries(themeInput).some(\n ([key, value]) =>\n key &&\n value &&\n key.trim() !== '' &&\n value !== '' &&\n (typeof value === 'string' || isTextmateTheme)\n );\n\n if (isMultiThemeConfig) {\n const themeId = validMultiThemeObj\n ? `multi-${Object.values(themeInput)\n .map(\n (theme) =>\n (typeof theme === 'string' ? theme : theme?.name) ||\n 'custom'\n )\n .sort()\n .join('-')}`\n : 'multi-default';\n\n // If config is invalid, return null to handle fallback in `buildShikiOptions()`\n return {\n isMultiTheme: true,\n themeId,\n multiTheme: validMultiThemeObj ? themeInput : null,\n themesToLoad: validMultiThemeObj ? Object.values(themeInput) : [],\n };\n }\n\n return {\n isMultiTheme: false,\n themeId:\n typeof themeInput === 'string'\n ? themeInput\n : themeInput?.name || 'custom',\n singleTheme: themeInput,\n themesToLoad: [themeInput],\n };\n}\n","\n export default function styleInject(css, { insertAt } = {}) {\n if (!css || typeof document === 'undefined') return\n \n const head = document.head || document.getElementsByTagName('head')[0]\n const style = document.createElement('style')\n style.type = 'text/css'\n \n if (insertAt === 'top') {\n if (head.firstChild) {\n head.insertBefore(style, head.firstChild)\n } else {\n head.appendChild(style)\n }\n } else {\n head.appendChild(style)\n }\n \n if (style.styleSheet) {\n style.styleSheet.cssText = css\n } else {\n style.appendChild(document.createTextNode(css))\n }\n }\n ","import styleInject from '#style-inject';styleInject(\".relative {\\n position: relative;\\n}\\n.defaultStyles pre {\\n overflow: auto;\\n border-radius: 0.5rem;\\n padding-left: 1.5rem;\\n padding-right: 1.5rem;\\n padding-top: 1.25rem;\\n padding-bottom: 1.25rem;\\n}\\n.languageLabel {\\n position: absolute;\\n right: 0.75rem;\\n top: 0.5rem;\\n font-family: monospace;\\n font-size: 0.75rem;\\n letter-spacing: -0.05em;\\n color: rgba(107, 114, 128, 0.85);\\n}\\n\")","import './styles.css';\nimport { clsx } from 'clsx';\nimport { resolveLanguage } from './resolvers';\n\nimport type {\n HighlighterOptions,\n Language,\n Theme,\n Themes,\n} from './types';\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 'pre'\n */\n as?: React.ElementType;\n}\n\n/**\n * Base ShikiHighlighter component factory.\n * This creates a component that uses the provided hook implementation.\n */\nexport const createShikiHighlighterComponent = (\n useShikiHighlighterImpl: (\n code: string,\n lang: Language,\n themeInput: Theme | Themes,\n options?: HighlighterOptions\n ) => React.ReactNode\n) => {\n return ({\n language,\n theme,\n delay,\n transformers,\n defaultColor,\n cssVariablePrefix,\n addDefaultStyles = true,\n style,\n langStyle,\n className,\n langClassName,\n showLanguage = true,\n children: code,\n as: Element = 'pre',\n customLanguages,\n ...shikiOptions\n }: ShikiHighlighterProps): React.ReactElement => {\n const options: HighlighterOptions = {\n delay,\n transformers,\n customLanguages,\n defaultColor,\n cssVariablePrefix,\n ...shikiOptions,\n };\n\n // Use resolveLanguage to get displayLanguageId directly\n const { displayLanguageId } = resolveLanguage(\n language,\n customLanguages\n );\n\n const highlightedCode = useShikiHighlighterImpl(\n code,\n language,\n theme,\n options\n );\n\n return (\n <Element\n data-testid=\"shiki-container\"\n className={clsx(\n 'relative',\n 'not-prose',\n addDefaultStyles && 'defaultStyles',\n className\n )}\n style={style}\n id=\"shiki-container\"\n >\n {showLanguage && displayLanguageId ? (\n <span\n className={clsx('languageLabel', langClassName)}\n style={langStyle}\n id=\"language-label\"\n >\n {displayLanguageId}\n </span>\n ) : null}\n {highlightedCode}\n </Element>\n );\n };\n};\n"],"mappings":"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","names":["jsx","jsxs"]}
|