Monaco EditorをSvelteKitで使ったメモ
SvelteKitでManaco Editorを少し使ってみたので、備忘録として記事にしました。
Monaco EditorはVS Codeから作られたブラウザで動作するテキストエディタで、シンタックスハイライトや予測変換などといったVS Codeで使える機能を、手軽に自分のWebアプリケーションに組み込めるようになります。
今回は、Monaco EditorをSvelteコンポーネントとしてラップし、Web Worker周りの実装をしました。
サンプル
サンプルアプリはGitHub Pagesで公開しており、以下のURLからアクセスできます。
https://dorayaki4369.github.io/monaco-editor-with-sveltekit/
リポジトリは以下になります。
https://github.com/dorayaki4369/monaco-editor-with-sveltekit
サンプルアプリの機能は以下のとおりです。
- Monaco Editorによるコード編集
- 言語切り替え
- Light/Darkテーマ切り替え
環境
使用した主要なライブラリは以下のようになります。
- svelte ^5.0.0
- sveltekit ^2.22.0
- monaco-editor ^0.52.2
- monaco-yaml ^5.4.0
- typescript ^5.0.0
- vite ^7.0.4
- pnpm 10.13.1
- Node.js 22.17.1
プロジェクトの作成
まず、SvelteのドキュメントにあるCreating a projectを参考にプロジェクトを作成します。
npx sv create monaco-editor-with-sveltekit
cd monaco-editor-with-sveltekit
pnpm install
その後、monaco-editorをインストールすれば完了です。
今回はyamlにも対応するため、monaco-yamlもインストールしています。
pnpm add monaco-editor monaco-yaml
Editorコンポーネントの作成
Monaco EditorをラップするEditor.svelteコンポーネントを作成します。
いきなりですが、完成系は以下のようになりました。
実装ポイントとして、このコンポーネントは全体的に以下のような構成で実装しています。
- global stateの
themeをインポート - propsの定義
- 内部で使用する変数の定義(初期化はしない)
- Monaco Editor初期化関数
- Monaco Editor初期化関数を実行する
onMount - プロパティ変更時に実行されるMonaco Editorの状態を変えるスクリプト
<script lang="ts">
import type * as monaco from "monaco-editor";
import { onMount } from "svelte";
// global stateのthemeインポート
import { theme } from "$lib/theme.svelte";
// propsの定義
let {...} = $props<...>();
// DOMエレメントやeditor、monacoなど、内部で使用する変数の定義
let container: HTMLElement;
let editor: monaco.editor.IStandaloneCodeEditor;
let m: typeof import("monaco-editor");
// プロパティ変更時に実行されるMonaco Editorの状態を変えるスクリプト
$effect(() => {...});
...
// Monaco Editor初期化関数
async function innitializeMonacoEditor() {
...
}
// Monaco Editor初期化関数の実行&dispose関数の実装
onMount(() => {
initializeMonacoEditor();
return () => editor?.dispose();
});
</script>
<div id="editor" bind:this={container} {...props} class={props.class}></div>
重要なのは、Monaco Editorはクライアントサイドで確実に初期化されるようにすることと、プロパティの変更をMonaco Editorに反映させる関数の実装です。
それぞれのポイントは以下のとおりです。
1. global stateのthemeをインポート
このサンプルアプリでは、 ユーザーによって任意に変えられるLight/Darkテーマの切り替え機能の状態管理を、別モジュールの$lib/theme.svelte.tsファイルにて行なっています。
theme変数を読み込み、Monaco Editorのテーマを切り替えられるようにします。
2. propsの定義
これはSvelteのRuneを使ったプロパティの実装になります。
value(テキスト内容)をbindingすることで、このエディタで行ったコードの変更が親コンポーネントにも伝わるようにしています。
3. 内部で使用する変数の定義
内部で使用する変数として、エディタのコンテナとなるDOMエレメントの変数であるcontainer、エディタの変数であるeditor、monaco変数の3つがあります。
これらの変数は、containerを除き、onMount変数で初期化され、$effect関数内で使用されます。
4. Monaco Editor初期化関数
Monaco Editorの初期化を行う関数を定義します。
この関数はonMountから切り出したもので、monacoを動的インポートし、getWorker関数の定義、エディタのインスタンス化をしています。
動的インポートをするのは、サーバーサイドでインポート処理が実行されないようにするためです。
getWorker関数内でもWeb Workerを動的インポートするようにしています。
これは、$propsで渡された言語のWeb Workerのみをインポートするようにしているため、効率的にWeb Workerを取得することができるようになっています。
getWorkerの定義後、m.editor.create関数で、monaco editorをインスタンス化し、3で定義したeditor変数に代入しています。
また、onDidChangeModelContext関数に、エディタで更新されたテキスト内容をbindingされたvalueに代入するリスナーを登録しています。
これによって、エディタの最新のテキスト内容を親コンポーネントに伝えることができるようになっています。
5. Monaco Editor初期化関数を実行するonMount
先ほど定義した初期化関数をonMountで実行します。
また、戻り値でMonaco Editorをdisposeする関数を返すことで、このコンポーネントが使用されなくなったタイミングでMonaco Editorもクリーンアップされるようになります。
6. プロパティ変更時に実行されるMonaco Editorの状態を変えるスクリプト
これまでの処理でMonaco Editorは初期化されますが、プロパティの変更があったとしてもMonaco Editorにはその変更が伝わらないため、プロパティの変更に応じてMonaco Editorの状態を変える関数を実装する必要があります。
このコンポーネントでは、$effectを使用してlanguageとtheme変数が変わった際にMonaco Editorの設定も変えるようにしています。
各関数の最初の行で$:と書いていますが、この行がないとプロパティが変化しても関数が実行されなかったので注意が必要です。(値に依存している関数だとコンパイラに判定されない?)
まとめ
本記事ではSvelteKitとMonaco Editorを組み合わせてみたところを記事にしました。
今回の実装だと対応言語は少ないですが、ブラウザ上で手軽にVS Codeと同じエディタが動くのはとても良いですね!
この実装の発展系として、LSPサーバーに接続して対応言語を増やしてみたいと思います。
Discussion