はじめに
個人的に、「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は、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({})
]
};
基本的な使い方
シンプルなエディタの作成
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級の体験が、数十行のコードで手に入ります。