シンタックスハイライター`Shiki`の紹介
shiki とは何か
shiki は、VS Code のシンタックスハイライトと同じエンジンである TextMate の文法とテーマをベースにした、高度なカスタマイズが可能なシンタックスハイライターです。
Astro でも内部で使われていたり Node.js のWebsiteでも使用されていたりします。
この記事では、shiki の使い方や特徴について紹介していきますが、とりあえず試したい方は公式ドキュメントにPlaygroundが用意されているので使ってみるのもいいかもしれません。
前提:
この記事の情報はv.1.2.0
の公式ドキュメントを参考にしています。
shiki の名前の由来は?
日本語の「式(Style)」から来ているそうです。確かにこのライブラリのロゴも「式」に見えますね。
特徴と使用例
最小で試す
pnpm add -D shiki
shiki を使い始める最も手っ取り早い方法である、提供されているcodeToHtml
を使います。
import { codeToHtml } from "shiki";
import { CODE } from "@/constants";
export default async function DemoPage() {
const html = await codeToHtml(CODE, {
lang: "tsx",
theme: "github-dark-dimmed",
});
return <div dangerouslySetInnerHTML={{ __html: html }}></div>;
}
結果はこうなりました。いけてますね。
ちなみにcodeToHtml
の返り値をみてみましょう。
<pre
class="shiki github-light"
style="background-color:#fff;color:#24292e"
tabindex="0"
>
<code>
<span class="line">
<span style="color:#D73A49">const</span>
<span style="color:#005CC5"> greeting</span>
<span style="color:#D73A49"> =</span>
<span style="color:#032F62"> "Hello, World!"</span>
<span style="color:#24292E">;</span>
</span>
<span class="line"></span>
<span class="line">
<span style="color:#24292E">console.</span>
<span style="color:#6F42C1">log</span>
<span style="color:#24292E">(greeting); </span>
<span style="color:#6A737D">// Hello, World!</span>
</span>
</code>
</pre>
こんな感じで生成された HTML にはインラインでスタイルが適用されます。別で CSS を持つ必要がないのでとても楽です。(後述のダークモードの対応には CSS のスニペットが必要になってきます)
Highlighter を使用して同期的にハイライトする
上述のcodeToHtml
は内部でテーマと言語をオンデマンドでロードするために非同期で実行されます。しかし、getHighlighter
を使用することで、同期的にハイライトすることができます。
import { getHighlighter } from "shiki";
import { CODE } from "@/constants";
export default async function HighlighterPage() {
const highlighter = await getHighlighter({
themes: ["github-light"],
langs: ["tsx"],
});
const html = highlighter.codeToHtml(CODE, {
lang: "tsx",
theme: "github-light",
});
return <div dangerouslySetInnerHTML={{ __html: html }}></div>;
}
同様の結果が得られました。
Light / Dark テーマサポート
shiki は、Light / Dark テーマをサポートしています。themes
プロパティに複数のテーマを指定することで、テーマを切り替えることができます。
import { codeToHtml } from "shiki";
import { CODE } from "@/constants";
export default async function MyThemePage() {
const html = await codeToHtml(CODE, {
lang: "tsx",
themes: {
// lightとdarkのテーマを指定
light: "github-light",
dark: "github-dark-dimmed",
},
});
return <div dangerouslySetInnerHTML={{ __html: html }}></div>;
}
注意点として、ダークモードを利用するには以下のような CSS スニペットを追加する必要があります。
html.dark .shiki,
html.dark .shiki span {
color: var(--shiki-dark) !important;
background-color: var(--shiki-dark-bg) !important;
}
こんな感じになりました。
Light モード
Dark モード
shiki は Light / Dark モード だけでなく、より多くのテーマを設定することもできます。詳しくはこちらを参照してください。
デコレーション
カスタムクラスやカスタム属性を付与することができます。
import { codeToHtml } from "shiki";
import { CODE } from "@/constants";
export default async function DecorationsPage() {
const html = await codeToHtml(CODE, {
lang: "tsx",
theme: "github-dark-dimmed",
decorations: [
// "const"の背景色を赤、文字色を白にする
{
start: 0,
end: 5,
properties: {
class: "bg-red-500 !text-white",
},
},
// 変数名"greeting"に緑のborderを付与する
{
start: 6,
end: 14,
properties: {
class: "border-2 border-green-500",
},
},
],
});
return <div dangerouslySetInnerHTML={{ __html: html }}></div>;
}
以下のようにクラスを当てたい箇所にstart
とend
を指定することで、その範囲にクラスを適用することができました。
Transformers
shiki は hast(HTML を AST(抽象構文木)として表現するための仕様)をサポートしており、独自のトランスフォーマーを書くことで、生成される HTML をカスタマイズすることができます。
import { codeToHtml } from "shiki";
import { CODE, DEFAULT_LANG_AND_THEMES } from "@/constants";
export default async function TransformersPage() {
const html = await codeToHtml(CODE, {
...DEFAULT_LANG_AND_THEMES,
transformers: [
{
line(node, line) {
// 奇数行に下線を追加
if (line % 2 === 1) this.addClassToHast(node, "underline");
},
},
],
});
return <div dangerouslySetInnerHTML={{ __html: html }}></div>;
}
結果はこんな感じになりました。
奇数行に下線を追加
line
以外にも様々な Hooks が提供されているので詳しくみたい方はこちらを参照してください。
また、@shikijs/transformers
というパッケージに含まれている transformers を使用することも可能で、コードの diff を表示するためのtransformerNotationDiff()
やコードにフォーカスのスタイルを当てることができるようにするtransformerNotationFocus()
など便利な Transformers が用意されています。
pnpm add -D @shikijs/transformers
import { codeToHtml } from "shiki";
import { transformerNotationDiff } from "@shikijs/transformers";
export default async function TransformersPage() {
/**
* [!code --] が付与されたlineは削除を表す
* [!code ++] が付与されたlineは追加を表す
*/
const code = `
console.log('hewwo') // [!code --]
console.log('hello') // [!code ++]`;
const html = await codeToHtml(code, {
lang: "tsx",
theme: "github-light",
transformers: [transformerNotationDiff()],
});
return <div dangerouslySetInnerHTML={{ __html: html }}></div>;
}
transformerNotationDiff()
の使用例
import { codeToHtml } from "shiki";
import { transformerNotationFocus } from "@shikijs/transformers";
export default async function TransformersPage() {
/**
* [!code focus] が付与されたlineはフォーカスされた状態を表す
*/
const code = `
console.log('Not focused');
console.log('Focused') // [!code focus]
console.log('Not focused');`;
const html = await codeToHtml(code, {
lang: "tsx",
theme: "github-light",
transformers: [transformerNotationFocus()],
});
return <div dangerouslySetInnerHTML={{ __html: html }}></div>;
}
transformerNotationFocus()
の使用例
注意点として、Transformers は、クラスを適用するだけでスタイルは付属していないので、自分でスタイルを適用する必要があります。
以下の例は、transformerNotationDiff()
を使用した場合のスタイルを適用する例です。生成された HTML にdiff remove
とdiff add
の class が付与されて返却されるのでそこにスタイルが当たるようにしています。
<pre
class="shiki github-dark-dimmed has-diff"
style="background-color:#22272e;color:#adbac7"
tabindex="0"
>
<code>
<!-- 赤背景にする -->
<span class="line diff remove">
<span style="color:#ADBAC7">console.</span>
<span style="color:#DCBDFB">log</span>
<span style="color:#ADBAC7">(</span>
<span style="color:#96D0FF">'hewwo'</span>
<span style="color:#ADBAC7">) </span>
</span>
<!-- 緑背景にする -->
<span class="line diff add">
<span style="color:#ADBAC7">console.</span>
<span style="color:#DCBDFB">log</span>
<span style="color:#ADBAC7">(</span>
<span style="color:#96D0FF">'hello'</span>
<span style="color:#ADBAC7">)</span>
</span>
</code>
</pre>
.diff.remove {
background-color: red;
}
.diff.add {
background-color: green;
}
Theme Colors Manipulation
テーマを作成して使用したり、既存のテーマの一部の色を変更変更したりすることができる機能です。
自分の好みやプロジェクトのテーマに合わせて細かい調整ができそうです。詳しくはこちらを参照してください。
統合
いくつか用意されていますが個人的に面白いなと思ったのは TypeScript Twoslash
です
TypeScript Twoslash
はコードにカーソルを置くと型を表示できる機能です。
vite の公式ドキュメントでは v5.2 のアプデートから@shikijs/vitepress-twoslash
を使用することでこの機能を導入しています。
終わりに
以上、個人的に今来ていると感じるシンタックスハイライター shiki の紹介でした。
間違い等あればコメントにてお願いします。
参考
ユーザーファーストなサービスを伴に考えながらつくる、デザインとエンジニアリングの会社です。エンジニア積極採用中です!hrmos.co/pages/funteractive/jobs
Discussion