はじめに
以前よりMonaco Editorを使って自分専用のテキストエディタを作っていたが、Vue2の頃であり、かなり知識も古くなっていた。
そこでVue3かつ、Vueに則ったMonacoEditorを使うべく、色々試して確認できた結果を覚書として記しておく。
VueのMonaco Editor
いくつかの実装がある。
ライブラリ名 | Vue対応 | 特徴 | 推奨度 |
---|---|---|---|
@guolao/vue-monaco-editor |
Vue 2/3 | CDN版、積極的な開発 | ⭐⭐⭐⭐⭐ |
monaco-editor-vue3 |
Vue 3 | TypeScript完全対応 | ⭐⭐⭐⭐ |
egoist/vue-monaco |
Vue 2/3 | 老舗、webpack必須 | ⭐⭐⭐ |
これらのライブラリは共通して、Monaco EditorをVueコンポーネントとしてラップし、v-modelを使った双方向データバインディングを提供している。しかし、あくまで簡単にMonaco EditorをVueで使うことを念頭に置いている模様。
自分は @guolao/vue-monaco-editor
を使うことにした。
基本的な使い方
昔自分が書いた記事で、Monaco Editorを素のJavascriptのまま使う方法を紹介した。
時代はだいぶ流れ、気づいたらMonaco Editorでも使用不可になったプロパティや名称が変わったプロパティ・メソッドもあったりして、だいぶ浦島太郎状態。
Vueの枠組みで使う限りは、以前のようなrequireを色々駆使したりする手間はどうやら不要な模様。
インストール
基本的には開発者様のGitHubリポジトリを見ていただければスムーズだ。
とはいえ、同じVueでもフレームワークによって若干違うのかもしれない。
自分は、Quasar というフレームワークのQuasar CLIというツール経由でセットアップして使うことにした。
まずは次を実行する。
npm intall monaco-editor @guolao/vue-monaco-editor
組み込み
vueの <template></template><script></script>
の記載の中では次のようにする。
<template>
<vue-monaco-editor
v-model:value="vmonaco.text"
:theme="vmonaco.theme"
:language=vmonaco.language
:options="vmonaco.options"
/>
</template>
<script setup>
import { VueMonacoEditor } from '@guolao/vue-monaco-editor';
import { reactive } from "vue"
const vmonaco = reactive({
text : "こんにちは世界。\nHello world.",
theme : "vs-dark",
language : "plaintext",
options : {
fontSize : 12,
minimap : { enabled : true },
automaticLayout : true
}
});
</script>
リアクティブな変数あたりは自分のオリジナルの組み方なので参考程度で。
Quasar CLIで生成したプロジェクトのソース構成では、特に Global Registration やLocal Registration をしなくても使えた。
解説
v-model:value は、valueに文字列を受け渡してエディタ部とデータを紐づける。
素のJavascriptだと、 monaco.editor.getEditors()[0].getModel().setValue
や monaco.editor.getEditors()[0].getValue()
などで値をやり取りしていた部分に相当する。
:theme はエディタ部のテーマ(配色)を設定する。
素のJavascriptだと、 monaco.editor.setTheme
をしていた部分だ。
:language はエディタの認識する言語モードを切り替える。
monaco.editor.setModelLanguage
や monaco.editor.getEditors()[0].getModel().getLanguageId()
などをしていた部分だ。
:options はエディタのオブションを設置する。
monaco.editor.getEditors()[0].getRawOptions()
や monaco.editor.getEditors()[0].updateOptions
をしていた部分だ。
他にもプロパティはあるが、基本的な使い方ではこれだけで十分Vueで高度なテキストエディタが作れるわけだ。
変更を検知するイベントも、素のMonaco Editorでは onDidChangeModelContent
や onDidChangeCursorPosition
などと長ったらしい名前だが、vueのこのコンポーネントを使うと、 @change
や @validate
などと短くて済む。
もちろん後述の素のエディタインスタンスなどを参照して、イベントを直接設定しても良い。
疑問
Monaco Editorに慣れた人ならこれでふと気づくかもしれない。
「モデルの管理は?」
そう。Monaco Editorは単なるテキストエディタというわけではない。
エディタ部とモデル部があり、1つのエディタに複数のモデルを紐づけることで、非常に簡単に複数文書を切り替えて使うことができるのだ。
その文書の単位が、ITextModel型たる モデル だ。
モデルを使った文書内容の切り替え
上記コンポーネントの使用例だと、 v-model:value
に紐づけたデータバインディングの変数に値をセットすることで、随時エディタの内容を切り替えることができるようになる。
しかし、それだけだ。
実際に内部的には一つのモデルに対して、何度も上書き更新するだけになってしまう。
それではMonaco Editorの真価を発揮することはできない。
そこで使うのが、 path
プロパティだ。
<template>
<!--vueの流儀で使用した例-->
<vue-monaco-editor
v-model:value="vmonaco.text"
:theme="vmonaco.theme"
:language=vmonaco.language
:options="vmonaco.options"
/>
<!--Monaco Editorのモデルをフル活用する例-->
<vue-monaco-editor
v-model:path="vmonaco.model_path"
:theme="vmonaco.theme"
:language=vmonaco.language
:options="vmonaco.options"
/>
</template>
<script setup>
import { VueMonacoEditor } from '@guolao/vue-monaco-editor';
import { reactive } from "vue"
const vmonaco = reactive({
text : "こんにちは世界。\nHello world.",
model_path : "/1",
theme : "vs-dark",
language : "plaintext",
options : {
fontSize : 12,
minimap : { enabled : true },
automaticLayout : true
}
});
</script>
v-model:valueのままだと、1つのモデルに何度も値を書き換えることになってしまう。
そこでpathを使うのだ。
pathを使って一つのエディタに複数のモデルを切り替えさせる
path
は、現在のモデルのパスということになっている。
※似たプロパティに defaultPath
もある。
実はMonaco EditorのITextModelには、uriプロパティが存在する。
uriプロパティを見ると、次のようになっている。
pathは、 "/1" という値になっている。つまり文書の1つ目だ。
フル表記は file:///1 だ。
ここはメモリにモデルを作成すると inmemory となる。つまりモデルを保持する領域によって異なる。
このuriプロパティをvueのMonaco Editorへ紐づけるのに大事なのは、 uri.path
プロパティということになる。
つまり、 path
プロパティには、ITextModelの uri.path
をセットすることで、そのモデルが保持するテキスト内容をエディタに表示して編集できるようになる。
vueの双方向データバインディングの考え方と、Monaco Editorのモデル式のデータの管理の考え方が併用できるのだ。
さらなる疑問(monacoへのアクセス)
pathでモデルを切り替えられるのはわかった。
でも、vueのMonaco Editorだけでは、Monaco Editorの多彩な機能使えないでしょ。新しいモデルの作成は? エディタの高度な機能へのアクセスは?
そんな疑問ももっともである。
そこで使うのが、 @mount
イベントだ。
このイベントを使うと、Monaco Editorの初期化が完了したときにイベントを発火させることができる。
<template>
<vue-monaco-editor
v-model:path="vmonaco.model_path"
:theme="vmonaco.theme"
:language=vmonaco.language
:options="vmonaco.options"
@mount="handleMount"
/>
</template>
<script setup>
import { VueMonacoEditor } from '@guolao/vue-monaco-editor';
import { reactive } from "vue"
const vmonaco = reactive({
text : "こんにちは世界。\nHello world.",
model_path : "/1",
theme : "vs-dark",
language : "plaintext",
options : {
fontSize : 12,
minimap : { enabled : true },
automaticLayout : true
},
parent : null
});
const handleMount = (editorInstance, monacoObject) => {
console.log(editorInstance);
console.log(monacoObject);
vmonaco.parent = monacoObject;
}
</script>
このイベントでは2つの引数を受け取って処理できる。
editorInstance は、初期化が完了したエディタ IEditorのインスタンスを取得できる。
monacoObject は、なんとmonacoつまり従来使っていたmonacoオブジェクトを参照できるのだ。
vueは素のjavascriptと異なり、初期化のタイミングなどがずれることがある。
そのままmonacoを参照しても、初期化が済んでおらず参照できない場合がありうる。
そんなとき、@mount
で受け取れる 2つ目の引数である monacoObject
は確実に参照できる。
これを別のリアクティブな変数に保持しておけば、アプリ内の好きなタイミングで安全に使用できるだろう。
もう一つの editorInstance
も、現在のエディタに生でアクセスできるので、vueでは足りない機能の実行は、これを介して行うとよいだろう。
(例:undoの実行やMonaco Editorが本来持つテキストの選択、編集機能など)
安全にモデルを切り替える
ここまでを踏まえて、たとえばどこかのボタンにモデルを切り替える処理を定義しておけば、それだけでもうモデルを動的に切り替えて編集できるマルチなエディタの完成だ。
//monacoObjectをvmonaco.parentに保持した例
function chtest1() {
vmonaco.model_path = vmonaco.parent.editor.getModels()[1].uri.toString();
}
function chtest2() {
vmonaco.model_path = vmonaco.parent.editor.getModels()[0].uri.toString();
}
これで、バックエンドで退避しておいたモデル0とモデル1を切り替えられる。
注意事項
vue-monaco-editorコンポーネント上で、v-model:valueとpathを同時に使用すると、内部的には2つのモデルが発生してしまう。
<!-- ❌ 避けるべきパターン -->
<vue-monaco-editor
v-model:value="content" <!-- valueモデルで管理 -->
:path="currentPath" <!-- pathも同時に指定 -->
:language="language"
/>
エディタ部に直接描画されないモデルをうっかり複数作ってしまうと、管理がしづらくなるしわかりづらい。
なによりメモリの無駄になる。
次のような使い分けがいいだろう。
シンプルな単一文書のみのテキストエディタ : v-model:value 形式
VSCodeばりに複数文書を扱うテキストエディタ : pathまたはv-model:path 形式
2025/09/21追記
pathだけの管理の状態で、ITextModelのsetValueを呼び出して値をセットすると、ウェブページがフリーズしてしまうことが判明。
おそらく、MonacoEditorの編集機能とVueのライフサイクルが競合を起こしている可能性が高いと判断。
MonacoEditorの内部的な管理が複雑すぎて、vueと相性が悪くなっている。この点は要調査としたい。
素直に、v-model:valueだけを使う分には問題ないだろう。
終わりに
AIもこのことを細かく知らなかったようで、Gensparkとのやりとりでこの発見を報告したら、驚かれてしまった。
そりゃ、知識のベースは世の中に公開されているGitHubの各内容次第だから、そこにプロパティ自体の記載は合ったとしても、細かく触れられていなければAIも想定だけでは解説しようがないのだろう。
実験して試して知見を溜められるのは人間の特権だ。
今回の記事が、Monaco Editorをvueベースで使いたい人のヒントになれれば幸いだ。