ソースコードのシンタックスハイライトはどのように対応していますか? 普段コードを書いていると、Qiita やZenn などの技術記事プラットフォームや技術系のブログなどで、ハイライトされたコードを見ることは多いのではないでしょうか?
Shiki は、シンタックスハイライトのためのJavaScriptライブラリです。多くの主要プログラミング言語に対して、非常に正確で高速なシンタックスハイライトを提供します。名前の由来は日本語の「式」で、「スタイル」を意味しています。
WebフレームワークのAstro やNuxt のコンテンツ管理モジュールであるNuxt Content 、スライド作成ツールの Slidev などでもシンタックスハイライトに利用されています。
本サイトのコードブロックも、すべてShikiを利用してハイライトしています!
Shikiの特徴 シンタックスハイライトのためのJavaScriptライブラリというと、代表的なものにPrism やhighlight.js があります。Prismは、軽量で拡張性が高く、プラグインを利用して機能を追加できるといった特徴があります(現在は、v2への移行に向けて2022年頃から開発がストップしている状態のようです Roadmap for Prism v2 )。highlight.jsは、200以上のプログラミング言語をサポートし、プログラムコードの言語を自動で検出してハイライトするといった機能があります。直近1年間のnpmダウンロード数を見ると、どちらも多くのサイトで利用されていることが伺えます。
highlight.js vs prismjs vs shiki | npm trends では、今回紹介するShikiはどのような特徴があるのでしょうか?
VSCodeと同じTextMate文法エンジン ShikiはVSCodeのシンタックスハイライトと同じ、TextMateの文法とテーマをベースにしています。そのため、VSCodeの更新に伴ってShikiの文法とテーマも更新されます。執筆時点(2024/07/10)では、デフォルトで46のテーマと208の言語をサポートしており、カスタマイズしたテーマや言語を使用することもできます。
公式サイトでは、これらのテーマと言語をプレビューできるPlaygroundも用意されており、使用したいテーマを事前にプレビューすることができます。
あらゆるJavaScriptランタイム上で動作 Node.jsのAPIやファイルシステムに依存せず、ブラウザ、Node.js、Cloudflare Workersなど、最新のJavaScriptランタイムで動作します。例えば、Shikiのドキュメントサイト ではコードブロックはビルド時にレンダリングし静的に配信され、前述のPlaygroundのみ、クライアントサイドでレンダリングされているようです。
高度なカスタマイズが可能 ハイライトするコードの指定範囲に、独自のクラスや属性を適用できる装飾用のAPIが提供されています。これにより、コードの特定箇所に枠をつけるなどの処理が簡単に行えるようになっています。
また、Shikiはhast (HTMLのASTフォーマット)を使用してHTMLを生成します。このhastを操作する処理(トランスフォーマー)を独自に書くことで、生成されるHTMLを柔軟にカスタマイズすることができます。
柔軟なバンドル メインのShiki
エントリーには、サポートされているすべてのテーマと言語がバンドルされています。文法が使用されるときにのみインポート・ダウンロードされるため、パフォーマンスに優れています。また、ブラウザのランタイムで使用する場合などに細かくコントロールしたい場合は、独自にバンドルを構成することも可能です。あらかじめ構成された以下の2つのバンドルも提供されています。
基本の使い方 Shikiの特徴がなんとなく分かってきたと思いますので、ここから基本的な使用方法について紹介します。
導入 Shikiを利用するには、npm経由でインストールするか、CDNで利用することができます。今回はnpm経由での利用方法を紹介します。CDNでの利用については公式ドキュメント を参照してください。
簡単な使用例 まずは、Shikiが提供するcodeToHtml
関数を用いることで、簡単に使い始めることができます。言語とテーマを指定し、対象のコードをcodeToHtml
関数に渡すと、ハイライトされたHTML文字列が返されます。
TypeScript
import { codeToHtml } from "shiki"
const code = "const a = 1" // 表示するコード
const html = await codeToHtml (code, {
lang: "javascript" , // 言語を指定
theme: "github-light" // テーマを指定
})
HTML文字列には、各トークンのインラインスタイルが含まれているので、スタイルを設定するための追加のCSSは必要ありません。
戻り値のHTML文字列
< 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" > a</ span >
< span style = "color:#D73A49" > =</ span >
< span style = "color:#005CC5" > 1</ span >
</ span >
</ code >
</ pre >
これを表示すると、以下のようになります。
codeToHtml
関数の他にも、codeToTokens
やcodeToHast
を使用して中間データ構造を取得し利用することもできます。
参照:https://shiki.style/guide/install#usage
同期的にハイライトする 先ほど使用したcodeToHtml
は、テーマと言語をロードするために非同期に実行されます。コードを同期的にハイライトする場合は、getHighlighter
関数を使用して事前にインスタンスを作成します。
TypeScript
import { getHighlighter } from "shiki"
const code = "const a = 1"
// 事前にインスタンスを作成
const highlighter = await getHighlighter ({
langs: [ "javascript" ],
themes: [ "github-light" ],
})
// 作成したインスタンスを使用して同期的にハイライト
const html = highlighter. codeToHtml (code, {
lang: "javascript" ,
theme: "github-light"
})
また、ハイライト作成後にテーマや言語をロードしたい場合は、loadTheme
やloadLanguage
メソッドを使うことができます。
TypeScript
// テーマを追加
await highlighter. loadTheme ( "github-dark" )
// 言語を追加
await highlighter. loadLanguage ( "css" )
すべてのテーマと言語を読み込みたい場合は、bundledLanguages
とbundledThemes
からすべてのキーを読み込むことができますが、こちらは推奨されていません。
参照:https://shiki.style/guide/install#highlighter-usage
発展的な機能 ライトモード・ダークモード(デュアルテーマ) Shikiはライトモード・ダークモードのデュアルテーマ出力をサポートしています。
設定方法は、まずthemes
オプションのlight
とdark
キーにそれぞれ利用したいテーマを指定します。
TypeScript
import { codeToHtml } from "shiki"
const code = "const a = 1"
const html = await codeToHtml (code, {
lang: "javascript" ,
themes: {
light: "github-light" , // ライトモードのテーマを指定
dark: "github-dark" , // ダークモードのテーマを指定
}
})
Shikiは各トークンの色を保存するためにCSS変数を使用しているため、以下のどちらかの方法でCSSスニペットを追加する必要があります。プレフィックスはオプションで変更することも可能です(デフォルトは--shiki-
)。
CSSスニペット:クエリーベース
@media (prefers-color-scheme: dark) {
.shiki ,
.shiki span {
color : var ( --shiki-dark ) !important ;
background-color : var ( --shiki-dark-bg ) !important ;
/* フォントスタイルにも対応する場合は以下のオプションも追加 */
font-style : var ( --shiki-dark-font-style ) !important ;
font-weight : var ( --shiki-dark-font-weight ) !important ;
text-decoration : var ( --shiki-dark-text-decoration ) !important ;
}
}
CSSスニペット:クラスベース
html .dark .shiki ,
html .dark .shiki span {
color : var ( --shiki-dark ) !important ;
background-color : var ( --shiki-dark-bg ) !important ;
/* フォントスタイルにも対応する場合は以下のオプションも追加 */
font-style : var ( --shiki-dark-font-style ) !important ;
font-weight : var ( --shiki-dark-font-weight ) !important ;
text-decoration : var ( --shiki-dark-text-decoration ) !important ;
}
これで、ライトモード・ダークモードによってテーマを変更することができるようになりました。
複数のテーマ ライトモード・ダークモードのみではなく、2つ以上のテーマをサポートすることも可能です。themes
オプションは、任意の数のテーマを指定することができます。defaultColor
オプションでデフォルトのテーマを指定することもできます。
TypeScript
import { codeToHtml } from "shiki"
const code = "const a = 1"
const html = await codeToHtml (code, {
lang: "javascript" ,
themes: {
light: "github-light" ,
dark: "github-dark" ,
dim: "github-dimmed" ,
// ... 任意の数を追加可能
},
// オプションでデフォルトのテーマを指定
defaultColor: "light" ,
})
コードの装飾 Shikiにはコードをハイライトするだけではなく、コードの特定箇所に独自のクラスを適用するなど、装飾を細かくコントロールするいくつかの機能があります。
Decorations decorations APIを使用することで、独自のクラスや属性を指定の範囲に適用することができます。使用方法は、decorations
オプション内にstart
とend
で適用範囲を指定し、properties
に任意のプロパティを指定します。例として、highlighted-word
クラスを用意し、コード内の指定箇所に適用してみます。
TypeScript
import { codeToHtml } from "shiki"
const code = `
const greeting = "Hello World!"
console.log(greeting)
` . trim ();
const html = await codeToHtml (code, {
lang: "javascript" ,
theme: "github-light" ,
decorations: [
{
start: { line: 1 , character: 12 }, // 開始位置を指定
end: { line: 1 , character: 20 }, // 終了位置を指定
properties: { class: "highlighted-word" }, // 適用するプロパティを指定
},
],
});
CSS
.highlighted-word {
border : solid 1 px #555555 ;
padding : 0 2 px ;
}
表示結果は以下のようになります。2行目の13文字目から20文字目まで、「greeting」の箇所にhighlighted-word
のスタイルが適用されていることがわかります。
Transformers Shikiはhast (HTMLのASTフォーマット)を使用してHTMLを生成しています。Transformers機能を利用すると、このhastを操作する処理を独自に書くことができます。コードの特定の行にアンダーラインを引いたり、diffを表示するといったことが可能です。
使用方法は、transformers
オプション内で変換用のHookを利用した処理を記述します。以下のようなHookが用意されており、任意のタイミングで処理を挟みむことができます。
出典:https://shiki.style/guide/transformers#transformer-hooks 例えば、特定の行に装飾を加えたい場合は、line
を使用します。
TypeScript
import { codeToHtml } from "shiki" ;
const code = `
const greeting = "Hello World!"
console.log(greeting)
` . trim ();
const html = await codeToHtml (code, {
lang: "javascript" ,
theme: "github-light" ,
transformers: [
{
// 2行目にアンダーラインを追加
line ( node , line ) {
if (line === 2 ) this . addClassToHast (node, "underline" );
},
},
],
});
これで、以下のように2行目にアンダーラインを引くことができました。
また、別パッケージとして提供される@shikijs/transformers には、いくつかの便利なTransformersが用意されています。こちらをインストールして利用することで、コードのdiff表示 や特定の行をハイライト するといったことが簡単に行えます。
npm i -D @shikijs/transformers
コードのdiff表示を追加したい場合は、transformerNotationDiff
を使用します。transformers
オプションにインポートしたtransformerNotationDiff()
を追加し、コードの差分表示したい行の末尾に// [!code --]
と// [!code ++]
をそれぞれ追加します。
TypeScript
import { codeToHtml } from "shiki" ;
import { transformerNotationDiff } from "@shikijs/transformers" ;
const code = `
const greeting = "Hello World!" // [!code --]
const greeting = "Hello Japan!" // [!code ++]
console.log(greeting)
` . trim ();
const html = await codeToHtml (code, {
lang: "javascript" ,
theme: "github-light" ,
transformers: [ transformerNotationDiff ()],
});
これで、// [!code ++]
を加えた箇所は<span class="line diff add">
、// [!code --]
を加えた箇所は<span class="line diff remove">
とそれぞれclassが付与されたアウトプットが得られます。スタイル自体は生成されないため、好みのスタイルを追加します。
CSS
.line.diff.remove {
background-color : #fef2f2 ;
}
.line.diff.remove::before {
margin-right : 4 px ;
content : "−" ;
color : #dc2626 ;
}
.line.diff.add {
background-color : #f0fdf4 ;
}
.line.diff.add::before {
margin-right : 4 px ;
content : "+" ;
color : #16a34a ;
}
表示結果は以下のようになりました。
まだまだ進化するShiki 今回は、シンタックスハイライター「Shiki」の特徴と基本機能について簡単に紹介しました。
Shikiには今回紹介した機能以外にも、まだまだ便利なものが備わっています。いくつか用意されているインテグレーション を利用し、Towslash やMonaco Editor などを、機能統合して使用することもできます。また、最新のv1.10
ではコード・スニペットの一部をハイライトしたい場合などに便利なGrammarState
の機能が追加されました。(参考:https://github.com/shikijs/shiki/pull/712 )
まだまだ進化するShikiを利用して、コードブロックに美しいハイライトを施してみてください!
参考資料 ウェブのお悩み、世路庵にご相談ください ウェブ制作会社には、「言ったことしかやってくれない」「提案がない」といった不満を抱かれるケースがあります。目を引くようなビジュアルは作れるがビジネス理解が不足している、運用はしてもらえるがデザインやコーディングは外注に丸投げしている、といった体制では、しばしばプロジェクトが袋小路に迷い込んでしまいます。
世路庵は、ビジネスとクリエイティブを両立するウェブ制作会社です。ウェブサイトやウェブアプリケーションに課題を感じている方は、創業16年以上の経験と、業種・業態を選ばない800件以上の実績を持つ世路庵をぜひご検討ください。
合同会社世路庵