react-shiki 0.10.1 → 0.11.1
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/LICENSE +1 -1
- package/README.md +249 -118
- package/dist/{component-CYq7SnJ0.mjs → component-Cf8Topxy.mjs} +165 -81
- package/dist/component-Cf8Topxy.mjs.map +1 -0
- package/dist/{component-CjpuWrgO.d.mts → component-DlK6KonE.d.mts} +66 -23
- package/dist/core.d.mts +5 -7
- package/dist/core.mjs +3 -3
- package/dist/core.mjs.map +1 -1
- package/dist/factory-DySLHBEo.mjs +17 -0
- package/dist/factory-DySLHBEo.mjs.map +1 -0
- package/dist/index.d.mts +5 -7
- package/dist/index.mjs +8 -16
- package/dist/index.mjs.map +1 -1
- package/dist/style.css +1 -46
- package/dist/web.d.mts +5 -7
- package/dist/web.mjs +9 -17
- package/dist/web.mjs.map +1 -1
- package/package.json +15 -15
- package/dist/component-CYq7SnJ0.mjs.map +0 -1
- package/src/styles/features.css +0 -25
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
# 🎨 react-shiki
|
|
2
2
|
|
|
3
|
-
|
|
4
3
|
A performant client-side syntax highlighting component and hook for React, built with [Shiki](https://shiki.matsu.io/).
|
|
5
4
|
|
|
6
|
-
[See the demo page with highlighted code blocks showcasing several Shiki themes!](https://react-shiki.vercel.app/)
|
|
7
|
-
|
|
8
5
|
<!--toc:start-->
|
|
9
|
-
- [🎨 react-shiki](
|
|
6
|
+
- [🎨 react-shiki](#-react-shiki)
|
|
10
7
|
- [Features](#features)
|
|
11
8
|
- [Installation](#installation)
|
|
12
9
|
- [Usage](#usage)
|
|
@@ -15,11 +12,14 @@ A performant client-side syntax highlighting component and hook for React, built
|
|
|
15
12
|
- [`react-shiki/web` (Web Bundle)](#react-shikiweb-web-bundle)
|
|
16
13
|
- [`react-shiki/core` (Minimal Bundle)](#react-shikicore-minimal-bundle)
|
|
17
14
|
- [RegExp Engines](#regexp-engines)
|
|
18
|
-
- [
|
|
19
|
-
- [
|
|
20
|
-
|
|
15
|
+
- [Full and Web Bundles](#full-and-web-bundles)
|
|
16
|
+
- [Custom Highlighters (Core Bundle)](#custom-highlighters-core-bundle)
|
|
17
|
+
- [JavaScript Raw Engine](#javascript-raw-engine)
|
|
18
|
+
- [Output Formats](#output-formats)
|
|
21
19
|
- [Configuration](#configuration)
|
|
22
20
|
- [Common Configuration Options](#common-configuration-options)
|
|
21
|
+
- [react-shiki Options](#react-shiki-options)
|
|
22
|
+
- [Shiki Pass-through Options](#shiki-pass-through-options)
|
|
23
23
|
- [Component-specific Props](#component-specific-props)
|
|
24
24
|
- [Embedded Language Highlighting](#embedded-language-highlighting)
|
|
25
25
|
- [Multi-theme Support](#multi-theme-support)
|
|
@@ -37,15 +37,14 @@ A performant client-side syntax highlighting component and hook for React, built
|
|
|
37
37
|
- [Handling Inline Code](#handling-inline-code)
|
|
38
38
|
- [Performance](#performance)
|
|
39
39
|
- [Throttling Real-time Highlighting](#throttling-real-time-highlighting)
|
|
40
|
-
- [Output Format Optimization](#output-format-optimization)
|
|
41
40
|
<!--toc:end-->
|
|
42
41
|
|
|
43
42
|
## Features
|
|
44
43
|
|
|
45
44
|
- 🖼️ Provides both a `ShikiHighlighter` component and a `useShikiHighlighter` hook for more flexibility
|
|
46
|
-
- 🔐 Flexible output: Choose between React elements (no `dangerouslySetInnerHTML`) or HTML strings
|
|
47
|
-
- 📦 Multiple bundle options: Full bundle (~1.2MB gz), web bundle (~
|
|
48
|
-
- 🖌️ Full support for custom TextMate themes and languages
|
|
45
|
+
- 🔐 Flexible output: Choose between React elements (no `dangerouslySetInnerHTML`) or HTML strings
|
|
46
|
+
- 📦 Multiple bundle options: Full bundle (~1.2MB gz), web bundle (~707KB gz), or minimal core bundle for fine-grained bundle control
|
|
47
|
+
- 🖌️ Full support for custom TextMate themes and languages, passed as objects or dynamic imports
|
|
49
48
|
- 🧬 Automatic highlighting of embedded languages (e.g. TypeScript fenced inside Markdown) via Shiki's `guessEmbeddedLanguages`
|
|
50
49
|
- 🔧 Supports passing custom Shiki transformers to the highlighter, in addition to all other options supported by `codeToHast`
|
|
51
50
|
- 🚰 Performant highlighting of streamed code, with optional throttling
|
|
@@ -99,8 +98,8 @@ function CodeBlock({ code, language }) {
|
|
|
99
98
|
```tsx
|
|
100
99
|
import ShikiHighlighter from 'react-shiki';
|
|
101
100
|
```
|
|
102
|
-
- **Size**: ~6.4MB minified, ~1.2MB gzipped (
|
|
103
|
-
- **Languages**: All Shiki languages and themes
|
|
101
|
+
- **Size**: ~6.4MB minified, ~1.2MB gzipped (react-shiki itself: ~9KB minified, ~4KB gzipped)
|
|
102
|
+
- **Languages**: All Shiki [languages](https://shiki.style/languages) and [themes](https://shiki.style/themes)
|
|
104
103
|
- **Exported engines**: `createJavaScriptRegexEngine`, `createJavaScriptRawEngine`
|
|
105
104
|
- **Use case**: Unknown language requirements, maximum language support
|
|
106
105
|
- **Setup**: Zero configuration required
|
|
@@ -109,8 +108,8 @@ import ShikiHighlighter from 'react-shiki';
|
|
|
109
108
|
```tsx
|
|
110
109
|
import ShikiHighlighter from 'react-shiki/web';
|
|
111
110
|
```
|
|
112
|
-
- **Size**: ~3.8MB minified, ~707KB gzipped (
|
|
113
|
-
- **Languages**: Web-focused languages (HTML, CSS, JS, TS, JSON, Markdown, Vue, JSX, Svelte)
|
|
111
|
+
- **Size**: ~3.8MB minified, ~707KB gzipped (react-shiki itself: ~9KB minified, ~4KB gzipped)
|
|
112
|
+
- **Languages**: [Web-focused languages](https://shiki.style/guide/bundles#shiki-bundle-web) (HTML, CSS, JS, TS, JSON, Markdown, Vue, JSX, Svelte)
|
|
114
113
|
- **Exported engines**: `createJavaScriptRegexEngine`, `createJavaScriptRawEngine`
|
|
115
114
|
- **Use case**: Web applications with balanced size/functionality
|
|
116
115
|
- **Setup**: Drop-in replacement for main entry point
|
|
@@ -135,7 +134,7 @@ const highlighter = await createHighlighterCore({
|
|
|
135
134
|
{code}
|
|
136
135
|
</ShikiHighlighter>
|
|
137
136
|
```
|
|
138
|
-
- **Size**: ~
|
|
137
|
+
- **Size**: ~9KB minified (~4KB gzipped) + your imported themes/languages
|
|
139
138
|
- **Languages**: User-defined via custom highlighter
|
|
140
139
|
- **Use case**: Production apps requiring maximum bundle control
|
|
141
140
|
- **Setup**: Requires custom highlighter configuration
|
|
@@ -144,40 +143,57 @@ const highlighter = await createHighlighterCore({
|
|
|
144
143
|
### RegExp Engines
|
|
145
144
|
|
|
146
145
|
Shiki offers three built-in engines for syntax highlighting:
|
|
147
|
-
- **Oniguruma** - Default engine using compiled WebAssembly, offers maximum language support
|
|
148
|
-
- **JavaScript RegExp** - Smaller bundle, faster startup, compiles patterns on-the-fly, recommended for client-side highlighting
|
|
149
|
-
- **JavaScript Raw** - For [pre-compiled languages](https://shiki.style/guide/regex-engines#pre-compiled-languages), skips transpilation step for best performance
|
|
150
146
|
|
|
151
|
-
|
|
147
|
+
| Engine | Payload (gzip) | Startup | Grammar support |
|
|
148
|
+
| --- | --- | --- | --- |
|
|
149
|
+
| **Oniguruma** (default) | ~150 KB (466 KB WASM) | fetch + compile WASM | Reference engine, all grammars |
|
|
150
|
+
| **JavaScript RegExp** | ~20 KB | none, plain JS | All built-in languages ([compatibility table](https://shiki.style/references/engine-js-compat)); [strict by default](#full-and-web-bundles) |
|
|
151
|
+
| **JavaScript Raw** | ~1 KB | none | [Pre-compiled grammars](https://shiki.style/guide/regex-engines#pre-compiled-languages) only, has a [known bug](https://github.com/shikijs/shiki/issues/918) |
|
|
152
152
|
|
|
153
|
-
|
|
153
|
+
Payload sizes measured against shiki 4.3.0: `onig.wasm` is 466 KB (149 KB gzipped), the JavaScript engine's pattern transpiler ([oniguruma-to-es](https://github.com/slevithan/oniguruma-to-es)) is 57 KB (20 KB gzipped), and the raw engine skips the transpiler entirely. Beyond the smaller download, the JavaScript engine also starts instantly (no WASM fetch and compile) and [runs patterns as native JavaScript RegExp](https://shiki.style/guide/regex-engines#javascript-regexp-engine), which is faster for some languages.
|
|
154
154
|
|
|
155
|
-
|
|
156
|
-
import {
|
|
157
|
-
useShikiHighlighter,
|
|
158
|
-
createJavaScriptRegexEngine,
|
|
159
|
-
createJavaScriptRawEngine
|
|
160
|
-
} from 'react-shiki';
|
|
155
|
+
#### Full and Web Bundles
|
|
161
156
|
|
|
162
|
-
|
|
157
|
+
The full and web bundles use Oniguruma by default. You can swap to the JavaScript engine by passing `'javascript'` to `engine`. react-shiki creates and caches the engine internally, no need to import it, no WASM fetch with `'javascript'`, and it's safe to pass inline:
|
|
158
|
+
|
|
159
|
+
```tsx
|
|
160
|
+
// Hook
|
|
163
161
|
const highlightedCode = useShikiHighlighter(code, 'typescript', 'github-dark', {
|
|
164
|
-
engine:
|
|
162
|
+
engine: 'javascript'
|
|
165
163
|
});
|
|
166
164
|
|
|
167
|
-
// Component
|
|
168
|
-
|
|
169
|
-
<ShikiHighlighter
|
|
170
|
-
language="typescript"
|
|
171
|
-
theme="github-dark"
|
|
172
|
-
engine={createJavaScriptRawEngine()}
|
|
173
|
-
>
|
|
165
|
+
// Component
|
|
166
|
+
<ShikiHighlighter language="typescript" theme="github-dark" engine="javascript">
|
|
174
167
|
{code}
|
|
175
168
|
</ShikiHighlighter>
|
|
176
169
|
```
|
|
177
170
|
|
|
178
|
-
|
|
171
|
+
> [!NOTE]
|
|
172
|
+
> The underlying highlighter is a singleton created on the first highlight, so pass the same engine at every call site; mixed engines resolve to whichever highlighted first.
|
|
173
|
+
|
|
174
|
+
To customize engine configuration beyond the named defaults, pass an engine instance. **Create it once at module scope**; this avoids recreating new instances on every render, which would defeat memoization:
|
|
175
|
+
|
|
176
|
+
```tsx
|
|
177
|
+
import { useShikiHighlighter, createJavaScriptRegexEngine } from 'react-shiki';
|
|
178
|
+
|
|
179
|
+
// Ensure this is at the module scope: created once, stable across renders
|
|
180
|
+
const engine = createJavaScriptRegexEngine({ forgiving: true });
|
|
181
|
+
|
|
182
|
+
const highlightedCode = useShikiHighlighter(code, 'typescript', 'github-dark', {
|
|
183
|
+
engine
|
|
184
|
+
});
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
The JavaScript engine is strict by default: it throws when a grammar uses patterns it can't convert. `forgiving: true` skips those patterns for best-effort output. It also accepts `target` and `regexConstructor`, see [Shiki - RegExp Engines](https://shiki.style/guide/regex-engines) for details.
|
|
188
|
+
|
|
189
|
+
The JavaScript Raw engine works the same way, but only with [pre-compiled languages](https://shiki.style/guide/regex-engines#pre-compiled-languages) (`@shikijs/langs-precompiled`). The full and web bundles don't support the raw engine because they bundle standard grammars, you need a [custom highlighter](#custom-highlighters-core-bundle).
|
|
179
190
|
|
|
180
|
-
|
|
191
|
+
> [!IMPORTANT]
|
|
192
|
+
> **Heads up:** in a future release, react-shiki's default engine will be swapped from Oniguruma WASM to the JavaScript engine (with [`forgiving`](https://shiki.style/guide/regex-engines#use-with-unsupported-languages) enabled by default).
|
|
193
|
+
|
|
194
|
+
#### Custom Highlighters (Core Bundle)
|
|
195
|
+
|
|
196
|
+
Named engines (`'javascript'`, `'oniguruma'`) don't apply to the core bundle since you create the highlighter with `createHighlighterCore`; instead, use `createJavaScriptRegexEngine` or `createOnigurumaEngine` to create a custom engine instance:
|
|
181
197
|
|
|
182
198
|
```tsx
|
|
183
199
|
import {
|
|
@@ -193,42 +209,130 @@ const highlighter = await createHighlighterCore({
|
|
|
193
209
|
});
|
|
194
210
|
```
|
|
195
211
|
|
|
196
|
-
|
|
212
|
+
##### JavaScript Raw Engine
|
|
197
213
|
|
|
198
|
-
The JavaScript
|
|
214
|
+
The JavaScript Raw engine is the smallest option at only ~1 KB, but requires [pre-compiled languages](https://shiki.style/guide/regex-engines#pre-compiled-languages), and faces a known upstream bug -- [shikijs/shiki:issue#918](https://github.com/shikijs/shiki/issues/918) -- causing some languages (Python, HTML, Perl, and YAML among those reported) to highlight incorrectly.
|
|
199
215
|
|
|
200
216
|
```tsx
|
|
201
|
-
|
|
217
|
+
import {
|
|
218
|
+
useShikiHighlighter,
|
|
219
|
+
createHighlighterCore,
|
|
220
|
+
createJavaScriptRawEngine,
|
|
221
|
+
} from 'react-shiki/core';
|
|
222
|
+
|
|
223
|
+
// Module scope: raw engine + pre-compiled grammars, smallest and fastest
|
|
224
|
+
const highlighter = await createHighlighterCore({
|
|
225
|
+
themes: [import('@shikijs/themes/github-dark')],
|
|
226
|
+
langs: [import('@shikijs/langs-precompiled/typescript')],
|
|
227
|
+
engine: createJavaScriptRawEngine()
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
const highlightedCode = useShikiHighlighter(code, 'typescript', 'github-dark', {
|
|
231
|
+
highlighter
|
|
232
|
+
});
|
|
202
233
|
```
|
|
203
234
|
|
|
204
|
-
|
|
235
|
+
Be sure to verify your language highlights correctly if you choose to use the raw engine ([shikijs/shiki:issue#918](https://github.com/shikijs/shiki/issues/918)).
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
## Output Formats
|
|
205
239
|
|
|
240
|
+
`react-shiki` can return highlighted code in three formats:
|
|
241
|
+
|
|
242
|
+
**React Nodes (Default)** - Rendered as React elements, no `dangerouslySetInnerHTML` required
|
|
243
|
+
```tsx
|
|
244
|
+
// Hook
|
|
245
|
+
const highlightedCode = useShikiHighlighter(code, "tsx", "github-dark");
|
|
246
|
+
|
|
247
|
+
// Component
|
|
248
|
+
<ShikiHighlighter language="tsx" theme="github-dark">
|
|
249
|
+
{code}
|
|
250
|
+
</ShikiHighlighter>
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
**HTML String** - Rendered via `dangerouslySetInnerHTML`
|
|
254
|
+
```tsx
|
|
255
|
+
// Hook (returns HTML string, use dangerouslySetInnerHTML to render)
|
|
256
|
+
const highlightedCode = useShikiHighlighter(code, "tsx", "github-dark", {
|
|
257
|
+
outputFormat: 'html'
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
// Component (automatically uses dangerouslySetInnerHTML when outputFormat is 'html')
|
|
261
|
+
<ShikiHighlighter language="tsx" theme="github-dark" outputFormat="html">
|
|
262
|
+
{code}
|
|
263
|
+
</ShikiHighlighter>
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
**Shiki Tokens (Experimental)** - Hook-only output (bring your own renderer, react-shiki does not provide one)
|
|
267
|
+
```tsx
|
|
268
|
+
const highlighted = useShikiHighlighter(code, "tsx", "github-dark", {
|
|
269
|
+
outputFormat: "tokens",
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
// Sample only, not a production renderer: you own rendering, memoization,
|
|
273
|
+
// and commit management (critical for streaming highlighting)
|
|
274
|
+
const rendered = highlighted?.tokens.map((line, i) => (
|
|
275
|
+
<div key={i}>
|
|
276
|
+
{line.map((token, j) => (
|
|
277
|
+
<span key={j} style={{ color: token.color }}>
|
|
278
|
+
{token.content}
|
|
279
|
+
</span>
|
|
280
|
+
))}
|
|
281
|
+
</div>
|
|
282
|
+
));
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
The React and HTML formats spend their rendering work in different phases. HTML output skips per-token React element creation and reconciliation, reducing render-phase overhead for large, one-shot highlights, but each update replaces the code block's entire DOM subtree via `innerHTML`. React output pays for element creation and diffing on every update, but commits only incremental DOM mutations, minimizing DOM churn for frequently re-highlighted code such as streaming LLM output.
|
|
286
|
+
|
|
287
|
+
HTML output hands the highlighted markup to the DOM via `dangerouslySetInnerHTML`, so only use it with trusted code sources. The default React output is the safe choice for untrusted content.
|
|
288
|
+
|
|
289
|
+
Token output is for when you need to own rendering yourself; it is intentionally available on the hook, not the `ShikiHighlighter` component, since react-shiki's markup-based features no longer apply once you control the markup. Note that Shiki's `codeToTokens` does not run transformers or decorations, so markup-producing options, including transformers, decorations, line numbers, and `structure`, have no effect on token output.
|
|
206
290
|
|
|
207
291
|
## Configuration
|
|
208
292
|
|
|
209
293
|
### Common Configuration Options
|
|
210
294
|
|
|
295
|
+
The core inputs, positional arguments on the hook, props/children on the component:
|
|
211
296
|
|
|
212
|
-
|
|
|
297
|
+
| Input | Type | Default | Description |
|
|
213
298
|
| ------------------- | ------------------ | --------------- | ----------------------------------------------------------------------------- |
|
|
214
299
|
| `code` | `string` | - | Code to highlight |
|
|
215
|
-
| `language` | `string \| object` | - | Language to highlight, built-in or custom
|
|
216
|
-
| `theme` | `string \| object` | `'github-dark'` | Single or multi-theme configuration, built-in or custom
|
|
217
|
-
|
|
300
|
+
| `language` | `string \| object` | - | Language to highlight, built-in or custom TextMate grammar object |
|
|
301
|
+
| `theme` | `string \| object` | `'github-dark'` | Single or multi-theme configuration, built-in or custom TextMate theme object |
|
|
302
|
+
|
|
303
|
+
#### react-shiki Options
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
| Option | Type | Default | Description |
|
|
307
|
+
| ------------------- | ------------------ | --------------- | ----------------------------------------------------------------------------- |
|
|
308
|
+
| `delay` | `number` | - | Minimum time between highlights (in milliseconds), no throttling by default |
|
|
309
|
+
| `engine` | `'javascript' \| 'oniguruma' \| RegexEngine` | `'oniguruma'` | RegExp engine for syntax highlighting; named engines are created and cached internally |
|
|
310
|
+
| `highlighter` | `Highlighter \| HighlighterCore` | - | Custom highlighter instance, required for the core bundle |
|
|
311
|
+
| `outputFormat` | `string` | `'react'` | Output format: 'react' for React nodes, 'html' for HTML string, or 'tokens' for Shiki tokens (hook only, experimental) |
|
|
312
|
+
| `preloadLanguages` | `array` | `[]` | Preload bundled language IDs, custom grammar objects, or dynamic grammar imports |
|
|
218
313
|
| `customLanguages` | `array` | `[]` | **Deprecated**: use `preloadLanguages` instead. |
|
|
219
|
-
| `preloadLanguages` | `array` | `[]` | Preload bundled language IDs and custom language grammars |
|
|
220
|
-
| `langAlias` | `object` | `{}` | Map of language aliases |
|
|
221
|
-
| `engine` | `RegexEngine` | Oniguruma | RegExp engine for syntax highlighting (Oniguruma, JavaScript RegExp, or JavaScript Raw) |
|
|
222
314
|
| `showLineNumbers` | `boolean` | `false` | Display line numbers alongside code |
|
|
223
315
|
| `startingLineNumber` | `number` | `1` | Starting line number when line numbers are enabled |
|
|
224
|
-
| `
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
316
|
+
| `highlightLineNumbers` | `number[]` | - | Displayed line numbers to highlight |
|
|
317
|
+
|
|
318
|
+
#### Shiki Pass-through Options
|
|
319
|
+
|
|
320
|
+
All of Shiki's options pass-through and are accessible in react-shiki.
|
|
321
|
+
|
|
322
|
+
Based on `outputFormat`:
|
|
323
|
+
- `'tokens'`: All options supported by Shiki's [`codeToTokens`](https://github.com/shikijs/shiki/blob/v4.3.0/packages/types/src/tokens.ts#L27)
|
|
324
|
+
- `'html'`: All options supported by Shiki's [`codeToHtml`](https://github.com/shikijs/shiki/blob/v4.3.0/packages/types/src/options.ts#L136)
|
|
325
|
+
- `'react'`: All options supported by Shiki's [`codeToHast`](https://github.com/shikijs/shiki/blob/v4.3.0/packages/types/src/options.ts#L136)
|
|
326
|
+
|
|
327
|
+
Commonly used:
|
|
328
|
+
- `transformers`: modify the highlighting output with Shiki transformers (default: `[]`)
|
|
329
|
+
- `decorations`: wrap ranges of highlighted tokens (default: `[]`)
|
|
330
|
+
- `defaultColor`: default theme mode with multi-theme, `false` to disable (default: `'light'`)
|
|
331
|
+
- `cssVariablePrefix`: prefix for theme color CSS variables (default: `'--shiki-'`)
|
|
332
|
+
- `langAlias`: map custom language names to bundled ones (default: `{}`)
|
|
333
|
+
- `structure`: `'classic'` or `'inline'` HAST/HTML shape (default: `'classic'`)
|
|
334
|
+
- `tabindex`: tab index of the code block (default: `0`)
|
|
335
|
+
- `meta`: arbitrary metadata passed to transformers (default: `{}`)
|
|
232
336
|
|
|
233
337
|
### Component-specific Props
|
|
234
338
|
|
|
@@ -246,7 +350,7 @@ The `ShikiHighlighter` component offers minimal built-in styling and customizati
|
|
|
246
350
|
|
|
247
351
|
### Embedded Language Highlighting
|
|
248
352
|
|
|
249
|
-
react-shiki uses Shiki's [`guessEmbeddedLanguages`](https://shiki.
|
|
353
|
+
react-shiki uses Shiki's [`guessEmbeddedLanguages`](https://github.com/shikijs/shiki/blob/main/packages/core/src/utils/strings.ts) to automatically detect, load, and highlight languages nested inside other languages. For example, a TypeScript fenced code block inside a Markdown document will be highlighted as TypeScript without any extra configuration. Embedded languages are dynamically loaded on demand, as long as they exist in your bundle.
|
|
250
354
|
|
|
251
355
|
### Multi-theme Support
|
|
252
356
|
|
|
@@ -333,11 +437,11 @@ Ensure your site sets the `color-scheme` CSS property:
|
|
|
333
437
|
|
|
334
438
|
For broader browser support or more control, add CSS snippets to your site to enable theme switching with media queries or class-based switching. See [Shiki's documentation](https://shiki.matsu.io/guide/dual-themes) for the required CSS snippets.
|
|
335
439
|
|
|
336
|
-
> **Note**: The `light-dark()` function requires modern browser support. For older browsers, use the manual CSS variables approach.
|
|
440
|
+
> **Note**: The `light-dark()` function requires [modern browser support](https://caniuse.com/wf-light-dark). For older browsers, use the manual CSS variables approach.
|
|
337
441
|
|
|
338
442
|
### Custom Themes
|
|
339
443
|
|
|
340
|
-
Custom themes can be passed as a TextMate theme
|
|
444
|
+
Custom themes can be passed as a TextMate theme JavaScript object ([example](https://github.com/antfu/textmate-grammars-themes/blob/main/packages/tm-themes/themes/dark-plus.json)).
|
|
341
445
|
|
|
342
446
|
```tsx
|
|
343
447
|
import tokyoNight from "../styles/tokyo-night.json";
|
|
@@ -351,9 +455,29 @@ import tokyoNight from "../styles/tokyo-night.json";
|
|
|
351
455
|
const highlightedCode = useShikiHighlighter(code, "tsx", tokyoNight);
|
|
352
456
|
```
|
|
353
457
|
|
|
458
|
+
When using a fine-grained highlighter via `react-shiki/core`: pass the theme as a dynamic import to `createHighlighterCore` so it stays out of the initial chunk, then reference it by its `name` field:
|
|
459
|
+
|
|
460
|
+
```tsx
|
|
461
|
+
const highlighter = await createHighlighterCore({
|
|
462
|
+
themes: [import("../styles/tokyo-night.json")],
|
|
463
|
+
langs: [import("@shikijs/langs/tsx")],
|
|
464
|
+
engine: createJavaScriptRegexEngine(),
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
// Component
|
|
468
|
+
<ShikiHighlighter highlighter={highlighter} language="tsx" theme="tokyo-night">
|
|
469
|
+
{code.trim()}
|
|
470
|
+
</ShikiHighlighter>
|
|
471
|
+
|
|
472
|
+
// Hook
|
|
473
|
+
const highlightedCode = useShikiHighlighter(code, "tsx", tokyoNight, {
|
|
474
|
+
highlighter,
|
|
475
|
+
});
|
|
476
|
+
```
|
|
477
|
+
|
|
354
478
|
### Custom Languages
|
|
355
479
|
|
|
356
|
-
Custom languages should be passed as a TextMate grammar
|
|
480
|
+
Custom languages should be passed as a TextMate grammar JavaScript object ([example](https://github.com/shikijs/textmate-grammars-themes/blob/main/packages/tm-grammars/grammars/typescript.json)).
|
|
357
481
|
```tsx
|
|
358
482
|
import mcfunction from "../langs/mcfunction.tmLanguage.json";
|
|
359
483
|
|
|
@@ -366,6 +490,28 @@ import mcfunction from "../langs/mcfunction.tmLanguage.json";
|
|
|
366
490
|
const highlightedCode = useShikiHighlighter(code, mcfunction, "github-dark");
|
|
367
491
|
```
|
|
368
492
|
|
|
493
|
+
Passing grammar objects as props works with the full and web bundles, where react-shiki creates the highlighter and loads them for you. To keep custom grammars out of the main bundle, pass them as dynamic imports via `preloadLanguages` and reference them by name — see [Preloading Languages](#preloading-languages).
|
|
494
|
+
|
|
495
|
+
On the core bundle you own the highlighter, so include grammars there as dynamic imports:
|
|
496
|
+
|
|
497
|
+
```tsx
|
|
498
|
+
const highlighter = await createHighlighterCore({
|
|
499
|
+
themes: [import("@shikijs/themes/github-dark")],
|
|
500
|
+
langs: [import("../langs/mcfunction.tmLanguage.json")],
|
|
501
|
+
engine: createJavaScriptRegexEngine(),
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
// Component
|
|
505
|
+
<ShikiHighlighter highlighter={highlighter} language="mcfunction" theme="github-dark">
|
|
506
|
+
{code.trim()}
|
|
507
|
+
</ShikiHighlighter>
|
|
508
|
+
|
|
509
|
+
// Hook
|
|
510
|
+
const highlightedCode = useShikiHighlighter(code, "mcfunction", "github-dark", {
|
|
511
|
+
highlighter,
|
|
512
|
+
});
|
|
513
|
+
```
|
|
514
|
+
|
|
369
515
|
#### Preloading Languages
|
|
370
516
|
|
|
371
517
|
If highlighting is dynamic at runtime, preload custom languages.
|
|
@@ -389,8 +535,29 @@ const highlightedCode = useShikiHighlighter(code, "typescript", "github-dark", {
|
|
|
389
535
|
});
|
|
390
536
|
```
|
|
391
537
|
|
|
538
|
+
Custom grammars can also be dynamic imports, keeping them out of the main bundle. Reference them by the grammar's `name` field:
|
|
539
|
+
|
|
540
|
+
```tsx
|
|
541
|
+
// Module scope: stable identity across renders, fetched on first highlight
|
|
542
|
+
const preloadLanguages = [
|
|
543
|
+
() => import("../langs/mcfunction.tmLanguage.json"),
|
|
544
|
+
() => import("../langs/bosque.tmLanguage.json"),
|
|
545
|
+
];
|
|
546
|
+
|
|
547
|
+
<ShikiHighlighter
|
|
548
|
+
language="mcfunction"
|
|
549
|
+
theme="github-dark"
|
|
550
|
+
preloadLanguages={preloadLanguages}
|
|
551
|
+
>
|
|
552
|
+
{code.trim()}
|
|
553
|
+
</ShikiHighlighter>
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
> [!IMPORTANT]
|
|
557
|
+
> Define dynamic imports at module scope, preferring the getter form (`() => import(...)`). A fresh arrow function on every render re-triggers highlighting; bare promises are compared by type, so swapping one at runtime won't be picked up.
|
|
558
|
+
|
|
392
559
|
> [!NOTE]
|
|
393
|
-
> Bundled
|
|
560
|
+
> Bundled languages are loaded on demand and do not need to be preloaded. `preloadLanguages` applies to the full and web bundles; with a custom highlighter, load languages in `createHighlighterCore` instead.
|
|
394
561
|
|
|
395
562
|
### Language Aliases
|
|
396
563
|
|
|
@@ -430,47 +597,37 @@ const highlightedCode = useShikiHighlighter(code, "tsx", "github-dark", {
|
|
|
430
597
|
|
|
431
598
|
### Line Numbers
|
|
432
599
|
|
|
433
|
-
|
|
434
|
-
and can be customized with CSS variables:
|
|
600
|
+
Line numbers are CSS-based and can be customized with CSS variables:
|
|
435
601
|
|
|
436
602
|
```tsx
|
|
437
603
|
// Component
|
|
438
604
|
<ShikiHighlighter
|
|
439
605
|
language="javascript"
|
|
440
606
|
theme="github-dark"
|
|
441
|
-
showLineNumbers
|
|
607
|
+
showLineNumbers
|
|
442
608
|
startingLineNumber={0} // default is 1
|
|
609
|
+
highlightLineNumbers={[2, 4]}
|
|
443
610
|
>
|
|
444
611
|
{code}
|
|
445
612
|
</ShikiHighlighter>
|
|
446
613
|
|
|
447
|
-
|
|
448
|
-
language="python"
|
|
449
|
-
theme="github-dark"
|
|
450
|
-
showLineNumbers
|
|
451
|
-
startingLineNumber={0}
|
|
452
|
-
>
|
|
453
|
-
{code}
|
|
454
|
-
</ShikiHighlighter>
|
|
455
|
-
|
|
456
|
-
// Hook (import 'react-shiki/css' for line numbers to work)
|
|
614
|
+
// Hook (line-number styles inject automatically when enabled)
|
|
457
615
|
const highlightedCode = useShikiHighlighter(code, "javascript", "github-dark", {
|
|
458
616
|
showLineNumbers: true,
|
|
459
|
-
startingLineNumber: 0,
|
|
617
|
+
startingLineNumber: 0,
|
|
618
|
+
highlightLineNumbers: [2, 4],
|
|
460
619
|
});
|
|
461
620
|
```
|
|
462
621
|
|
|
463
622
|
> [!NOTE]
|
|
464
|
-
>
|
|
465
|
-
>
|
|
466
|
-
> ```tsx
|
|
467
|
-
> import 'react-shiki/css';
|
|
468
|
-
> ```
|
|
623
|
+
> No CSS import is needed: the shared highlighting hook injects line-number and highlight styles automatically when those features are enabled, including when used through the component.
|
|
624
|
+
> To customize them, override `.rs-line-number` (line `span`), `.rs-highlighted-line` (line `span`), and `.rs-has-line-numbers` / `.rs-has-highlighted-lines` (container `code` element) in your own CSS.
|
|
469
625
|
|
|
470
|
-
|
|
471
|
-
> Legacy `.line-numbers` / `.has-line-numbers` selectors and unprefixed line-number CSS variables are removed as of 0.10.1.
|
|
626
|
+
`highlightLineNumbers` uses displayed line numbers, so it works with `startingLineNumber`.
|
|
472
627
|
|
|
473
|
-
Component-
|
|
628
|
+
Component styles are embedded in the JS and installed automatically when the component mounts — no CSS import or bundler configuration needed. Injection is scoped to usage: component styles tree-shake out of hook-only bundles entirely, while line-number/highlight styles are installed only when those options are enabled. The complete compiled stylesheet is also exported as `react-shiki/css` for build-time CSS pipelines or as an externally loaded fallback. Importing it does not disable automatic runtime injection.
|
|
629
|
+
|
|
630
|
+
Component-internal default classes are namespaced under `rs-*` and written with zero-specificity `:where()` selectors, so they can't be clobbered by CSS resets (e.g. Tailwind preflight) regardless of stylesheet load order, while any rule in your own CSS — even a bare element selector — overrides them.
|
|
474
631
|
|
|
475
632
|
Available CSS variables for customization:
|
|
476
633
|
```css
|
|
@@ -481,8 +638,12 @@ Available CSS variables for customization:
|
|
|
481
638
|
--rs-line-numbers-font-size: inherit;
|
|
482
639
|
--rs-line-numbers-font-weight: inherit;
|
|
483
640
|
--rs-line-numbers-opacity: 1;
|
|
641
|
+
--rs-highlighted-line-background: color-mix(in srgb, currentColor 10%, transparent);
|
|
642
|
+
--rs-highlighted-line-number-foreground: color-mix(in srgb, currentColor 65%, transparent);
|
|
484
643
|
```
|
|
485
644
|
|
|
645
|
+
With default styles, highlighted-line backgrounds automatically extend into the `pre`'s horizontal padding, reaching the container edges. When styling the `pre` yourself, highlights cover the code content area.
|
|
646
|
+
|
|
486
647
|
You can customize them in your own CSS or by using the style prop on the component:
|
|
487
648
|
```tsx
|
|
488
649
|
<ShikiHighlighter
|
|
@@ -573,9 +734,9 @@ const CodeHighlight = ({ className, children, node, ...props }) => {
|
|
|
573
734
|
**Method 2: Using the `rehypeInlineCodeProperty` plugin:**
|
|
574
735
|
|
|
575
736
|
`react-shiki` also exports `rehypeInlineCodeProperty`, a rehype plugin that
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
737
|
+
adds an `inline` prop to `react-markdown` code components. It works by checking
|
|
738
|
+
if `<code>` is nested within a `<pre>` tag; if not, it's considered inline code
|
|
739
|
+
and the `inline` prop is set to `true`.
|
|
579
740
|
|
|
580
741
|
It's passed as a `rehypePlugin` to `react-markdown`:
|
|
581
742
|
|
|
@@ -623,7 +784,7 @@ const CodeHighlight = ({
|
|
|
623
784
|
|
|
624
785
|
### Throttling Real-time Highlighting
|
|
625
786
|
|
|
626
|
-
|
|
787
|
+
To reduce highlighting work for frequently changing code:
|
|
627
788
|
|
|
628
789
|
```tsx
|
|
629
790
|
// With the component
|
|
@@ -637,36 +798,6 @@ const highlightedCode = useShikiHighlighter(code, "tsx", "github-dark", {
|
|
|
637
798
|
});
|
|
638
799
|
```
|
|
639
800
|
|
|
640
|
-
### Output Format Optimization
|
|
641
|
-
|
|
642
|
-
`react-shiki` provides two output formats to balance safety and performance:
|
|
643
|
-
|
|
644
|
-
**React Nodes (Default)** - Safer, no `dangerouslySetInnerHTML` required
|
|
645
|
-
```tsx
|
|
646
|
-
// Hook
|
|
647
|
-
const highlightedCode = useShikiHighlighter(code, "tsx", "github-dark");
|
|
648
|
-
|
|
649
|
-
// Component
|
|
650
|
-
<ShikiHighlighter language="tsx" theme="github-dark">
|
|
651
|
-
{code}
|
|
652
|
-
</ShikiHighlighter>
|
|
653
|
-
```
|
|
654
|
-
|
|
655
|
-
**HTML String** - 15-45% faster performance
|
|
656
|
-
```tsx
|
|
657
|
-
// Hook (returns HTML string, use dangerouslySetInnerHTML to render)
|
|
658
|
-
const highlightedCode = useShikiHighlighter(code, "tsx", "github-dark", {
|
|
659
|
-
outputFormat: 'html'
|
|
660
|
-
});
|
|
661
|
-
|
|
662
|
-
// Component (automatically uses dangerouslySetInnerHTML when outputFormat is 'html')
|
|
663
|
-
<ShikiHighlighter language="tsx" theme="github-dark" outputFormat="html">
|
|
664
|
-
{code}
|
|
665
|
-
</ShikiHighlighter>
|
|
666
|
-
```
|
|
667
|
-
|
|
668
|
-
Choose HTML output when performance is critical and you trust the code source. Use the default React output when handling untrusted content or when security is the primary concern.
|
|
669
|
-
|
|
670
801
|
---
|
|
671
802
|
|
|
672
803
|
Made with ❤️ by [Bassim (AVGVSTVS96)](https://github.com/AVGVSTVS96)
|