はじめに
個人的に、「Webアプリにコードエディタを組み込みたい」と思ったとき、Monaco Editor一択だと思っています。
GitHubスター44,900超え。MITライセンス。月間1,000万以上のnpmダウンロード。そしてなにより、VS Codeと同じエディタエンジンという安心感。
正直なところ、数年前まではWebでまともなコードエディタを実装するのは大変だったんですよ。Ace EditorやCodeMirrorもあったけど、VS Codeレベルの体験を提供するのは難しかった。でもMonaco Editorが登場してから、その状況が一変しました。
30代になって思うのは、「車輪の再発明」に時間を使うのがもったいないということ。コードエディタなんて、自分で1から作ったら何年かかるかわからない。Monaco Editorを使えば、数十行のコードでVS Code級のエディタが手に入る。これは使わない手はないですよね。
とはいえ、特徴を文章で並べるより、実際にMonaco Editorが動いているところを見た方が早いと思います。monaco.editor.create()で生成した本物のエディタをその場で操作できるサンプルを用意したので、先に触ってみたい方はこちらからどうぞ。
Monaco Editorとは
Monaco Editorは、Microsoftが開発しているブラウザベースのコードエディタライブラリです。
VS Codeのエディタ部分を切り出してWebで使えるようにしたもの、と考えるとわかりやすい。VS Codeを使ったことがある人なら、同じ操作感でコードが書ける。
特徴的なのは、単なるテキストエリアの強化版ではないということ。シンタックスハイライト、インテリセンス(自動補完)、エラー表示、複数カーソル、検索・置換、コード折りたたみ、ミニマップ。VS Codeにある機能のほとんどが使える。
2025年12月時点で、TypeScript、JavaScript、CSS、HTML、JSON、Python、Java、C#など、主要な言語はほぼ全て対応しています。
特徴・メリット
1. VS Codeと同じ編集体験
これ、意外と大きなメリットです。
開発者の多くがVS Codeを使っている。Monaco Editorを採用すれば、ユーザーは新しい操作を覚える必要がない。Ctrl+Dで同じ単語を選択、Alt+Clickでマルチカーソル、Ctrl+/でコメントアウト。全部そのまま使える。
学習コストゼロで本格的なエディタが使える。これがQOL上がるポイント。
2. 本格的なインテリセンス
Monaco Editorの真価はインテリセンスにあります。
- 自動補完: 変数名、関数名、プロパティ名を入力途中で候補表示
- 型情報: TypeScriptの型定義を認識して、適切な補完を提供
- ホバー情報: 変数や関数にカーソルを合わせると型情報を表示
- パラメータヒント: 関数の引数情報をリアルタイムで表示
単なるシンタックスハイライトじゃなくて、言語を理解した上での支援機能。これがブラウザで動くのは、正直すごい。
3. 複数言語のサポート
デフォルトで多くの言語をサポート:
- TypeScript / JavaScript
- HTML / CSS / SCSS / LESS
- JSON / YAML
- Python
- Java / C# / C++ / Go
- PHP / Ruby
- Markdown
- SQL
言語ごとにシンタックスハイライトのルールが定義されていて、追加設定なしで使える。
4. 差分エディタ
2つのファイルを比較する「Diff Editor」も標準装備。
GitのPRレビューUIとか、設定ファイルの比較機能とか、差分表示が必要な場面で重宝する。追加・削除・変更が色分けされて、どこが変わったか一目でわかる。
5. カスタマイズ性
テーマ、フォント、キーバインド、ほぼ全ての設定がカスタマイズ可能。
VS Codeのテーマをそのまま使えるのも良い。Dark+、Monokai、One Dark、好みのテーマを適用できる。
インストール方法
npmからインストール:
npm install monaco-editor
もしくはyarn/pnpm:
yarn add monaco-editor
pnpm add monaco-editor
パッケージにはESM版とTypeScript定義ファイル(monaco.d.ts)が含まれています。
Webpackの場合
monaco-editor-webpack-pluginを使うと設定が楽:
npm install monaco-editor-webpack-plugin
webpack.config.js:
const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
module.exports = {
plugins: [
new MonacoWebpackPlugin({
languages: ['javascript', 'typescript', 'json', 'html', 'css']
})
]
};
Viteの場合
vite-plugin-monaco-editorを使用:
npm install vite-plugin-monaco-editor
vite.config.ts:
import monacoEditorPlugin from 'vite-plugin-monaco-editor';
export default {
plugins: [
monacoEditorPlugin({})
]
};
Monaco Editorのサンプルを動かす
文章より先に、実際に手を動かして試してみましょう。以下はすべてmonaco.editor.create()で生成した本物のMonaco Editorです。エディタ内のコードは自由に書き換えられ、VS Codeと同じシンタックスハイライトが効いた状態で編集できます。
Monaco Editorはシンタックスチェックや補完のために内部でWeb Workerを使っています。webpackやViteでバンドルする場合は専用プラグインが設定を肩代わりしてくれますが、このサンプルのようにバンドラーを使わずCDNから直接読み込む場合は、AMDローダー(loader.js)経由でvs/editor/editor.mainを読み込む方式を使います。Monaco Editor 0.56.0ではeditor.main.js側でWorkerの生成まで面倒を見てくれるため、読み込み側で追加のWorker設定を書く必要はありません。
基本のMonaco Editorを生成する
まずは最小構成です。monaco.editor.create()にコンテナ要素と初期値・言語・テーマを渡すだけで、VS Code同等のエディタが表示されます。値の読み書きにはeditor.getValue()とeditor.setValue()を使います。
// AMDローダーでMonaco Editor本体を読み込んでから実行する
require.config({ paths: { vs: 'https://cdn.jsdelivr.net/npm/monaco-editor@0.56.0/min/vs' } })
require(['vs/editor/editor.main'], () => {
const editor = monaco.editor.create(document.getElementById('container'), {
value: 'console.log("Hello, Monaco!");',
language: 'javascript',
theme: 'vs-dark',
automaticLayout: true,
})
// 値の取得・設定
document.getElementById('reset').onclick = () => {
editor.setValue('console.log("Hello, Monaco!");')
}
})
下のサンプルでは、実際にエディタへコードを書き込んだあと、「初期コードに戻す」ボタンでeditor.setValue()による書き換えも確認できます。
自由にコードを書き換えてみてください。console.logの中身を変えたり、丸ごと消してから「初期コードに戻す」ボタンを押すと、editor.setValue()で元の内容に戻ることが確認できます。
言語を切り替える(JavaScript / TypeScript / JSON)
Monaco Editorは1つのエディタインスタンスのまま、表示言語を動的に切り替えられます。monaco.editor.setModelLanguage(model, 言語ID)を呼ぶだけで、シンタックスハイライトと補完の挙動がその言語用に切り替わります。
// エディタのモデルを取得し、言語を切り替える
const model = editor.getModel()
monaco.editor.setModelLanguage(model, 'typescript')
以下のサンプルでは、ボタンを押すたびにsetModelLanguageでJavaScript・TypeScript・JSONを切り替え、それぞれの言語のサンプルコードをsetValue()で流し込んでいます。
JavaScriptからTypeScriptに切り替えると、同じ関数でも型注釈(name: string)が付いたコードに変わり、Monaco側の言語モードもTypeScript用に切り替わっているのが分かります。設定ファイルの種類に応じてエディタの言語をユーザーに選ばせたい場面などで使えるパターンです。
onDidChangeModelContentで変更を検知する
エディタの内容が変わるたびに何か処理をしたい場合は、editor.onDidChangeModelContent()にコールバックを登録します。オートセーブやリアルタイムのバリデーション、文字数カウントなどに使えるイベントです。
// 内容が変更されるたびに呼ばれる
editor.onDidChangeModelContent(() => {
const value = editor.getValue()
console.log('文字数:', value.length)
})
以下のサンプルでは、コードを書き換えるたびに文字数と行数をリアルタイムで表示します。実際にコードを増やしたり消したりして、数値が即座に変わることを確認してみてください。
onDidChangeModelContentはキー入力のたびに発火するため、changeイベントのようにフォーカスが外れるまで待つ必要がありません。この仕組みを使えば、エディタの内容をリアルタイムで別のUIコンポーネントに反映させたり、一定時間操作がなければ自動保存する、といった実装が可能になります。
基本的な使い方
シンプルなエディタの作成
import * as monaco from 'monaco-editor';
// エディタを配置するコンテナ要素
const container = document.getElementById('editor');
// エディタの作成
const editor = monaco.editor.create(container, {
value: 'console.log("Hello, Monaco!");',
language: 'javascript',
theme: 'vs-dark',
automaticLayout: true
});
これだけで、VS Code風のエディタが表示される。
主要なオプション
const editor = monaco.editor.create(container, {
// 初期値
value: '',
// 言語
language: 'typescript',
// テーマ: 'vs', 'vs-dark', 'hc-black'
theme: 'vs-dark',
// 読み取り専用
readOnly: false,
// ミニマップの表示
minimap: { enabled: true },
// 行番号の表示
lineNumbers: 'on',
// 自動インデント
autoIndent: 'full',
// フォントサイズ
fontSize: 14,
// フォントファミリー
fontFamily: "'JetBrains Mono', monospace",
// タブサイズ
tabSize: 2,
// 折り返し: 'off', 'on', 'wordWrapColumn', 'bounded'
wordWrap: 'on',
// コンテナサイズに自動追従
automaticLayout: true,
// スクロール設定
scrollBeyondLastLine: false
});
エディタの値を取得・設定
// 値の取得
const code = editor.getValue();
// 値の設定
editor.setValue('const x = 1;');
// 言語の変更
monaco.editor.setModelLanguage(editor.getModel(), 'python');
イベントの監視
// 内容が変更されたとき
editor.onDidChangeModelContent((event) => {
console.log('Content changed:', editor.getValue());
});
// カーソル位置が変更されたとき
editor.onDidChangeCursorPosition((event) => {
console.log('Cursor position:', event.position);
});
// フォーカスを得たとき
editor.onDidFocusEditorText(() => {
console.log('Editor focused');
});
実践的なユースケース
Reactでの使用
import { useEffect, useRef } from 'react';
import * as monaco from 'monaco-editor';
interface CodeEditorProps {
value: string;
language: string;
onChange?: (value: string) => void;
}
export function CodeEditor({ value, language, onChange }: CodeEditorProps) {
const containerRef = useRef<HTMLDivElement>(null);
const editorRef = useRef<monaco.editor.IStandaloneCodeEditor | null>(null);
useEffect(() => {
if (!containerRef.current) return;
// エディタの作成
editorRef.current = monaco.editor.create(containerRef.current, {
value,
language,
theme: 'vs-dark',
automaticLayout: true,
minimap: { enabled: false },
fontSize: 14
});
// 変更イベントの監視
editorRef.current.onDidChangeModelContent(() => {
onChange?.(editorRef.current?.getValue() ?? '');
});
// クリーンアップ
return () => {
editorRef.current?.dispose();
};
}, []);
// 外部からの値の更新
useEffect(() => {
const editor = editorRef.current;
if (editor && editor.getValue() !== value) {
editor.setValue(value);
}
}, [value]);
return <div ref={containerRef} style={{ height: '400px', width: '100%' }} />;
}
差分エディタ(Diff Editor)
const diffEditor = monaco.editor.createDiffEditor(container, {
theme: 'vs-dark',
automaticLayout: true,
readOnly: true
});
diffEditor.setModel({
original: monaco.editor.createModel('const x = 1;', 'javascript'),
modified: monaco.editor.createModel('const x = 2;', 'javascript')
});
カスタムテーマの定義
monaco.editor.defineTheme('myCustomTheme', {
base: 'vs-dark',
inherit: true,
rules: [
{ token: 'comment', foreground: '6A9955', fontStyle: 'italic' },
{ token: 'keyword', foreground: 'C586C0' },
{ token: 'string', foreground: 'CE9178' }
],
colors: {
'editor.background': '#1a1a2e',
'editor.foreground': '#eaeaea',
'editor.lineHighlightBackground': '#2a2a4e'
}
});
// テーマの適用
monaco.editor.setTheme('myCustomTheme');
TypeScriptの型定義を追加
// 外部ライブラリの型定義を追加
monaco.languages.typescript.typescriptDefaults.addExtraLib(
`declare module 'lodash' {
export function debounce(func: Function, wait: number): Function;
export function throttle(func: Function, wait: number): Function;
}`,
'lodash.d.ts'
);
// compilerOptionsの設定
monaco.languages.typescript.typescriptDefaults.setCompilerOptions({
target: monaco.languages.typescript.ScriptTarget.ES2020,
module: monaco.languages.typescript.ModuleKind.ESNext,
strict: true,
esModuleInterop: true
});
コード補完のカスタマイズ
monaco.languages.registerCompletionItemProvider('javascript', {
provideCompletionItems: (model, position) => {
const word = model.getWordUntilPosition(position);
const range = {
startLineNumber: position.lineNumber,
endLineNumber: position.lineNumber,
startColumn: word.startColumn,
endColumn: word.endColumn
};
return {
suggestions: [
{
label: 'myCustomFunction',
kind: monaco.languages.CompletionItemKind.Function,
insertText: 'myCustomFunction(${1:param})',
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
documentation: 'カスタム関数の説明',
range
}
]
};
}
});
Monaco Editorの4つの核心概念
公式ドキュメントで説明されている4つの概念を理解しておくと、より高度な使い方ができます。
1. Models
ファイルの内容、言語、編集履歴を管理するオブジェクト:
// モデルの作成
const model = monaco.editor.createModel(
'const x = 1;',
'javascript',
monaco.Uri.parse('file:///main.js')
);
// 複数のエディタで同じモデルを共有可能
editor1.setModel(model);
editor2.setModel(model);
2. URIs
各モデルを一意に識別するパス。同じURIのモデルは1つしか存在できない。
3. Editors
ユーザーが実際に操作するDOM要素。1つのモデルを複数のエディタで表示可能。
4. Providers
インテリセンスやホバー情報などのスマート機能を提供するプロバイダー。
まとめ
Monaco Editorを使う理由:
- VS Code品質: 同じエディタエンジンで、同じ操作感
- 本格的な機能: インテリセンス、シンタックスハイライト、差分表示
- 多言語対応: TypeScript、Python、Java、ほぼ全ての主要言語
- カスタマイズ性: テーマ、キーバインド、補完、全て設定可能
- 活発な開発: 44,900スター、252名の貢献者、MITライセンス
正直なところ、Webアプリにコードエディタを組み込むなら、Monaco Editor一択だと思います。
オンラインIDE、設定ファイルエディタ、コードスニペット共有サービス、プログラミング学習サイト。コードを編集する機能が必要なら、まずMonaco Editorを検討してみてください。VS Code級の体験が、数十行のコードで手に入ります。