Monaco Editor は、Visual Studio Code のコアエンジンとして知られる高機能コードエディタです。このエディタを React アプリケーションにオープンソースで取り入れるためのラッパーとして、react-monaco-editor が広く利用されています。本稿では、このライブラリを活用して、本格的なコード編集機能を備えた UI を実装するための手順と実践的チューニング方法を解説します。
導入準備と基本構成
まず、プロジェクトにライブラリを追加します。
npm install react-monaco-editor monaco-editor
# または
yarn add react-monaco-editor monaco-editor次に、编辑器コンポーネントを簡易実装します。ステート管理とエディタ初期化ロジックを分離し、再利用可能な設計としています。
import React, { useState, useCallback } from 'react';
import Editor from 'react-monaco-editor';
const CodeEditor = () => {
const [source, setSource] = useState('// 対応している言語: JavaScript, TypeScript, JSON, CSS など');
const handleUpdate = useCallback((value) => {
setSource(value || '');
}, []);
const config = {
automaticLayout: true,
fontSize: 14,
lineNumbers: 'on',
minimap: { enabled: true, maxColumn: 80 },
tabSize: 2,
theme: 'vs-dark',
wordWrap: 'on'
};
return (
<div style={{ height: '500px' }}>
<Editor
value={source}
language="typescript"
options={config}
onChange={handleUpdate}
editorDidMount={(editor) => {
editor.focus();
}}
/>
</div>
);
};
export default CodeEditor;ビルド시스템調整: Webpack プラグインの統合
Monaco Editor は Webpack の動的インポートに依存しているため、ビルド設定での特別扱いが必要です。Monaco 提供の Webpack Plugin を利用し、必要な言語ファイルと機能モジュールのみを含めるように最適化します。
// webpack.config.js
const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
module.exports = {
plugins: [
new MonacoWebpackPlugin({
languages: ['javascript', 'typescript', 'json', 'html', 'css'],
features: ['accessibilityHelp', 'bracketMatching', 'caretOperations', 'cursorUndo', 'dnd', 'find', 'gotoLine', 'hover', 'iPadShowKeyboard', 'inPlaceReplace', 'linesOperations', 'mouseWheelScroll', 'multiCursor边际操作', 'referenceSearch', 'rename', 'smartSelect', 'suggest', 'toggleTabFocusMode', 'transpose']
})
],
module: {
rules: [
{
test: /monaco-editor/,
type: 'asset/source'
}
]
}
};この設定により、Unpkg による CDN 読み込みや手動でのポインタ調整を避け、静的アセットとして効率的にビルドできます。
細かなカスタマイズと高度利用
エディタのマウント完了後に得られるエディタ API を活用することで、高度なインタラクションも実現可能です。
const editorDidMount = (editor, monaco) => {
// リジョンしたでの範囲強調表示
const model = editor.getModel();
monaco/editor.setModelMarkers(model, 'lint', [
{
severity: monaco.MarkerSeverity.Error,
startLineNumber: 3,
startColumn: 11,
endLineNumber: 3,
endColumn: 25,
message: 'Identifier ' + JSON.stringify('unusedVar') + ' is declared but never used.',
code: 'TS6133'
}
]);
// カスタムコマンド追加
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, () => {
console.log('Save proxy called');
// 実際の保存処理は別途実装
});
// カスタム言語定義例: ミニ言語のシンタックスハイライト
monaco.languages.register({ id: 'domainLang' });
monaco.languages.setMonarchTokensProvider('domainLang', {
tokenizer: {
root: [
[/[a-z_$][\w$]*/, { cases: { '@keywords': 'keyword', '@default': 'identifier' } }],
[/[0-9]+/, 'number'],
[/["'`]/, { token: 'string', bracket: '@open', next: '@string' }]
],
string: [
[/[^"'\`\\]+/, 'string'],
[/\\./, 'string.escape'],
[/["'`]/, { token: 'string', bracket: '@close', next: '@pop' }]
]
},
keywords: ['exec', 'eval', 'query']
});
};差分編集と同時編集処理
2つのテキスト比較には MonacoDiffEditor を利用します。元の内容と変更後の内容を引数で渡すだけで、前述の設定と同様のオプションを受け取ります。
import { DiffEditor } from 'react-monaco-editor';
const ComparisonViewer = () => {
const before = [
'function greet(name) {',
' console.log("Hello, " + name);',
'}'
].join('\n');
const after = [
'function greet(name) {',
' console.log(`Hello, ${name.toUpperCase()}`);',
'}'
].join('\n');
const options = {
renderSideBySide: true,
readOnly: true,
automaticLayout: true,
diffWordWrap: 'on'
};
return (
<div style={{ height: '400px' }}>
<DiffEditor
language="javascript"
original={before}
modified={after}
options={options}
/>
</div>
);
};このモジュールは、Git ブラウザベースの変更.diff 表示や CI 上での自動レビュー結果画面など、ソフトウェア開発支援ツールに有効です。
パフォーマンスとメモリ制御
- 初期描画時に全言語定義を読み込まないよう、初期化時に必要言語のみをロードする設定が推奨(例:
languages: ['typescript']) - 多くのエディタを一度に描画する際は、 эмулятор要素の
static表示の抑制注意(bias があるとレイアウト崩れの原因に) editorWillUnmount回呼で、非同期リスナやエディタされた履歴データの解放忘れに注意する- 編集対象が histórico 化された大きなファイルは、別途ストリーミング取得や lazy-load を検討
補完の高度な拡張
補完候補は registerCompletionItemProvider で独自実装可能です。特に、言語固有の API 名・構文補完や、ランタイムバインドされた DTO フィールド補完を実現できます。
monaco.languages.registerCompletionItemProvider('typescript', {
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: 'sampleSnippet',
kind: monaco.languages.CompletionItemKind.Snippet,
insertText: 'console.log(${1:message});',
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
range
}
]
};
}
});上記のように snippet 機能と連携すると、定型コードの生成に活用できます。VS Code の Code Action やQuickFix は独自プロバイダで再現も可能ですが、多くの場合 Monaco のビルトイン機能で十分です。