CodeMirrorは、バージョン5以前とバージョン6とで、根本的な設計が変わっている。具体的には、アタタタッとグローバル変数でなんとかする設計から、ヒャォゥッと細かいモジュールに分割する設計に変わっている。どこが具体的だ。しかもアタタタッの方も最終的には肉片というモジュールに分解されるので、混乱のもとだ。
さておき、いったんCodeMirror6.x系の基本的な知識や使い方をまとめておこうと思う。
CodeMirror6と5以前との比較
これまでのバージョンと現行バージョンでは、おおよそ以下のような違いがある。
項目 | CodeMirror6以降 | CodeMirror5以前 |
---|---|---|
モジュールシステム | ES Modules / CommonJS | 非対応 |
パッケージ | @codemirror スコープ下で細分化 |
codemirror でほぼ完結 |
設定 | 拡張(Extension )で指定 |
オプションオブジェクトで指定 |
状態管理 | イミュータブルなオブジェクト(EditorState )で更新 |
エディタインスタンスを直接変更 |
ビュー | ビューインスタンス(EditorView )で制御 |
エディタインスタンスで制御 |
テキストエリアとの連携 | 専用メソッドでテキストエリアと同期(エディタを作るだけならテキストエリアは不要) | 既存のテキストエリアをもとにエディタ作成 |
ざっとまとめただけでも、諸機能がモジュール化されていることがわかる。正直、5の方がとっつきやすい。6は追いかけるモジュールが多く、大枠を理解するまでがなかなか大変である。というか絶賛大変中で、まだ大枠を理解できていない。
改変点が多いことに配慮して、公式リファレンスにはCodeMirror6の設計概要がまとめられたページが用意されている。私はなぜかこのページをすっ飛ばし、適当にドキュメントをつまみ読みして迷宮に迷い込んでしまった。はじめにこれを読んでいたらだいぶ違ったであろう。
CodeMirror6の基本知識
CodeMirror6は、@codemirror
下のパッケージ群で構成される。これらを個別に管理するの手間であるため、最低限必要な依存関係が無印のcodemirror
にまとめられている。これをインストールすれば、手始めに必要なモジュールの大部分が芋づる式に、もとい、イモジュール式にインストールされる。
どシンプルなエディタだけであれば、codemirror
パッケージだけで足りる。ただ、任意の言語をサポートする場合は、その言語のパーサやらなんやらが必要である。そういった言語の各種情報は、@codemirror/lang-xxx
というパッケージで提供される(xxxには言語名が入る)。手始めに、HTMLの言語サポートパッケージを使ってみる。
CodeMirror6では、エディタはEditorView
オブジェクトとして表現される。このオブジェクトは、エディタの初期状態(あるいは初期状態の生成に必要な情報)を受け取って生成される。初期状態が渡されなかったときは、内部で自動的に初期状態がセットされる。
ミニマルなエディタのサンプルコード
以下は、ドキュメントの内容と、codemirror
パッケージの中に用意される基本的な拡張のセット、HTMLの言語サポート機能(こちらも拡張)を渡してエディタを作成するサンプルコードである。
現在(2023年7月)、CodeMirror6をホスティングしているCDNサービスは見当たらない(後述)。そもそも、6はバンドルされることを前提にモジュール化されているようである。
上記のサンプルコードでは、デモを表示するための苦肉の策として、必要なモジュールをバンドルしてインポートしている(このブログでは、サンプルコードをiframe
にねじ込んでデモを表示している)。本来は、コメントアウトしているimport文を使う。
Version 5では、CSSやJavaScirpt混じりのコードをハイライトするために、XML、JavaScript、CSSの各モードが必要だった。CodeMirror6ではlang-html
パッケージだけで、style
タグ下のCSSとscript
タグ下のJavaScriptがハイライトされる。これは、HTMLの言語パッケージの内部で、CSSとJavaScript(とTypeScriptとJSXとTSX)の言語パッケージを読むことで実現されている。
バンドルせずに使う方法
明敏な有志によって、バンドルせずにCodeMirror6を使う方法も形にされている。
エディタを表現するEditorView
CodeMirror6では、エディタはEditorView
オブジェクトとして表現される。EditorView
コンストラクタにエディタの設定に必要な情報を渡せば、インスタンス化と同時にエディタがレンダリングされる。
エディタの設定に必要な情報は、以下のようなEditorViewConfig
インタフェースに準拠したオブジェクトとして表現される。
引用元:EditorViewConfigインタフェースの定義
先のサンプルコードでEditorView
コンストラクタに渡しているオブジェクトには、EditorViewConfig
インタフェースの定義にないdoc
プロパティとextensions
プロパティが含まれている。
これらは、EditroViewConfigインタフェースが継承するEditorStateConfig
インタフェースで定義されている。
引用元:EditorStateConfigインタフェースの定義
これらのオプションをすべて省略しても、EditorView
オブジェクトは作られる。ただその場合、機能的にはテキストエリアと変わらないエディタが、宙ぶらりんで変数に収まることになる。羽をもがれた蝶、あるいは穴のないうまい棒のように、CodeMirrorエディタのアイデンティティが失われてしまう。かわいそうだ。最低限Extensions
プロパティは指定しておきたいところである。
機能を担うExtensions
例えば以下のように、エディタにはさまざまな機能が期待される。
- シンタクスハイライト
- ブロックの折りたたみ
- キーボードショットカット
- テキストの選択・置換
- オートコンプリート…etc
CodeMirror6では、これらは拡張(Extension)という形で個々に提供される。
拡張は、エディタの状態を管理したり、状態の変更が必要になったときに特定の動作を行う機能を提供する。CodeMirror6では、エディタを作る際に任意の拡張を設定オブジェクトのextensions
プロパティに渡し、エディタの諸機能を実現する。ネストされた拡張は、処理する段階でフラットに直される。
先のサンプルコードで指定したbasicSetup
も、複数の拡張をまとめた配列変数である。名前の通り、これにはエディタが必要とするであろう拡張が一通り詰まっている。 ちなみにbasicSetup
に含まれている拡張は、以下の通りである。
bascSetupに含まれる拡張の一覧
引用元:basicSetupの定義
何気ないエディタに、こんなにたくさんの機能が含まれていたのかと驚かされる。ちなみにファセット(=Facet)は「側面」や「面」を意味する英単語で、CodeMirror6ではファセットは拡張が共有する変数を管理するための機能を指す(まだ理解が追いついていないため、間違っているかもしれないが)。
また、拡張が同じ状態に対して動作を行う場合、拡張がextension
プロパティに渡された順番によって優先度が決まる。つまり、先に渡された拡張の動作が先に適用される。
CodeMirrorの設定に使える拡張は、公式サイトのList of Core Extensionsに一覧がある。また、リファレンスにも、拡張を返す関数がたくさん見られる。これらを組み合わせて、自前で作成することもできる。
言語情報を担うLanguageSupport
折りたたみやハイライト、オートコンプリートといった一部の拡張は、単体だと機能が発揮されない。エディタに指定された言語の諸情報(パーサーなど)を内部で参照して、各言語に応じた処理を行うからである。この言語の諸情報は言語サポートパッケージと呼ばれ、やはり拡張として提供される。最初にインストールした@codemirror/lang-html
がそれに当たる。
以下は、CodeMirror6で用意されている言語パッケージの一覧である。
あとは、コミュニティによりいくつかの言語サポートパッケージが提供されている。いずれにせよCodeMirror5よりだいぶ少ない。欲しいものがなければ、自分で作るしかない。私はPugを扱いたいので、そのうち挑戦したいと思う。
まとめ
CodeMirror6では、エディタをEditorView
オブジェクトで表現し、その機能はExtension
と呼ばれる種々雑多なオブジェクトにより個別に追加される。
折りたたみやシンタクスハイライト、オートコンプリートといった、言語に依存する機能は、各言語に共通する部分と言語に依存する部分とに分離され、それぞれ別個の拡張にまとめられている。両方追加しなければ本領が発揮されない。なお、言語に依存する部分をまとめた拡張は言語サポートと呼ばれる。
CodeMirrorエディタの細かい操作は、おいおい学んでいきたいと思う。