docs/packages/transformers.md
Common transformers for Shiki, inspired by shiki-processor.
::: code-group
npm i -D @shikijs/transformers
yarn add -D @shikijs/transformers
pnpm add -D @shikijs/transformers
bun add -D @shikijs/transformers
deno add npm:@shikijs/transformers
:::
// @twoslash-cache: {"v":1,"hash":"8efec5d385627b3d23e03e4c9bdf13f9a3c80178f3105cfb878cd54cef1eacb9","data":"N4Igdg9gJgpgziAXAbVAFwJ4AcZJACwgDcYAnEAGhDRgA808AKAQwBsBLZuASgAIAzAK5gAxmnYQwvNKWZg4/CKQC2ZAHIQ0zcZIAi7fv0YQsO+QH5EvACqz5iles3aJYfYYDyp1zysBlfHYAa3ZbOQUlVVIAHTB2ZSwlNGk7CMdSDS0zd35KECgIEQREEABVOBheAANkAEIRaEqAaiaAXSreOShquobYXgBaAfbpCF5lZlIgzqhYbq7eUhhlYhhujjB4ADo8uC1SBkQAZgAWKlYYMABzNHwkACYjqn2rmEPqVIcozJc9A1zzuxNg8qCJ8JNmGIyA8AL4UdDYXAlQgkcjPOjvFgcLh8BryZJ9GDWCAACTQylYVkYhKse1IQKuFF4JjMcCsAGFGsSSVw0F5WQAeABCwigFygABk5FdBMxXkyRWAxWtrPhljAAHx8AC8Gt4AAVSBBlOwKgK6QyNbF4okDrxCdzyaxdvtDgBGN0ABnOlxud0QXuek1e7wdpKdeQ2SIAbKDwbIoeREPc4QicHgUdD0fQ8Hi9vbGlZoiA8xALltWBArowAORq1iVmvcYsuybuk5nEAXa63JCdl5vXONSNApEATjjEMTSGjqeoiIzqzR1AxQ/xvHwTtpMgZrYOSDdAA5vV3fb3EP3g4PkRHAcCAyewVOaEnZ/D5+nkUu8jQcyUsZwPAFrAjoUtSha8Ba1xMiyPgclypK8vyPjCqK4pStcsryrwirKlAqrqlqViGsapowOaO7XFacQJEkwFEuGFJ7u6Y5PKePb+oG1BXqGCFkkxd5IlxT4Ji+B5jnOmCfgQ37Zrx640rwxaluWlbVnWMANhATYtlQextg8nqdt2fp9kGpAhkOsAjvewnxpCYnHJ6kkLl+qI/qu/5YEaOAHBgfCsNKRbUHAukgPp+7Jm6xlnv6l4WdeXbSjZQlupOonQsmLnSZmy6/pi3kmGQmB8JysDIZIcB+AyFwEaoqFKuKdWalstzqlYzUAEowFcpoyL8YAAIJgBgvAAD68H4lFXBK7AvmwpRxJIDV4c1TJQVcGrMQ89yHj6HFIAArOZlklG1qgpQe9zpQ5mUpu+UlIjJ7lyUwhW+SVNifJEZAVfIrXfekcCWJNgQhGE9g/aQyCtONvCijA/CjlAeQFEUeAQ2kURwAISjSGqoPBOwvBYOwOBRjsemug8JzHexpkBtd3EJe8/WQ0Dl0BmxIm3Um91pk9uUeX+IAATiKThF8ThZK4OTGN4lUg5jUsZM42T/H9viE+DgNRNatF2mzWPSwNOSo4UxRlBUPT1I0vAtCMCw1Lb/RDCMaBjBMUwzHMnRKosyyrOso5wJT4XU8m0Z0yZ56PCdiVGyrPzq4YnPRTd04XjCrSgsOJQAPT57wvR24EVz4Bw5doIgh2tPrtrJMAsS8BL7PfGrsv/BQze8IXvBbAPsQwgIRrKLwNYAAJwGD7AAFZwPnidQ3ANb13RTdSPRoGsN3YDD/wo/j9PROr2AsR5gSdvatUKkwBWVa1vWjbcFU5+Vckm4Urw1/MAA7swc0t6MVYOBWATIN4t0CtcKwNY0Ar13i3c6MAYGQFIFAGsCDW7G1IGyYuPdEG6xNinIw3AmR9xLv0MuFd2BVzrpvFufcB5bB7jnIe3A8iqC0EgUAGJLhwFcHgOBIAYQwiAA==="}
// [!code highlight:5]
import {
transformerNotationDiff,
// ...
} from '@shikijs/transformers'
import {
codeToHtml,
} from 'shiki'
const code = `console.log('hello')`
const html = await codeToHtml(code, {
lang: 'ts',
theme: 'nord',
transformers: [
transformerNotationDiff(), // [!code highlight]
// ...
],
})
Transformers only applies classes and does not come with styles; you can provide your own CSS rules to style them properly.
We found that the algorithm for matching comments in v1 is sometime conterintuitive, where we are trying to fix it in a progressive way. Since v1.29.0, we introduced a new matchAlgorithm option to most of the transformer for you to toggle between different matching algorithms. Right now, the default is v1 which is the old algorithm, and v3 is the new algorithm. When Shiki v3 is landed, the default will be v3.
const html = await codeToHtml(code, {
lang: 'ts',
theme: 'nord',
transformers: [
transformerNotationDiff({
matchAlgorithm: 'v3', // [!code hl]
}),
],
})
matchAlgorithm: 'v1'The matching algorithm mostly affects the single-line comment matching, in v1, it will count the comment line as the first line, while in v3, it will count start from the comment line:
// [\!code highlight:3]
console.log('highlighted') // [!code hl]
console.log('highlighted') // [!code hl]
console.log('not highlighted')
matchAlgorithm: 'v3'In v3, the matching algorithm will start counting from the line below the comment:
// [\!code highlight:2]
console.log('highlighted') // [!code hl]
console.log('highlighted') // [!code hl]
console.log('not highlighted')
transformerNotationDiffUse [!code ++] and [!code --] to mark added and removed lines.
```ts
console.log('hewwo') // [\!code --]
console.log('hello') // [\!code ++]
console.log('goodbye')
```
Renders (with custom CSS rules):
console.log('hewwo') // [!code --]
console.log('hello') // [!code ++]
console.log('goodbye')
// [!code ++] outputs: <span class="line diff add">// [!code --] outputs: <span class="line diff remove"><pre> tag is modified: <pre class="has-diff">::: details HTML Output
<!-- Output (stripped of `style` attributes for clarity) -->
<pre class="shiki has-diff"> <!-- Notice `has-diff` -->
<code>
<span class="line"></span>
<span class="line"><span>function</span><span>()</span><span></span><span>{</span></span>
<span class="line diff remove"> <!-- Notice `diff` and `remove` -->
<span></span><span>console</span><span>.</span><span>log</span><span>(</span><span>'</span><span>hewwo</span><span>'</span><span>) </span>
</span>
<span class="line diff add"> <!-- Notice `diff` and `add` -->
<span></span><span>console</span><span>.</span><span>log</span><span>(</span><span>'</span><span>hello</span><span>'</span><span>) </span>
</span>
<span class="line"><span></span><span>}</span></span>
<span class="line"><span></span></span>
</code>
</pre>
:::
transformerNotationHighlightUse [!code highlight] to highlight a line.
```ts
console.log('Not highlighted')
console.log('Highlighted') // [\!code highlight]
console.log('Not highlighted')
```
Renders (with custom CSS rules):
console.log('Not highlighted')
console.log('Highlighted') // [!code highlight]
console.log('Not highlighted')
// [!code highlight] outputs: <span class="line highlighted"><pre> tag is modified: <pre class="has-highlighted">You can also highlight multiple lines with a single comment:
```ts
// [\!code highlight:3]
console.log('Highlighted')
console.log('Highlighted')
console.log('Not highlighted')
```
```ts
console.log('Not highlighted')
// [\!code highlight:1]
console.log('Highlighted')
console.log('Not highlighted')
```
Renders:
// [!code highlight:3]
console.log('Highlighted')
console.log('Highlighted')
console.log('Not highlighted')
console.log('Not highlighted')
// [!code highlight:1]
console.log('Highlighted')
console.log('Not highlighted')
transformerNotationWordHighlightUse [!code word:Hello] to highlight the word Hello in any subsequent code.
```ts
// [\!code word:Hello]
const message = 'Hello World'
console.log(message) // prints Hello World
```
Renders (with custom CSS rules):
// [!code word:Hello]
const message = 'Hello World'
console.log(message) // prints Hello World
Outputs: <span class="highlighted-word">Hello</span> for matched words.
You can also specify the number of lines to highlight words on, e.g. [!code word:Hello:1] will only highlight occurrences of Hello on the next line.
```ts
// [\!code word:Hello:1]
const message = 'Hello World'
console.log(message) // prints Hello World
```
Renders:
// [!code word:Hello:1]
const message = 'Hello World'
console.log(message) // prints Hello World
transformerNotationFocusUse [!code focus] to focus a line.
```ts
console.log('Not focused');
console.log('Focused') // [\!code focus]
console.log('Not focused');
```
Renders (with custom CSS rules):
console.log('Not focused')
console.log('Focused') // [!code focus]
console.log('Not focused')
<span class="line focused"><pre> tag is modified: <pre class="has-focused">You can also focus multiple lines with a single comment:
```ts
// [\!code focus:3]
console.log('Focused')
console.log('Focused')
console.log('Not focused')
```
Renders:
// [!code focus:3]
console.log('Focused')
console.log('Focused')
console.log('Not focused')
transformerNotationErrorLevelUse [!code error], [!code warning], and [!code info] to mark a line with an error, warning, or info levels.
```ts
console.log('No errors or warnings')
console.error('Error') // [\!code error]
console.warn('Warning') // [\!code warning]
console.log('Info') // [\!code info]
```
<span class="line highlighted error"> for errors<span class="line highlighted warning"> for warnings<span class="line highlighted info"> for info<pre> tag is modified: <pre class="has-highlighted">With some additional CSS rules, you can make it look like this:
console.log('No errors or warnings')
console.error('Error') // [!code error]
console.warn('Warning') // [!code warning]
console.log('Info') // [!code info]
transformerRenderWhitespaceRender whitespaces (tabs and spaces) as individual spans, with classes tab and space.
Options:
position: 'all' | 'boundary' | 'trailing' | 'leading'. Default 'all'.With some additional CSS rules, you can make it look like this:
<div class="language-js vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre v-pre class="shiki shiki-themes vitesse-light vitesse-dark" style="--shiki-light:#393a34;--shiki-dark:#dbd7caee;--shiki-light-bg:#ffffff;--shiki-dark-bg:#121212;" tabindex="0"><code><span class="line"><span style="--shiki-light:#AB5959;--shiki-dark:#CB7676;">function</span><span class="space"> </span><span style="--shiki-light:#59873A;--shiki-dark:#80A665;">block</span><span style="--shiki-light:#999999;--shiki-dark:#666666;">(</span><span class="space"> </span><span style="--shiki-light:#999999;--shiki-dark:#666666;">)</span><span class="space"> </span><span style="--shiki-light:#999999;--shiki-dark:#666666;">{</span></span> <span class="line"><span class="space"> </span><span class="space"> </span><span style="--shiki-light:#59873A;--shiki-dark:#80A665;">space</span><span style="--shiki-light:#999999;--shiki-dark:#666666;">(</span><span class="space"> </span><span style="--shiki-light:#999999;--shiki-dark:#666666;">)</span></span> <span class="line"><span class="tab">	</span><span class="tab">	</span><span style="--shiki-light:#59873A;--shiki-dark:#80A665;">tab</span><span style="--shiki-light:#999999;--shiki-dark:#666666;">(</span><span class="space"> </span><span style="--shiki-light:#999999;--shiki-dark:#666666;">)</span><span style="--shiki-light:#393A34;--shiki-dark:#DBD7CAEE;"> </span></span> <span class="line"><span style="--shiki-light:#999999;--shiki-dark:#666666;">}</span></span></code></pre></div>::: details Example CSS
pre.shiki .tab,
pre.shiki .space {
position: relative;
}
pre.shiki .tab::before {
content: '⇥';
position: absolute;
opacity: 0.3;
}
pre.shiki .space::before {
content: '·';
position: absolute;
opacity: 0.3;
}
:::
transformerRenderIndentGuidesRender indentations as individual spans, with class indent.
With some additional CSS rules, you can make it look like this:
<div class="language-js vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre v-pre class="shiki shiki-themes vitesse-light vitesse-dark" style="--shiki-light:#393a34;--shiki-dark:#dbd7caee;--shiki-light-bg:#ffffff;--shiki-dark-bg:#121212;" tabindex="0"><code><span class="line"><span style="color:#CB7676">function</span><span style="color:#80A665"> func</span><span style="color:#666666">()</span><span style="color:#666666"> {</span></span> <span class="line"><span class="indent"> </span><span style="color:#BD976A">console</span><span style="color:#666666">.</span><span style="color:#80A665">log</span><span style="color:#666666">(</span><span style="color:#4C9A91">1</span><span style="color:#666666">);</span></span> <span class="line"><span class="indent" style="--indent-offset: 0ch;"></span></span> <span class="line"><span class="indent"> </span><span style="color:#4D9375">for</span><span style="color:#666666"> (</span><span style="color:#CB7676">const </span><span style="color:#BD976A">i</span><span style="color:#CB7676"> of</span><span style="color:#666666"> [])</span><span style="color:#666666"> {</span></span> <span class="line"><span class="indent"> </span><span class="indent"> </span><span style="color:#BD976A">console</span><span style="color:#666666">.</span><span style="color:#80A665">log</span><span style="color:#666666">(</span><span style="color:#4C9A91">2</span><span style="color:#666666">);</span></span> <span class="line"><span class="indent"> </span><span style="color:#666666">}</span></span> <span class="line"><span style="color:#666666">}</span></span></code></pre></div>::: details Example CSS
pre.shiki .indent {
display: inline-block;
position: relative;
left: var(--indent-offset);
}
pre.shiki .indent:empty {
height: 1lh;
vertical-align: bottom;
}
pre.shiki .indent::before {
content: '';
position: absolute;
opacity: 0.15;
width: 1px;
height: 100%;
background-color: currentColor;
}
:::
transformerMetaHighlightHighlight lines based on the meta string provided on the code snippet.
```js {1,3-4}
console.log('1')
console.log('2')
console.log('3')
console.log('4')
```
Renders (with custom CSS rules):
console.log('1')
console.log('2')
console.log('3')
console.log('4')
<span class="line highlighted"> for included lines.transformerMetaWordHighlightHighlight words based on the meta string provided on the code snippet.
```js /Hello/
const msg = 'Hello World'
console.log(msg)
console.log(msg) // prints Hello World
```
Renders (with custom CSS rules):
const msg = 'Hello World'
console.log(msg) // prints Hello World
Outputs: <span class="highlighted-word">Hello</span> for matched words.
transformerCompactLineOptionsSupport for shiki's lineOptions that is removed in shiki.
transformerRemoveLineBreakRemove line breaks between <span class="line">. Useful when you set display: block to .line in CSS.
transformerRemoveNotationEscapeTransform // [\!code ...] to // [!code ...].
Avoid rendering the escaped notation syntax as it is.
transformerStyleToClassConvert Shiki's inline styles to unique classes.
Class names are generated based on the hash value of the style object with the prefix/suffix you provide. You can put this transformer in multiple highlights passes and then get the CSS at the end to reuse the exact same styles. As Shiki doesn't handle CSS, it's on your integration to decide how to extract and apply/bundle the CSS.
For example:
import { transformerStyleToClass } from '@shikijs/transformers'
import { codeToHtml } from 'shiki'
const toClass = transformerStyleToClass({ // [!code highlight:3]
classPrefix: '__shiki_',
})
const code = `console.log('hello')`
const html = await codeToHtml(code, {
lang: 'ts',
themes: {
dark: 'vitesse-dark',
light: 'vitesse-light',
},
defaultColor: false,
transformers: [toClass], // [!code highlight]
})
// The transformer instance exposes some methods to get the CSS
const css = toClass.getCSS() // [!code highlight]
// use `html` and `css` in your app
HTML output:
<pre class="shiki shiki-themes vitesse-dark vitesse-light __shiki_9knfln" tabindex="0"><code><span class="line">
<span class="__shiki_14cn0u">console</span>
<span class="__shiki_ps5uht">.</span>
<span class="__shiki_1zrdwt">log</span>
<span class="__shiki_ps5uht">(</span>
<span class="__shiki_236mh3">'</span>
<span class="__shiki_1g4r39">hello</span>
<span class="__shiki_236mh3">'</span>
<span class="__shiki_ps5uht">)</span>
</span></code></pre>
CSS output:
.__shiki_14cn0u {
--shiki-dark: #bd976a;
--shiki-light: #b07d48;
}
.__shiki_ps5uht {
--shiki-dark: #666666;
--shiki-light: #999999;
}
.__shiki_1zrdwt {
--shiki-dark: #80a665;
--shiki-light: #59873a;
}
.__shiki_236mh3 {
--shiki-dark: #c98a7d77;
--shiki-light: #b5695977;
}
.__shiki_1g4r39 {
--shiki-dark: #c98a7d;
--shiki-light: #b56959;
}
.__shiki_9knfln {
--shiki-dark: #dbd7caee;
--shiki-light: #393a34;
--shiki-dark-bg: #121212;
--shiki-light-bg: #ffffff;
}
transformerRemoveCommentsRemove comments from the code. It works by checking the internal grammar token metadata to determine if the token is a comment.
This transformer requires includeExplanation: true to work.
import { transformerRemoveComments } from '@shikijs/transformers'
const html = await codeToHtml(code, {
lang: 'ts',
includeExplanation: true, // [!code highlight]
transformers: [
transformerRemoveComments(), // [!code highlight]
],
})
Options:
removeEmptyLines: Remove lines that become empty after removing comments. Default true.For example:
```js
// This is a comment
const x = 1 // Inline comment
/* Block comment */
const y = 2
// Another comment
```
Will renders:
const x = 1
const y = 2