react-shiki 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +68 -20
- package/dist/index.d.ts +57 -10
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/package.json +9 -9
package/README.md
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
# 🎨 [react-shiki](https://npmjs.com/react-shiki)
|
|
2
2
|
|
|
3
3
|
> [!NOTE]
|
|
4
|
-
> This
|
|
5
|
-
>
|
|
4
|
+
> This library is still in development, more features will
|
|
5
|
+
> continue to be implemented, and API may change.
|
|
6
|
+
> Contributions are welcome!
|
|
6
7
|
|
|
7
8
|
Performant client side syntax highlighting component + hook
|
|
8
|
-
for react
|
|
9
|
+
for react built with [Shiki](https://shiki.matsu.io/)
|
|
9
10
|
|
|
10
11
|
[See the demo page with highlighted code blocks showcasing several Shiki themes!](https://react-shiki.vercel.app/)
|
|
11
12
|
|
|
@@ -18,6 +19,8 @@ for react using [Shiki](https://shiki.matsu.io/)
|
|
|
18
19
|
- [`react-markdown`](#react-markdown)
|
|
19
20
|
- [Check if code is inline](#check-if-code-is-inline)
|
|
20
21
|
- [Custom themes](#custom-themes)
|
|
22
|
+
- [Custom languages](#custom-languages)
|
|
23
|
+
- [Preloading custom languages](#preloading-custom-languages)
|
|
21
24
|
- [Custom transformers](#custom-transformers)
|
|
22
25
|
- [Performance](#performance)
|
|
23
26
|
- [Throttling real-time highlighting](#throttling-real-time-highlighting)
|
|
@@ -29,22 +32,21 @@ for react using [Shiki](https://shiki.matsu.io/)
|
|
|
29
32
|
- 🖼️ Provides a `ShikiHighlighter` component for highlighting code as children,
|
|
30
33
|
as well as a `useShikiHighlighter` hook for more flexibility
|
|
31
34
|
- 🔐 No `dangerouslySetInnerHTML`, output from Shiki is parsed using `html-react-parser`
|
|
32
|
-
- 📦 Supports all Shiki languages and themes
|
|
33
|
-
- 🖌️ Full support for custom TextMate themes
|
|
35
|
+
- 📦 Supports all built-in Shiki languages and themes
|
|
36
|
+
- 🖌️ Full support for custom TextMate themes and languages
|
|
34
37
|
- 🔧 Supports passing custom Shiki transformers to the highlighter
|
|
35
|
-
- 🚰 Performant highlighting of streamed code
|
|
38
|
+
- 🚰 Performant highlighting of streamed code, with optional throttling
|
|
36
39
|
- 📚 Includes minimal default styles for code blocks
|
|
37
40
|
- 🚀 Shiki dynamically imports only the languages and themes used on a page,
|
|
38
41
|
optimizing for performance
|
|
39
42
|
- 🖥️ `ShikiHighlighter` component displays a language label for each code block
|
|
40
43
|
when `showLanguage` is set to `true` (default)
|
|
41
|
-
- 🎨
|
|
42
|
-
a `style` object or a `className`
|
|
44
|
+
- 🎨 Customizable styling of generated code blocks and language labels
|
|
43
45
|
|
|
44
46
|
## Installation
|
|
45
47
|
|
|
46
48
|
```bash
|
|
47
|
-
|
|
49
|
+
pnpm install react-shiki
|
|
48
50
|
```
|
|
49
51
|
|
|
50
52
|
## Usage
|
|
@@ -95,6 +97,7 @@ The component accepts several props in addition to language and theme:
|
|
|
95
97
|
- `style: object` - Inline style object to be passed to the component
|
|
96
98
|
- `langStyle: object` - Inline style object to be passed to the language label
|
|
97
99
|
- `langClassName: string` - Class names to be passed to the language label
|
|
100
|
+
- `customLanguages: LanguageRegistration[]` - Custom languages to be preloaded for highlighting
|
|
98
101
|
|
|
99
102
|
```tsx
|
|
100
103
|
function Houston() {
|
|
@@ -160,9 +163,9 @@ import { CodeHighlight } from "./CodeHighlight";
|
|
|
160
163
|
|
|
161
164
|
### Check if code is inline
|
|
162
165
|
|
|
163
|
-
There are two ways to check if a code block is inline:
|
|
164
|
-
`react-shiki` exports `isInlineCode
|
|
165
|
-
|
|
166
|
+
There are two built-in ways to check if a code block is inline, both provide the same result:
|
|
167
|
+
`react-shiki` exports `isInlineCode` which parses the `node`
|
|
168
|
+
prop to determine if the code is inline based on the presence of line breaks:
|
|
166
169
|
|
|
167
170
|
```tsx
|
|
168
171
|
const isInline: boolean | undefined = node ? isInlineCode(node) : undefined;
|
|
@@ -178,8 +181,9 @@ return !isInline ? (
|
|
|
178
181
|
);
|
|
179
182
|
```
|
|
180
183
|
|
|
181
|
-
`react-shiki` also exports `rehypeInlineCodeProperty`, a
|
|
182
|
-
to determine if code is inline
|
|
184
|
+
`react-shiki` also exports `rehypeInlineCodeProperty`, a rehype plugin that adds
|
|
185
|
+
an `inline` property to `react-markdown` to determine if code is inline based on
|
|
186
|
+
the presence of a `<pre>` tag as a parent of `<code>`.
|
|
183
187
|
It's passed as a rehype plugin to `react-markdown`:
|
|
184
188
|
|
|
185
189
|
```tsx
|
|
@@ -196,7 +200,7 @@ import { rehypeInlineCodeProperty } from "react-shiki";
|
|
|
196
200
|
</ReactMarkdown>;
|
|
197
201
|
```
|
|
198
202
|
|
|
199
|
-
|
|
203
|
+
Now `inline` can be accessed as a prop in the `CodeHighlight` component:
|
|
200
204
|
|
|
201
205
|
```tsx
|
|
202
206
|
const CodeHighlight = ({
|
|
@@ -208,11 +212,12 @@ const CodeHighlight = ({
|
|
|
208
212
|
}: CodeHighlightProps): JSX.Element => {
|
|
209
213
|
const match = className?.match(/language-(\w+)/);
|
|
210
214
|
const language = match ? match[1] : undefined;
|
|
215
|
+
const code = String(children).trim();
|
|
211
216
|
|
|
212
217
|
|
|
213
218
|
return !inline ? (
|
|
214
219
|
<ShikiHighlighter language={language} theme={"houston"} {...props}>
|
|
215
|
-
{
|
|
220
|
+
{code}
|
|
216
221
|
</ShikiHighlighter>
|
|
217
222
|
) : (
|
|
218
223
|
<code className={className} {...props}>
|
|
@@ -223,24 +228,67 @@ return !inline ? (
|
|
|
223
228
|
|
|
224
229
|
### Custom themes
|
|
225
230
|
|
|
231
|
+
Pass custom TextMate themes as a JSON object:
|
|
232
|
+
|
|
226
233
|
```tsx
|
|
227
|
-
import tokyoNight from '
|
|
234
|
+
import tokyoNight from '../styles/tokyo-night.json';
|
|
228
235
|
|
|
236
|
+
// component
|
|
229
237
|
<ShikiHighlighter language="tsx" theme={tokyoNight}>
|
|
230
|
-
{String(code)}
|
|
238
|
+
{String(code).trim()}
|
|
239
|
+
</ShikiHighlighter>;
|
|
240
|
+
|
|
241
|
+
// hook
|
|
242
|
+
const highlightedCode = useShikiHighlighter(code, "tsx", tokyoNight);
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Custom languages
|
|
246
|
+
|
|
247
|
+
Pass custom TextMate languages as a JSON object:
|
|
248
|
+
|
|
249
|
+
```tsx
|
|
250
|
+
import mcfunction from "../langs/mcfunction.tmLanguage.json"
|
|
251
|
+
|
|
252
|
+
// component
|
|
253
|
+
<ShikiHighlighter language={mcfunction} theme="github-dark" >
|
|
254
|
+
{String(code).trim()}
|
|
255
|
+
</ShikiHighlighter>;
|
|
256
|
+
|
|
257
|
+
// hook
|
|
258
|
+
const highlightedCode = useShikiHighlighter(code, mcfunction, "github-dark");
|
|
259
|
+
```
|
|
260
|
+
#### Preloading custom languages
|
|
261
|
+
|
|
262
|
+
For dynamic highlighting scenarios (like LLM chat apps) where language selection happens at runtime, preload custom languages to make them available when needed:
|
|
263
|
+
|
|
264
|
+
```tsx
|
|
265
|
+
import mcfunction from "../langs/mcfunction.tmLanguage.json"
|
|
266
|
+
import bosque from "../langs/bosque.tmLanguage.json"
|
|
267
|
+
|
|
268
|
+
// component
|
|
269
|
+
<ShikiHighlighter language={mcfunction} theme="github-dark" customLanguages={[mcfunction, bosque]} >
|
|
270
|
+
{String(code).trim()}
|
|
231
271
|
</ShikiHighlighter>;
|
|
272
|
+
|
|
273
|
+
// hook
|
|
274
|
+
const highlightedCode = useShikiHighlighter(code, mcfunction, "github-dark", { customLanguages: [mcfunction, bosque] });
|
|
232
275
|
```
|
|
233
276
|
|
|
234
277
|
### Custom transformers
|
|
235
278
|
|
|
236
279
|
```tsx
|
|
237
|
-
import { customTransformer } from '
|
|
280
|
+
import { customTransformer } from '../utils/shikiTransformers';
|
|
238
281
|
|
|
282
|
+
// component
|
|
239
283
|
<ShikiHighlighter
|
|
240
284
|
language="tsx"
|
|
241
|
-
transformers={[customTransformer]}
|
|
285
|
+
transformers={[customTransformer]}
|
|
286
|
+
>
|
|
242
287
|
{String(code).trim()}
|
|
243
288
|
</ShikiHighlighter>;
|
|
289
|
+
|
|
290
|
+
// hook
|
|
291
|
+
const highlightedCode = useShikiHighlighter(code, "tsx", "github-dark", [customTransformer]);
|
|
244
292
|
```
|
|
245
293
|
|
|
246
294
|
## Performance
|
package/dist/index.d.ts
CHANGED
|
@@ -1,13 +1,49 @@
|
|
|
1
1
|
import React, { ReactNode } from 'react';
|
|
2
|
-
import { BundledLanguage, SpecialLanguage, ThemeRegistration, BundledTheme
|
|
3
|
-
import {
|
|
2
|
+
import { LanguageRegistration as LanguageRegistration$1, ShikiTransformer, BundledLanguage, SpecialLanguage, ThemeRegistration, BundledTheme } from 'shiki';
|
|
3
|
+
import { Element, Root } from 'hast';
|
|
4
4
|
export { Element } from 'hast';
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Attribution:
|
|
8
|
+
* This code was written by github:hippotastic in expressive-code/expressive-code
|
|
9
|
+
* https://github.com/expressive-code/expressive-code/blob/main/packages/%40expressive-code/plugin-shiki/src/languages.ts
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
type IShikiRawRepository = LanguageRegistration$1['repository'];
|
|
13
|
+
type IShikiRawRule = IShikiRawRepository[keyof IShikiRawRepository];
|
|
14
|
+
type ILocation = IShikiRawRepository['$vscodeTextmateLocation'];
|
|
15
|
+
interface ILocatable {
|
|
16
|
+
readonly $vscodeTextmateLocation?: ILocation;
|
|
17
|
+
}
|
|
18
|
+
interface IRawRepositoryMap {
|
|
19
|
+
[name: string]: IRawRule;
|
|
20
|
+
}
|
|
21
|
+
type IRawRepository = IRawRepositoryMap & ILocatable;
|
|
22
|
+
interface IRawCapturesMap {
|
|
23
|
+
[captureId: string]: IRawRule;
|
|
24
|
+
}
|
|
25
|
+
type IRawCaptures = IRawCapturesMap & ILocatable;
|
|
26
|
+
interface IRawRule extends Omit<IShikiRawRule, 'applyEndPatternLast' | 'captures' | 'patterns'> {
|
|
27
|
+
readonly applyEndPatternLast?: boolean | number;
|
|
28
|
+
readonly captures?: IRawCaptures;
|
|
29
|
+
readonly comment?: string;
|
|
30
|
+
readonly patterns?: IRawRule[];
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* A less strict version of Shiki's `LanguageRegistration` interface that aligns better with
|
|
34
|
+
* actual grammars found in the wild. This version attempts to reduce the amount
|
|
35
|
+
* of type errors that would occur when importing and adding external grammars,
|
|
36
|
+
* while still being supported by the language processing code.
|
|
37
|
+
*/
|
|
38
|
+
interface LanguageRegistration extends Omit<LanguageRegistration$1, 'repository'> {
|
|
39
|
+
repository?: IRawRepository;
|
|
40
|
+
}
|
|
41
|
+
|
|
6
42
|
/**
|
|
7
43
|
* Languages for syntax highlighting.
|
|
8
44
|
* @see https://shiki.style/languages
|
|
9
45
|
*/
|
|
10
|
-
type Language = BundledLanguage | SpecialLanguage | (string & {}) | undefined;
|
|
46
|
+
type Language = BundledLanguage | LanguageRegistration | SpecialLanguage | (string & {}) | undefined;
|
|
11
47
|
/**
|
|
12
48
|
* A textmate theme object or a Shiki BundledTheme
|
|
13
49
|
* @see https://shiki.style/themes
|
|
@@ -26,6 +62,10 @@ type HighlighterOptions = {
|
|
|
26
62
|
* Custom Shiki transformers to apply to the highlighted code.
|
|
27
63
|
*/
|
|
28
64
|
transformers?: ShikiTransformer[];
|
|
65
|
+
/**
|
|
66
|
+
* Custom textmate grammar to be preloaded for highlighting.
|
|
67
|
+
*/
|
|
68
|
+
customLanguages?: LanguageRegistration | LanguageRegistration[];
|
|
29
69
|
};
|
|
30
70
|
|
|
31
71
|
/**
|
|
@@ -90,6 +130,10 @@ interface ShikiHighlighterProps extends HighlighterOptions {
|
|
|
90
130
|
* @default 'pre'
|
|
91
131
|
*/
|
|
92
132
|
as?: React.ElementType;
|
|
133
|
+
/**
|
|
134
|
+
* Custom languages to be preloaded for highlighting.
|
|
135
|
+
*/
|
|
136
|
+
customLanguages?: LanguageRegistration[];
|
|
93
137
|
}
|
|
94
138
|
/**
|
|
95
139
|
* ShikiHighlighter is a React component that provides syntax highlighting for code snippets.
|
|
@@ -106,7 +150,7 @@ interface ShikiHighlighterProps extends HighlighterOptions {
|
|
|
106
150
|
* </ShikiHighlighter>
|
|
107
151
|
* ```
|
|
108
152
|
*/
|
|
109
|
-
declare const ShikiHighlighter: ({ language, theme, delay, transformers, addDefaultStyles, style, langStyle, className, langClassName, showLanguage, children: code, as: Element, }: ShikiHighlighterProps) => React.ReactElement;
|
|
153
|
+
declare const ShikiHighlighter: ({ language, theme, delay, transformers, addDefaultStyles, style, langStyle, className, langClassName, showLanguage, children: code, as: Element, customLanguages, }: ShikiHighlighterProps) => React.ReactElement;
|
|
110
154
|
|
|
111
155
|
/**
|
|
112
156
|
* A React hook that provides syntax highlighting using Shiki.
|
|
@@ -116,24 +160,27 @@ declare const ShikiHighlighter: ({ language, theme, delay, transformers, addDefa
|
|
|
116
160
|
* const highlightedCode = useShikiHighlighter(code, language, theme, {
|
|
117
161
|
* transformers: [removeTabIndexFromPre],
|
|
118
162
|
* delay: 150
|
|
163
|
+
* customLanguages: ['bosque', 'mcfunction']
|
|
119
164
|
* });
|
|
120
165
|
*/
|
|
121
166
|
declare const useShikiHighlighter: (code: string, lang: Language, theme: Theme, options?: HighlighterOptions) => ReactNode;
|
|
122
167
|
|
|
123
168
|
/**
|
|
124
|
-
* Rehype plugin to add an 'inline' property to <code> elements
|
|
125
|
-
* Sets
|
|
169
|
+
* Rehype plugin to add an 'inline' property to <code> elements
|
|
170
|
+
* Sets 'inline' property to true if the <code> is not within a <pre> tag
|
|
126
171
|
*
|
|
127
|
-
* Pass this plugin to the `rehypePlugins` prop of
|
|
128
|
-
* You can then access `inline` as a prop
|
|
172
|
+
* Pass this plugin to the `rehypePlugins` prop of react-markdown
|
|
173
|
+
* You can then access `inline` as a prop in components passed to react-markdown
|
|
129
174
|
*
|
|
130
175
|
* @example
|
|
131
176
|
* <ReactMarkdown rehypePlugins={[rehypeInlineCodeProperty]} />
|
|
132
177
|
*/
|
|
133
178
|
declare function rehypeInlineCodeProperty(): (tree: Root) => undefined;
|
|
134
179
|
/**
|
|
135
|
-
* Function to determine if code is inline based on the presence of line breaks
|
|
136
|
-
*
|
|
180
|
+
* Function to determine if code is inline based on the presence of line breaks
|
|
181
|
+
*
|
|
182
|
+
* @example
|
|
183
|
+
* const isInline = node && isInlineCode(node: Element)
|
|
137
184
|
*/
|
|
138
185
|
declare const isInlineCode: (node: Element) => boolean;
|
|
139
186
|
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
function
|
|
2
|
-
`);import
|
|
3
|
-
`),
|
|
1
|
+
function L(e,{insertAt:r}={}){if(!e||typeof document>"u")return;let i=document.head||document.getElementsByTagName("head")[0],t=document.createElement("style");t.type="text/css",r==="top"&&i.firstChild?i.insertBefore(t,i.firstChild):i.appendChild(t),t.styleSheet?t.styleSheet.cssText=e:t.appendChild(document.createTextNode(e))}L(`.relative{position:relative}.defaultStyles pre{overflow:auto;border-radius:.5rem;padding:1.25rem 1.5rem}.languageLabel{position:absolute;right:.75rem;top:.5rem;font-family:monospace;font-size:.75rem;letter-spacing:-.05em;color:#6b7280d9}
|
|
2
|
+
`);import R from"react";import{clsx as b}from"clsx";import{useEffect as O,useRef as j,useState as M}from"react";import z from"html-react-parser";import{createHighlighter as H,createSingletonShorthands as $}from"shiki";import{visit as w}from"unist-util-visit";import{bundledLanguages as E,isSpecialLang as N}from"shiki";function P(){return function(e){w(e,"element",function(r,i,t){r.tagName==="code"&&t.tagName!=="pre"&&(r.properties.inline=!0)})}}var A=e=>!(e.children||[]).filter(i=>i.type==="text").map(i=>i.value).join("").includes(`
|
|
3
|
+
`),S={pre(e){return"properties"in e&&(e.properties.tabindex="-1"),e}},T=(e,r,i)=>{let t=Date.now();clearTimeout(r.current.timeoutId);let o=Math.max(0,r.current.nextAllowedTime-t);r.current.timeoutId=setTimeout(()=>{e().catch(console.error),r.current.nextAllowedTime=t+i},o)},u=(e,r=[])=>{if(e&&typeof e=="object")return{isCustom:!0,languageId:e.name,displayLanguageId:e.name,resolvedLanguage:e};if(typeof e=="string"){let i=e;if(e in E||N(e))return{isCustom:!1,languageId:e,displayLanguageId:i};let t=r.find(o=>o.fileTypes?.includes(e)||o.scopeName?.split(".")[1]===e||o.name?.toLowerCase()===e.toLowerCase());return t?{isCustom:!0,languageId:t.name,displayLanguageId:i,resolvedLanguage:t}:{isCustom:!1,languageId:"plaintext",displayLanguageId:i}}return{isCustom:!1,languageId:"plaintext",displayLanguageId:"plaintext"}};var B=$(H),I=new Map,x=(e,r,i,t={})=>{let[o,p]=M(null),d=typeof i=="string"?i:i.name,h=t.customLanguages?Array.isArray(t.customLanguages)?t.customLanguages:[t.customLanguages]:[],{isCustom:c,languageId:f,resolvedLanguage:g}=u(r,h),m=j({nextAllowedTime:0,timeoutId:void 0}),n=async(s,l)=>{let a=I.get(s);return a||(a=H({langs:[l],themes:[i]}),I.set(s,a)),a};return O(()=>{let s=!0,l=[S,...t.transformers||[]],a=async()=>{let y=await(c&&g?await n(`${g.name}--${d}`,g):B).codeToHtml(e,{lang:f,theme:i,transformers:l});s&&p(z(y))};return t.delay?T(a,m,t.delay):a().catch(console.error),()=>{s=!1,clearTimeout(m.current.timeoutId)}},[e,r]),o};var D=({language:e,theme:r,delay:i,transformers:t,addDefaultStyles:o=!0,style:p,langStyle:d,className:h,langClassName:c,showLanguage:f=!0,children:g,as:m="pre",customLanguages:n})=>{let s={delay:i,transformers:t,customLanguages:n},l=n?Array.isArray(n)?n:[n]:[],{isCustom:a,languageId:C,displayLanguageId:y,resolvedLanguage:v}=u(e,l),k=x(g,e,r,s);return R.createElement(m,{"data-testid":"shiki-container",className:b("relative","not-prose",o&&"defaultStyles",h),style:p,id:"shiki-container"},f&&e?R.createElement("span",{className:b("languageLabel",c),style:d,id:"language-label"},a?`${v?.scopeName.split(".")[1]}`:y||C):null,k)};export{D as default,A as isInlineCode,P as rehypeInlineCodeProperty,x as useShikiHighlighter};
|
|
4
4
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["#style-inject:#style-inject","../src/styles.css","../src/ShikiHighlighter.tsx","../src/useShiki.ts","../src/utils.ts"],"sourcesContent":["\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{position:relative}.defaultStyles pre{overflow:auto;border-radius:.5rem;padding:1.25rem 1.5rem}.languageLabel{position:absolute;right:.75rem;top:.5rem;font-family:monospace;font-size:.75rem;letter-spacing:-.05em;color:#6b7280d9}\\n\")","import './styles.css';\nimport React from 'react';\nimport { clsx } from 'clsx';\nimport { useShikiHighlighter } from './useShiki';\nimport type { Language, Theme, HighlighterOptions } from './types';\nimport type { ShikiTransformer } from 'shiki';\n\n/**\n * Props for the ShikiHighlighter component\n */\nexport interface ShikiHighlighterProps extends HighlighterOptions {\n /** \n * The programming language for syntax highlighting\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 for syntax highlighting\n * Supports Shiki and custom textmate themes\n * @see https://shiki.style/themes\n */\n theme: Theme;\n\n /** \n * The delay in milliseconds between streamed updates\n * Use this when highlighting code being streamed on the client\n * @default undefined (no delay)\n */\n delay?: number;\n\n /**\n * Pass custom Shiki transformers to the highlighter\n */\n transformers?: ShikiTransformer[];\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 * ShikiHighlighter is a React component that provides syntax highlighting for code snippets.\n * It uses Shiki to render beautiful, theme-based syntax highlighting with optimized performance.\n *\n * @example\n * ```tsx\n * <ShikiHighlighter \n * language=\"typescript\" \n * theme=\"github-dark\"\n * delay={100} // Optional throttling for streamed updates\n * >\n * {code}\n * </ShikiHighlighter>\n * ```\n */\nexport const ShikiHighlighter = ({\n language,\n theme,\n delay,\n transformers,\n addDefaultStyles = true,\n style,\n langStyle,\n className,\n langClassName,\n showLanguage = true,\n children: code,\n as: Element = 'pre',\n}: ShikiHighlighterProps): React.ReactElement => {\n const options: HighlighterOptions = { delay, transformers };\n const highlightedCode = useShikiHighlighter(code, language, theme, options);\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 && language ? (\n <span\n className={clsx('languageLabel', langClassName)}\n style={langStyle}\n id='language-label'\n >\n {language}\n </span>\n ) : null}\n {highlightedCode}\n </Element>\n );\n};\n","import { useEffect, useRef, useState, type ReactNode } from 'react';\nimport parse from 'html-react-parser';\n\nimport {\n createHighlighter,\n createSingletonShorthands,\n} from 'shiki';\n\nimport type {\n Language,\n Theme,\n HighlighterOptions,\n TimeoutState\n} from './types';\n\nimport { \n removeTabIndexFromPre,\n throttleHighlighting,\n resolvedLang\n} from './utils';\n\n\n/**\n * Create singleton shorthands which handle automatic loading\n * of the languages and themes, with support for custom themes.\n */\nconst highlighter = createSingletonShorthands(createHighlighter);\n\n\n/**\n * A React hook that provides syntax highlighting using Shiki.\n * Supports optional throttled highlights and custom themes.\n *\n * @example\n * const highlightedCode = useShikiHighlighter(code, language, theme, {\n * transformers: [removeTabIndexFromPre],\n * delay: 150\n * });\n */\nexport const useShikiHighlighter = (\n code: string,\n lang: Language,\n theme: Theme,\n options: HighlighterOptions = {}\n) => {\n const [highlightedCode, setHighlightedCode] = useState<ReactNode | null>(null);\n const language = resolvedLang(lang);\n\n const timeoutControl = useRef<TimeoutState>({\n nextAllowedTime: 0,\n timeoutId: undefined\n });\n\n useEffect(() => {\n let isMounted = true;\n\n const transformers = [removeTabIndexFromPre, ...(options.transformers || [])];\n\n const highlightCode = async () => {\n const html = await highlighter.codeToHtml(code, {\n lang: language,\n theme,\n transformers\n });\n\n if (isMounted) {\n setHighlightedCode(parse(html));\n }\n };\n\n const { delay } = options;\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 }, [code, lang]);\n\n return highlightedCode;\n};\n\n","import { visit } from 'unist-util-visit';\nimport { bundledLanguages, isSpecialLang, } from 'shiki';\nimport type { BundledLanguage, ShikiTransformer } from 'shiki';\nimport type { Element, Root } from 'hast';\nimport type { Language, TimeoutState } from './types';\n\n/**\n * Rehype plugin to add an 'inline' property to <code> elements.\n * Sets an 'inline' property to true if the <code> is not within a <pre> tag.\n *\n * Pass this plugin to the `rehypePlugins` prop of ReactMarkdown.\n * You can then access `inline` as a prop from ReactMarkdown.\n *\n * @example\n * <ReactMarkdown rehypePlugins={[rehypeInlineCodeProperty]} />\n */\nexport function rehypeInlineCodeProperty() {\n return function (tree: Root): undefined {\n visit(tree as any, 'element', function (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/**\n * Function to determine if code is inline based on the presence of line breaks.\n * Reports `inline = true` for single line fenced code blocks.\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/**\n * Shiki transformer to remove tabindex from <pre> elements.\n */\nexport const removeTabIndexFromPre: ShikiTransformer = {\n pre(node) {\n if ('properties' in node) {\n node.properties.tabindex = '-1';\n }\n return node;\n },\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.MutableRefObject<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\n/**\n * Resolve the requested language to a bundled language.\n * If the language is not special or not included in Shiki’s bundledLanguages,\n * fall back to \"plaintext\".\n *\n * @returns {BundledLanguage} The resolved language.\n */\nexport const resolvedLang = (lang: Language): BundledLanguage => {\n if (typeof lang === 'string') {\n if (!(lang in bundledLanguages) && !isSpecialLang(lang)) {\n return 'plaintext' as BundledLanguage;\n }\n return lang as BundledLanguage;\n }\n return lang as Language as BundledLanguage;\n};\n\nexport type { Element };\n"],"mappings":"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","names":["styleInject","css","insertAt","head","style","styleInject","React","clsx","useEffect","useRef","useState","parse","createHighlighter","createSingletonShorthands","visit","bundledLanguages","isSpecialLang","rehypeInlineCodeProperty","tree","node","_index","parent","isInlineCode","child","removeTabIndexFromPre","throttleHighlighting","performHighlight","timeoutControl","throttleMs","now","delay","resolvedLang","lang","highlighter","createSingletonShorthands","createHighlighter","useShikiHighlighter","code","lang","theme","options","highlightedCode","setHighlightedCode","useState","language","resolvedLang","timeoutControl","useRef","useEffect","isMounted","transformers","removeTabIndexFromPre","highlightCode","html","parse","delay","throttleHighlighting","ShikiHighlighter","language","theme","delay","transformers","addDefaultStyles","style","langStyle","className","langClassName","showLanguage","code","Element","highlightedCode","useShikiHighlighter","React","clsx"]}
|
|
1
|
+
{"version":3,"sources":["#style-inject:#style-inject","../src/styles.css","../src/ShikiHighlighter.tsx","../src/useShiki.ts","../src/utils.ts"],"sourcesContent":["\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{position:relative}.defaultStyles pre{overflow:auto;border-radius:.5rem;padding:1.25rem 1.5rem}.languageLabel{position:absolute;right:.75rem;top:.5rem;font-family:monospace;font-size:.75rem;letter-spacing:-.05em;color:#6b7280d9}\\n\")","import './styles.css';\nimport React from 'react';\nimport { clsx } from 'clsx';\nimport { useShikiHighlighter } from './useShiki';\nimport type { Language, Theme, HighlighterOptions } from './types';\nimport type { ShikiTransformer } from 'shiki';\nimport { resolveLanguage } from './utils';\nimport type { LanguageRegistration } from './customTypes';\n\n/**\n * Props for the ShikiHighlighter component\n */\nexport interface ShikiHighlighterProps extends HighlighterOptions {\n /**\n * The programming language for syntax highlighting\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 for syntax highlighting\n * Supports Shiki and custom textmate themes\n * @see https://shiki.style/themes\n */\n theme: Theme;\n\n /**\n * The delay in milliseconds between streamed updates\n * Use this when highlighting code being streamed on the client\n * @default undefined (no delay)\n */\n delay?: number;\n\n /**\n * Pass custom Shiki transformers to the highlighter\n */\n transformers?: ShikiTransformer[];\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 * Custom languages to be preloaded for highlighting.\n */\n customLanguages?: LanguageRegistration[];\n}\n\n/**\n * ShikiHighlighter is a React component that provides syntax highlighting for code snippets.\n * It uses Shiki to render beautiful, theme-based syntax highlighting with optimized performance.\n *\n * @example\n * ```tsx\n * <ShikiHighlighter\n * language=\"typescript\"\n * theme=\"github-dark\"\n * delay={100} // Optional throttling for streamed updates\n * >\n * {code}\n * </ShikiHighlighter>\n * ```\n */\nexport const ShikiHighlighter = ({\n language,\n theme,\n delay,\n transformers,\n addDefaultStyles = true,\n style,\n langStyle,\n className,\n langClassName,\n showLanguage = true,\n children: code,\n as: Element = 'pre',\n customLanguages,\n}: ShikiHighlighterProps): React.ReactElement => {\n const options: HighlighterOptions = {\n delay,\n transformers,\n customLanguages,\n };\n\n const normalizedCustomLanguages = customLanguages\n ? Array.isArray(customLanguages)\n ? customLanguages\n : [customLanguages]\n : [];\n\n const { isCustom, languageId, displayLanguageId, resolvedLanguage } =\n resolveLanguage(language, normalizedCustomLanguages);\n\n const highlightedCode = useShikiHighlighter(\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 && language ? (\n <span\n className={clsx('languageLabel', langClassName)}\n style={langStyle}\n id=\"language-label\"\n >\n {isCustom\n ? `${resolvedLanguage?.scopeName.split('.')[1]}`\n : displayLanguageId || languageId}\n </span>\n ) : null}\n {highlightedCode}\n </Element>\n );\n};\n","import { useEffect, useRef, useState, type ReactNode } from 'react';\n\nimport parse from 'html-react-parser';\n\nimport {\n createHighlighter,\n createSingletonShorthands,\n type Highlighter,\n} from 'shiki';\n\nimport type {\n LanguageRegistration,\n ShikiLanguageRegistration,\n} from './customTypes';\n\nimport type {\n Language,\n Theme,\n HighlighterOptions,\n TimeoutState,\n} from './types';\n\nimport {\n removeTabIndexFromPre,\n throttleHighlighting,\n resolveLanguage,\n} from './utils';\n\n// Use Shiki managed singleton for bundled languages, create a fresh instance for custom languages\nconst highlighter = createSingletonShorthands(createHighlighter);\nconst customHighlighterCache = new Map<string, Promise<Highlighter>>();\n\n/**\n * A React hook that provides syntax highlighting using Shiki.\n * Supports optional throttled highlights and custom themes.\n *\n * @example\n * const highlightedCode = useShikiHighlighter(code, language, theme, {\n * transformers: [removeTabIndexFromPre],\n * delay: 150\n * customLanguages: ['bosque', 'mcfunction']\n * });\n */\nexport const useShikiHighlighter = (\n code: string,\n lang: Language,\n theme: Theme,\n options: HighlighterOptions = {}\n) => {\n const [highlightedCode, setHighlightedCode] =\n useState<ReactNode | null>(null);\n\n // Setup themeKey for highlighter caching, checks if theme is string (builtin) or object (custom)\n const themeKey = typeof theme === 'string' ? theme : theme.name;\n\n const normalizedCustomLanguages: LanguageRegistration[] =\n options.customLanguages\n ? Array.isArray(options.customLanguages)\n ? options.customLanguages\n : [options.customLanguages]\n : [];\n\n const { isCustom, languageId, resolvedLanguage } = resolveLanguage(\n lang,\n normalizedCustomLanguages\n );\n\n const timeoutControl = useRef<TimeoutState>({\n nextAllowedTime: 0,\n timeoutId: undefined,\n });\n\n const getCachedCustomHighlighter = async (\n cacheKey: string,\n customLang: LanguageRegistration | typeof lang\n ) => {\n let instance = customHighlighterCache.get(cacheKey);\n if (!instance) {\n instance = createHighlighter({\n langs: [customLang as ShikiLanguageRegistration],\n themes: [theme],\n });\n customHighlighterCache.set(cacheKey, instance);\n }\n return instance;\n };\n\n useEffect(() => {\n let isMounted = true;\n\n const transformers = [\n removeTabIndexFromPre,\n ...(options.transformers || []),\n ];\n\n const highlightCode = async () => {\n const codeHighlighter =\n isCustom && resolvedLanguage\n ? await getCachedCustomHighlighter(\n `${resolvedLanguage.name}--${themeKey}`,\n resolvedLanguage\n )\n : highlighter;\n\n const html = await codeHighlighter.codeToHtml(code, {\n lang: languageId,\n theme,\n transformers,\n });\n\n if (isMounted) {\n setHighlightedCode(parse(html));\n }\n };\n\n if (options.delay) {\n throttleHighlighting(highlightCode, timeoutControl, options.delay);\n } else {\n highlightCode().catch(console.error);\n }\n\n return () => {\n isMounted = false;\n clearTimeout(timeoutControl.current.timeoutId);\n };\n }, [code, lang]);\n\n return highlightedCode;\n};\n","import { visit } from 'unist-util-visit';\nimport { bundledLanguages, isSpecialLang } from 'shiki';\nimport type { ShikiTransformer } from 'shiki';\nimport type { Element, Root } from 'hast';\nimport type { Language, TimeoutState } from './types';\nimport type { LanguageRegistration } from './customTypes';\n\n/**\n * Type for the HAST node, use to type `node` from react-markdown\n */\nexport type { Element };\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 function (tree: Root): undefined {\n visit(\n tree as any,\n 'element',\n function (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/**\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 * Shiki transformer to remove tabindex from <pre> elements\n *\n * Consider retaining tabindex for WCAG 3.1 compliance, scrollable code blocks should be focusable\n * https://github.com/shikijs/shiki/issues/428\n * https://www.w3.org/WAI/standards-guidelines/act/rules/0ssw9k/proposed/\n */\nexport const removeTabIndexFromPre: ShikiTransformer = {\n pre(node) {\n if ('properties' in node) {\n node.properties.tabindex = '-1';\n }\n return node;\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.MutableRefObject<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\ntype ResolvedLanguage = {\n isCustom: boolean;\n languageId: string;\n displayLanguageId?: string;\n resolvedLanguage?: LanguageRegistration;\n};\n\n/**\n * Determines whether a language is custom or built-in, normalizes\n * the language identifier and returns an object with metadata\n *\n * @param lang - The language identifier, either as a string name or language object\n * @param customLanguages - Optional array of custom language definitions\n * @returns Object containing:\n * - isCustom: Whether the language requires custom highlighting\n * - languageId: The normalized language identifier\n * - displayLanguageId: The display name for language label\n * - resolvedLanguage: The full language definition if custom\n */\nexport const resolveLanguage = (\n lang: Language,\n customLanguages: LanguageRegistration[] = []\n): ResolvedLanguage => {\n // Language is custom but not preloaded\n if (lang && typeof lang === 'object') {\n return {\n isCustom: true,\n languageId: lang.name,\n displayLanguageId: lang.name,\n resolvedLanguage: lang,\n };\n }\n\n // Language is a string\n if (typeof lang === 'string') {\n const displayLanguageId = lang;\n\n // Check if its built-in\n if (lang in bundledLanguages || isSpecialLang(lang)) {\n return {\n isCustom: false,\n languageId: lang,\n displayLanguageId,\n };\n }\n\n // Check if it matches a preloaded custom language\n const customMatch = customLanguages.find(\n (cl) =>\n cl.fileTypes?.includes(lang) ||\n cl.scopeName?.split('.')[1] === lang ||\n cl.name?.toLowerCase() === lang.toLowerCase()\n );\n\n if (customMatch) {\n return {\n isCustom: true,\n languageId: customMatch.name,\n displayLanguageId,\n resolvedLanguage: customMatch,\n };\n }\n\n // If unknown, highlight in plaintext but display unknown language\n return {\n isCustom: false,\n languageId: 'plaintext',\n displayLanguageId,\n };\n }\n\n // Fallback\n return {\n isCustom: false,\n languageId: 'plaintext',\n displayLanguageId: 'plaintext',\n };\n};\n"],"mappings":"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","names":["styleInject","css","insertAt","head","style","styleInject","React","clsx","useEffect","useRef","useState","parse","createHighlighter","createSingletonShorthands","visit","bundledLanguages","isSpecialLang","rehypeInlineCodeProperty","tree","node","_index","parent","isInlineCode","child","removeTabIndexFromPre","throttleHighlighting","performHighlight","timeoutControl","throttleMs","now","delay","resolveLanguage","lang","customLanguages","displayLanguageId","customMatch","cl","highlighter","createSingletonShorthands","createHighlighter","customHighlighterCache","useShikiHighlighter","code","lang","theme","options","highlightedCode","setHighlightedCode","useState","themeKey","normalizedCustomLanguages","isCustom","languageId","resolvedLanguage","resolveLanguage","timeoutControl","useRef","getCachedCustomHighlighter","cacheKey","customLang","instance","useEffect","isMounted","transformers","removeTabIndexFromPre","highlightCode","html","parse","throttleHighlighting","ShikiHighlighter","language","theme","delay","transformers","addDefaultStyles","style","langStyle","className","langClassName","showLanguage","code","Element","customLanguages","options","normalizedCustomLanguages","isCustom","languageId","displayLanguageId","resolvedLanguage","resolveLanguage","highlightedCode","useShikiHighlighter","React","clsx"]}
|
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.
|
|
4
|
+
"version": "0.4.0",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Bassim Shahidy",
|
|
@@ -43,19 +43,19 @@
|
|
|
43
43
|
"@types/jest": "^29.5.14",
|
|
44
44
|
"clsx": "^2.1.1",
|
|
45
45
|
"html-react-parser": "^5.1.12",
|
|
46
|
-
"shiki": "^
|
|
46
|
+
"shiki": "^3.0.0",
|
|
47
47
|
"unist-util-visit": "^5.0.0"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
|
-
"@testing-library/jest-dom": "^6.
|
|
51
|
-
"@testing-library/react": "^14.
|
|
50
|
+
"@testing-library/jest-dom": "^6.6.3",
|
|
51
|
+
"@testing-library/react": "^14.3.1",
|
|
52
52
|
"@types/hast": "^3.0.4",
|
|
53
|
-
"@types/node": "22.
|
|
54
|
-
"@types/react": "^18.3.
|
|
55
|
-
"@vitejs/plugin-react": "^4.
|
|
53
|
+
"@types/node": "22.13.4",
|
|
54
|
+
"@types/react": "^18.3.18",
|
|
55
|
+
"@vitejs/plugin-react": "^4.3.4",
|
|
56
56
|
"jsdom": "^22.1.0",
|
|
57
|
-
"tsup": "^8.
|
|
58
|
-
"vitest": "^0.34.
|
|
57
|
+
"tsup": "^8.3.6",
|
|
58
|
+
"vitest": "^0.34.6"
|
|
59
59
|
},
|
|
60
60
|
"scripts": {
|
|
61
61
|
"dev": "tsup --watch",
|