Lexicalでシンタックスハイライトする
LexicalはDraft.jsの後継となるテキストエディタフレームワークです。
元々Metaで開発されていたものがOSSとして公開されました。
Lexicalは主にリッチテキストを実装するためのフレームワークですが、
高度に抽象化されているためソースコードエディタを実装するのにも利用できます。
ただコードに関するモジュールである@lexical/codeはドキュメントが虚無なため、
本記事で使い方の紹介ができればと思います。
@lexical/code
Lexicalで既存言語のソースコードをシンタックスハイライトするために必要なものは
以下の3つで、全て@lexical/codeからエクスポートされています。
- CodeNode
- CodeHighlightNode
- registerCodeHighlighting
CodeNodeはコードブロックを表すElementNodeの子クラス、
CodeHighlightNodeはハイライト用のTextNodeの子クラス、
registerCodeHighlightingは上記のNode達やTextNodeの変換関数を登録するための関数です。
ここまでの説明でNodeやNodeの変換にピンと来ていない方にはこちらの記事が
とても参考になります。
Lexical自体は特にReact専用ではないですが、本記事のコードはReactで書いていきます。
Step.1 変換処理の登録
まずはregisterCodeHighlightingでの登録用コンポーネントです。
このコンポーネントはLexical中のサンプルにありますが、エクスポートされていないため、
サンプルを利用しない場合はコピペする必要があります。
※著作権はMetaに帰属するはずなので注意しましょう。
Step.2 NodeとPluginの利用
上記のCodeHighlightPluginをLexicalComposer内で利用するのと、
CodeNode, CodeHighlightNodeを利用するためにLexicalComposerの設定として渡します。
他は適当です。
import { LexicalComposer } from "@lexical/react/LexicalComposer";
import { PlainTextPlugin } from "@lexical/react/LexicalPlainTextPlugin";
import { ContentEditable } from "@lexical/react/LexicalContentEditable";
import { CodeHighlightNode, CodeNode } from "@lexical/code";
import CodeHighlightPlugin from "./CodeHighlightPlugin";
const CodeEditor = () => {
return (
<LexicalComposer
initialConfig={{
namespace: "Editor",
nodes: [CodeNode, CodeHighlightNode],
onError: console.error,
}}
>
<PlainTextPlugin
contentEditable={<ContentEditable />}
placeholder={null}
/>
<CodeHighlightPlugin />
</LexicalComposer>
);
};
export default CodeEditor;
ここまででCodeEditorをエラーなく利用できるようになっていますが、適当なコードを入力しても
ハイライトされません。
理由の1つは初期値を与えていないため、ParagraphNodeが挿入され、プレーンテキストとして
入力が処理されるからです。
もう1つはCodeHighlightNodeに対してのテーマ設定をしていないからです。
Step.3 初期値の登録
ParagraphNodeの代わりにCodeNodeを挿入すれば良いです。
import { $getRoot } from "lexical";
import { $createCodeNode } from "@lexical/code";
export const $getInitialState = () => {
const code = $createCodeNode();
$getRoot().append(code).selectEnd();
};
なお$createCodeNodeの引数では言語を設定できます(デフォルトはJavaScriptです)。
import { LexicalComposer } from "@lexical/react/LexicalComposer";
import { PlainTextPlugin } from "@lexical/react/LexicalPlainTextPlugin";
import { ContentEditable } from "@lexical/react/LexicalContentEditable";
import { CodeHighlightNode, CodeNode } from "@lexical/code";
import CodeHighlightPlugin from "./CodeHighlightPlugin";
+import { $getInitialState } from "./editorState";
const CodeEditor = () => {
return (
<LexicalComposer
initialConfig={{
namespace: "Editor",
nodes: [CodeNode, CodeHighlightNode],
onError: console.error,
}}
>
<PlainTextPlugin
contentEditable={<ContentEditable />}
placeholder={null}
+ initialEditorState={$getInitialState}
/>
<CodeHighlightPlugin />
</LexicalComposer>
);
};
export default CodeEditor;
Step.4 テーマ設定
CodeNode, CodeHightlightNodeへのクラス名の設定とCSSを書きます。
import { LexicalComposer } from "@lexical/react/LexicalComposer";
import { PlainTextPlugin } from "@lexical/react/LexicalPlainTextPlugin";
import { ContentEditable } from "@lexical/react/LexicalContentEditable";
import { CodeHighlightNode, CodeNode } from "@lexical/code";
import CodeHighlightPlugin from "./CodeHighlightPlugin";
import { $getInitialState } from "./editorState";
const CodeEditor = () => {
return (
<LexicalComposer
initialConfig={{
namespace: "Editor",
nodes: [CodeNode, CodeHighlightNode],
onError: console.error,
+ theme: {
+ code: "editor-code",
+ codeHighlight: {
+ function: "editor-tokenFunction",
+ keyword: "editor-tokenAttr",
+ number: "editor-tokenProperty",
+ string: "editor-tokenSelector",
+ },
+ },
}}
>
<PlainTextPlugin
contentEditable={<ContentEditable />}
placeholder={null}
initialEditorState={$getInitialState}
/>
<CodeHighlightPlugin />
</LexicalComposer>
);
};
export default CodeEditor;
色は適当です。
.editor-code {
font-family: Consolas, Menlo, monospace;
}
.editor-tokenAttr {
color: red;
}
.editor-tokenFunction {
color: violet;
}
.editor-tokenProperty {
color: blue;
}
.editor-tokenSelector {
color: green;
}
Themeに設定できるものは実際はもっと多いです。
これでシンタックスハイライトが実現できました。
最後に
執筆にあたってLexicalをいろいろ触ってみましたが、汎用的に作られているので
いろいろ使い道がありそうです。
今回は既存言語のハイライトを取り上げましたがNodeを自作すれば未対応の言語も
ハイライトすることができます。
ただLexicalは成熟しておらず、その旨がREADMEにも一番初めに書かれています。
今回利用したregisterCodeHighlightingも機能の分割が進められているようなので
本記事のコードはそのうち動かなくなるでしょう。
今後に期待!!
Discussion