Monacoエディタを組み込んでみる

2024-01-14

以前はブラウザ上で動くテキストエディタとしてAceエディタを使ってみたが、自分が普段使うエディタはほぼVSCodeになってしまったので、ブラウザでも同じ方が自分にとって望ましい。 VSCodeはMonacoというオープンソースで公開されているので、使い方を調べてみた。

導入方法

Webpackを使用する場合

Webpackを使ってるプロジェクトに組み込むにはnpmモジュールの、

  1. Monaco Editor Webpack Loader Plugin
  2. Monaco Editor

のどちらでもいけるっぽい。

1のプラグインじゃなくて2の素のMonaco Editorを組み込む場合にはsytle-loaderもdevDependenciesに追加し、 webpack.configやJavaScriptでのインポート後にあれこれ記述してやる:

// webpack.config.js
module.exports = {
entry: {
main: './src/main.ts',

// Package each language's worker and give these filenames in `getWorkerUrl`
'editor.worker': 'monaco-editor/esm/vs/editor/editor.worker.js',
'json.worker': 'monaco-editor/esm/vs/language/json/json.worker',
'css.worker': 'monaco-editor/esm/vs/language/css/css.worker',
'html.worker': 'monaco-editor/esm/vs/language/html/html.worker',
'ts.worker': 'monaco-editor/esm/vs/language/typescript/ts.worker',
},
module: {
rules: [
{test: /\.css$/, use: ['style-loader', 'css-loader']},
...
],
},
...
// main.js
import * as monaco from 'monaco-editor'

self.MonacoEnvironment = {
getWorkerUrl: function (_moduleId, label) {
if (label === 'json') {
return './json.worker.js'
}
if (label === 'css' || label === 'scss' || label === 'less') {
return './css.worker.js'
}
if (label === 'html' || label === 'handlebars' || label === 'razor') {
return './html.worker.js'
}
if (label === 'typescript' || label === 'javascript') {
return './ts.worker.js'
}
return './editor.worker.js'
}
}

const editor = monaco.editor.create(document.getElementById('editor'), {
...
  • Webpack Loader Pluginがなにをやってくれるのか、素で使う場合と比べてなにが違うのかはよく理解してない…
  • Webpackで組み込むと大量のファイルが生成されまたサイズも結構大きい(合計11メガバイトほど)のと、かなり時間もかかる(自分の環境では2分半ほど)

CDNを利用する場合

Webpackで組み込むのはいろいろ重いので、CDNも試してみた。 How to run the Monaco editor from a CDN like cdnjs? - Stack Overflow を参考に、

で使用できる。 Webpackの場合importですぐmonacoが使えるが、CDN経由の場合コールバックを待つ必要がある (他の方法もいろいろ書いてある)。

API

自分が使った際に必要になった機能をいくつかメモ:

作成

const editor = monaco.editor.create(document.getElementById('editor'), {
value: 'テキスト内容',
language: 'c', //
minimap: {
enabled: false,
},
})

内容取得

指定の行を画面中央に表示されるようにする

テキスト選択

const range = new monaco.Range(err.sourceLineNo, err.colStart, err.sourceLineNo, err.colStart + err.tokenLength)
editor.setSelection(range)

プログラムから内容を書き換える

現在のカーソル位置にテキストを挿入するには:

editor.executeEdits('', [{
range: editor.getSelection(),
text: 'ほげほげ',
}])

ショートカットキー

editor.addAction({
id: 'save',
label: 'Save',
keybindings: [
monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS,
],
run: () => ...,
})

マウスクリックの位置

editor.onMouseDown((e) => {
const pos = e.target.position // {lineNumber, column}
...
})

テキスト内容が変更されているかどうか判定する

参考