はじめに
Webアプリでコード入力欄が必要になったとき、どうしていますか?
普通のテキストエリアだとシンタックスハイライトもないし、補完も効かない。かといってVS Codeを埋め込むのは大げさすぎる。そんなときに選択肢に上がるのがCodeMirrorなんですよね。
個人的には、Webベースのコードエディタといえば真っ先にCodeMirrorが浮かびます。GitHubで7,500スター以上、使用プロジェクト377件以上という実績があって、信頼性は抜群です。
とはいえ、説明を読むより実際に触った方が早いと思います。ブラウザ上でCodeMirror 6のエディタを埋め込んで、実際にコードを編集できるサンプルを用意したので、先に挙動を見たい方はこちらからどうぞ。
CodeMirrorとは
CodeMirrorは、Webページに埋め込めるコード編集コンポーネントです。MITライセンスのオープンソースで、2007年から開発が続いている老舗中の老舗。
現在のバージョン6は2020年に発表された完全リニューアル版で、モダンなアーキテクチャで書き直されています。
公式サイト: https://codemirror.net GitHub: https://github.com/codemirror/dev
特徴・メリット
1. 20以上の機能が最初から使える
これ、意外とすごいことなんですよ。
CodeMirrorには以下のような機能が標準で搭載されています:
- シンタックスハイライト - 言語ごとに色分け
- コード補完 - 言語固有の入力候補を表示
- コード折りたたみ - 関数やブロックを畳める
- 検索・置換 - 正規表現対応
- 複数選択 - 複数箇所を同時編集
- 協調編集 - 複数ユーザーで同時編集可能
- 括弧のマッチング - 対応する括弧をハイライト
正直なところ、これだけあれば大抵のユースケースはカバーできます。
2. 20言語以上のサポート
Python、JavaScript、TypeScript、HTML、CSS、Java、Rust、Go、PHP、Markdown、YAML...と、メジャーな言語はほぼ網羅しています。
言語パッケージは個別にインストールする形式なので、必要な言語だけ入れればバンドルサイズも抑えられます。コスパ的にありがたい設計ですね。
3. アクセシビリティ対応
スクリーンリーダーやキーボード操作にきちんと対応しています。30代になって思うのは、こういう地味だけど重要なところがしっかりしているライブラリは信頼できるということ。
4. 大規模ドキュメント対応
ドキュメントが大きくなっても快適に動作します。内部的に効率的なデータ構造を使っているので、数万行のファイルを開いても問題なし。
5. IE11以降対応
ポリフィルを適用すればIE11でも動きます。まあ、2026年の今となってはIE対応が必要なケースは減りましたが、レガシー環境をサポートする必要がある場合は助かりますね。
インストール方法
CodeMirror 6はモジュラー構成になっていて、必要なパッケージを個別にインストールします。
基本パッケージ
npm install codemirror
言語パッケージ(例:JavaScript)
npm install @codemirror/lang-javascript
よく使う言語パッケージ
# Python
npm install @codemirror/lang-python
# HTML
npm install @codemirror/lang-html
# CSS
npm install @codemirror/lang-css
# Markdown
npm install @codemirror/lang-markdown
# JSON
npm install @codemirror/lang-json
CodeMirrorのサンプルを動かす
まずは実際にCodeMirror 6のエディタを動かしてみましょう。EditorViewにbasicSetup(行番号やシンタックスハイライトなどをまとめて有効化する拡張の詰め合わせ)とjavascript()(JavaScriptのシンタックスハイライト・言語サポート)を渡すだけで、コード入力欄が出来上がります。
要点だけを抜き出すと、こういう書き方になります。
import { EditorView, basicSetup } from 'codemirror'
import { javascript } from '@codemirror/lang-javascript'
const view = new EditorView({
doc: "console.log('Hello, CodeMirror!')",
extensions: [basicSetup, javascript()],
parent: document.getElementById('editor'),
})
// 現在の内容を取得
const code = view.state.doc.toString()
下のサンプルは、これに文字数カウンターを組み合わせたものです。エディタ内のコードを書き換えると、EditorView.updateListenerが変更を検知して下の文字数表示がリアルタイムに更新されます。試しにコードを増やしたり減らしたりしてみてください。
このように、new EditorView({ doc, extensions, parent })の3つのオプションを渡すだけでシンタックスハイライト付きのエディタが動きます。extensionsの配列に渡す拡張を増やせば、リンターやテーマなど機能を足していけるのがCodeMirror 6の設計思想です。次の節では、このbasicSetupまわりの使い方をもう少し詳しく見ていきます。
基本的な使い方
シンプルなエディタの作成
import { EditorView, basicSetup } from "codemirror";
import { javascript } from "@codemirror/lang-javascript";
const editor = new EditorView({
doc: "console.log('Hello, CodeMirror!');",
extensions: [basicSetup, javascript()],
parent: document.getElementById("editor")!,
});
basicSetupを使うと、行番号、シンタックスハイライト、括弧マッチングなど、基本的な機能がまとめて有効になります。時短になりますね。
値の取得と設定
// 現在の値を取得
const content = editor.state.doc.toString();
// 値を設定(全置換)
editor.dispatch({
changes: {
from: 0,
to: editor.state.doc.length,
insert: "// 新しいコード",
},
});
テーマの適用
import { EditorView } from "codemirror";
import { oneDark } from "@codemirror/theme-one-dark";
const editor = new EditorView({
extensions: [basicSetup, oneDark, javascript()],
parent: document.getElementById("editor")!,
});
ダークテーマが好みなら@codemirror/theme-one-darkパッケージを入れるだけ。
読み取り専用モード
import { EditorState } from "@codemirror/state";
const editor = new EditorView({
extensions: [
basicSetup,
javascript(),
EditorState.readOnly.of(true), // 読み取り専用
],
parent: document.getElementById("editor")!,
});
コードの表示だけしたい場合に便利です。
実践的なユースケース
1. コードスニペット共有サービス
import { EditorView, basicSetup } from "codemirror";
import { javascript } from "@codemirror/lang-javascript";
import { python } from "@codemirror/lang-python";
// 言語切り替え対応エディタ
function createEditor(language: string, parent: HTMLElement) {
const langExtension = language === "python" ? python() : javascript();
return new EditorView({
extensions: [basicSetup, langExtension],
parent,
});
}
言語選択ドロップダウンと組み合わせれば、Gistみたいなサービスが作れます。
実際にドロップダウンで言語を切り替えられるCodeMirrorのサンプルを用意しました。ポイントはCompartmentという仕組みです。Compartmentはエディタの一部の拡張だけを後から差し替えるための入れ物で、view.dispatch({ effects: languageConf.reconfigure(...) })のように呼び出すと、他の設定を保ったまま言語サポートだけをjavascript()からpython()へ入れ替えられます。
import { Compartment } from '@codemirror/state'
import { javascript } from '@codemirror/lang-javascript'
import { python } from '@codemirror/lang-python'
const languageConf = new Compartment()
// 初期状態はJavaScript
const view = new EditorView({
extensions: [basicSetup, languageConf.of(javascript())],
parent: document.getElementById('editor'),
})
// 言語を差し替える(他の拡張はそのまま)
view.dispatch({
effects: languageConf.reconfigure(python()),
})
下のサンプルではドロップダウンで「JavaScript」「Python」を選ぶと、Compartment経由で言語サポートが切り替わり、同時にサンプルコードも入れ替わります。ハイライトのされ方が言語ごとに変わる様子を確認してみてください。
言語パッケージを増やしてsamplesと分岐を足せば、対応言語をどんどん広げられます。Gistのようなスニペット共有サービスを作るときは、この仕組みをそのまま流用できるはずです。
2. オンラインIDEのコアエディタ
import { EditorView, basicSetup } from "codemirror";
import { javascript } from "@codemirror/lang-javascript";
import { linter, lintGutter } from "@codemirror/lint";
// ESLintと連携する例
const eslintLinter = linter((view) => {
// ESLintの診断結果を返す
return []; // 実際はESLintの結果をマッピング
});
const editor = new EditorView({
extensions: [basicSetup, javascript(), lintGutter(), eslintLinter],
parent: document.getElementById("editor")!,
});
リンター連携を入れれば、本格的なIDEに近づきます。
3. Markdownエディタ
import { EditorView, basicSetup } from "codemirror";
import { markdown } from "@codemirror/lang-markdown";
import { languages } from "@codemirror/language-data";
const editor = new EditorView({
extensions: [
basicSetup,
markdown({ codeLanguages: languages }), // コードブロックの言語ハイライト
],
parent: document.getElementById("editor")!,
});
Markdownエディタを作るなら、コードブロック内の言語ハイライトも対応しておくとQOL上がります。
4. 設定ファイルエディタ
import { EditorView, basicSetup } from "codemirror";
import { json } from "@codemirror/lang-json";
import { linter } from "@codemirror/lint";
// JSON構文エラーをチェック
const jsonLinter = linter((view) => {
const diagnostics = [];
try {
JSON.parse(view.state.doc.toString());
} catch (e: any) {
diagnostics.push({
from: 0,
to: view.state.doc.length,
severity: "error",
message: e.message,
});
}
return diagnostics;
});
const editor = new EditorView({
extensions: [basicSetup, json(), jsonLinter],
parent: document.getElementById("editor")!,
});
管理画面で設定JSONを編集させるときに便利です。
5. ダークテーマの動的切り替え
ユーザー設定でエディタのテーマを明るい/暗いに切り替えたい、というのもよくある要望です。ここでもCompartmentが活躍します。テーマ用のCompartmentを1つ用意しておき、ボタンが押されるたびにthemeConf.reconfigure(oneDark)とthemeConf.reconfigure([])を切り替えるだけで、ドキュメントの中身やカーソル位置を保ったままテーマだけを差し替えられます。
import { Compartment } from '@codemirror/state'
import { oneDark } from '@codemirror/theme-one-dark'
const themeConf = new Compartment()
let dark = false
const view = new EditorView({
extensions: [basicSetup, javascript(), themeConf.of([])],
parent: document.getElementById('editor'),
})
// テーマだけを差し替える
dark = !dark
view.dispatch({
effects: themeConf.reconfigure(dark ? oneDark : []),
})
下のサンプルでは、ボタンを押すと@codemirror/theme-one-darkのoneDarkテーマがオン・オフされます。テーマの適用中も入力したコードはそのまま残るので、実際の管理画面でユーザーがダークモードを切り替える体験に近い形で試せます。
oneDark以外にも、コミュニティ製のテーマパッケージが数多く公開されています。自作のテーマを作りたい場合も、EditorView.theme()で同じようにCompartmentに差し込めます。
Monaco Editorとの比較
よく比較されるのがVS Codeのエディタ部分であるMonaco Editor。個人的な使い分けはこんな感じです:
| 観点 | CodeMirror | Monaco Editor |
|---|---|---|
| バンドルサイズ | 軽量 | 重め |
| カスタマイズ性 | 高い | 中程度 |
| 学習コスト | 低め | 高め |
| 機能の充実度 | 必要十分 | VS Code並み |
| モバイル対応 | 良好 | やや難あり |
シンプルなコード入力欄ならCodeMirror一択ですね。フルIDEを作りたいならMonacoも検討する価値あり。
まとめ
CodeMirrorは、Webでコードエディタが必要なときの定番ライブラリです。
個人的に推せるポイントは:
- 軽量でモジュラー - 必要な機能だけインストール
- 20言語以上対応 - メジャー言語はほぼカバー
- 豊富な機能 - 補完、折りたたみ、検索などが揃っている
- カスタマイズ性 - テーマ、キーバインド、拡張が自由自在
- 長年の実績 - 2007年から続く信頼性
管理画面のコード編集欄、ドキュメントサイトのライブコード、オンラインIDEなど、使いどころは多いです。
まずはbasicSetupでサクッと試してみてください。
公式サイト: https://codemirror.net GitHub: https://github.com/codemirror/dev ドキュメント: https://codemirror.net/docs/ref