◆ 色付け機能だけをロードするのは難しそうなのでエディタ全体をロード
◆ 機能としてはとてもいいんだけど 重いのが欠点

ソースコードでキーワードなどに色を付けてくれるライブラリに highlight.js や prism.js があります
このブログでは今のところ highlight.js を使っています

highlight.js で基本的には悪くはないのですが ところどころ正しく色がつけられていないところがあります
途中で構文エラーとみなされたのか 途中までしか色がついてなかったりです

普段使ってる VSCode だと色付けで問題を感じたことはほぼないので VSCode で使われてる monaco-editor を試してみることにしました

monaco-editor のソースコードは monaco-editor のリポジトリにありますが ここは言語ごとのパースに必要な情報を管理しているリポジトリです
https://github.com/microsoft/monaco-editor

色付けだったり エディタとしての機能は monaco-editor-core というパッケージで VSCode のリポジトリ内にあります
https://github.com/microsoft/vscode/tree/main/src/vs/editor

vs/editor の中が monaco-editor-core になります

色付け機能のモジュールはこれです
https://github.com/microsoft/vscode/blob/main/src/vs/editor/standalone/browser/colorizer.ts

この Colorizer.colorize 関数を直接使えたら良かったのですが 引数に要求される languageService はエディタ機能の内部的なもので 対応している言語等を管理しているオブジェクトです
このモジュールだけを使う方法では この引数を渡すのは難しそうだったのでエディタ全体をロードすることにしました

こんな感じで エディタ全体をロードしてもコードは短く書けます

<script src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.32.1/min/vs/loader.min.js"></script>
<script>
require.config({ paths: { "vs": "https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.32.1/min/vs" } })

require(["vs/editor/editor.main"], () => {
monaco.editor.colorizeElement(element)
})
</script>

ページ全体としての例はこんな感じです

<!doctype html>
<meta charset="utf-8" />

<script src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.32.1/min/vs/loader.min.js"></script>
<script>
require.config({ paths: { "vs": "https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.32.1/min/vs" } })

require(["vs/editor/editor.main"], () => {
monaco.editor.colorizeElement(document.querySelector("code[data-lang]"))
})
</script>

<style>
pre {
border: 1px solid #888;
padding: 10px;
}
</style>

<pre><code data-lang="html">&lt;script&gt;
function f(a) {
const b = a + 1
for (let x = 0; x &lt; 10; x++) {
continue
}
const c = html`
&lt;div class="a"&gt;123&lt;/div&gt;
`
return null
}
&lt;/script&gt;
&lt;style&gt;
h1 {
color: red;
}
&lt;/style&gt;
&lt;/div class="a b c"&gt;
&lt;span&gt;xyz&lt;span&gt;
&lt;/div&gt;</code></pre>

HTML 内の script タグや style タグ内も正しく色付けされています
色だけではなくタブサイズ調整も含まれるので ロード後に色や文字位置が少し変わります
気になるなら CSS で色付け後に表示するようにもできます
色付け完了後に対象の要素にテーマ名のクラスが追加されるのでこういう感じです

	pre code {
opacity: 0;
transition: opacity .5s ease 0s;
}
pre code.vs {
opacity: 1;
}

テーマを標準以外のものにしたい場合は 第二引数のオプションで指定できます
色付け後に setTheme 関数を呼び出して変更もできます

monaco.editor.colorizeElement(element, { theme: "vs-dark" })

または

monaco.editor.colorizeElement(element)
monaco.editor.setTheme("vs-dark")

後から setTheme をする場合は要素に追加されたクラス名は変更されません

colorizeElement 関数で色が変わるのはコードの文字部分のみです
背景色は設定されないので ダークテーマにする場合の背景色は自分で設定が必要です

ダークテーマを使った例です

<!doctype html>
<meta charset="utf-8" />

<script src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.32.1/min/vs/loader.min.js"></script>
<script>
require.config({ paths: { "vs": "https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.32.1/min/vs" } })

require(["vs/editor/editor.main"], () => {
monaco.editor.colorizeElement(document.querySelector("code[data-lang]"), { theme: "vs-dark" })
})

</script>

<style>
pre {
border: 1px solid #888;
background: #1E1E1E;
padding: 10px;
}
pre code {
opacity: 0;
transition: opacity .5s ease 0s;
}
pre code.vs-dark {
opacity: 1;
}
</style>

<pre><code data-lang="html">&lt;script&gt;
function f(a) {
const b = a + 1
for (let x = 0; x &lt; 10; x++) {
continue
}
const c = html`
&lt;div class="a"&gt;123&lt;/div&gt;
`
return null
}
&lt;/script&gt;
&lt;style&gt;
h1 {
color: red;
}
&lt;/style&gt;
&lt;/div class="a b c"&gt;
&lt;span&gt;xyz&lt;span&gt;
&lt;/div&gt;</code></pre>

テーマはビルトインテーマの vs, vs-dark, hc-black が使えます
それ以外にも defineTheme 関数を使って自作も可能です
ビルトインテーマの設定はここにあるので これを参考に少し色を変えるだけという使い方もできます
https://github.com/microsoft/vscode/blob/main/src/vs/editor/standalone/common/themes.ts

monaco-editor は VSCode で使われてるだけあって highlight.js よりも高機能ですが その分ロードが遅いのが欠点です
色付けだけでエディタとしての機能は使わないのにエディタ全体をロードするので仕方ないのですけど
サイズは minify + br でも 500KB を超えています
このブログの highlight.js を置き換えるかは迷うところです