初期値から読み込んだコードブロックのハイライトが適用されない問題を拡張システムで解決
Lexical エディタで value プロパティから生成されたコードブロックが、最初の描画時にシンタックスハイライトされない回帰が修正されました。拡張システムを通して コードハイライト の登録タイミングを前倒しし、初期状態でも正しくトークン化できるようになっています。
背景
このセクションの主張: 初期値解析の順序が変わったことが、コードブロックのハイライト欠如を引き起こしていました。
v0.9.16 で導入された #initialize() から $initialEditorState への移行により、エディタ生成時に初期コンテンツが afterRegistration フェーズで適用されます。そのタイミングでは registerCodeHighlighting() がまだ呼ばれておらず、コードノードの変換 (transform) が実行されません。Lexical のクリーンアップパスは コミット済み のエディタ状態を参照するため、初期値はまだ未コミットの空ドキュメントとみなされ、コードノードは dirty と判定されません。結果として、ユーザーが言語を選択するなど編集操作を行うまでハイライトが適用されませんでした。
結論: 登録順序の変化がノード変換の実行タイミングに直接影響し、初期ロード時のハイライトが失われたことが原因です。
技術的な変更
このセクションの主張: コードハイライト登録を拡張システムへ移行し、エディタ生成前にトランスフォームを確保しました。
src/elements/editor.js から registerCodeHighlighting の直接呼び出しを削除し、代わりに新しい拡張 CodeHighlightingExtension をインポートして baseExtensions に追加しました。
-import { CodeHighlightNode, CodeNode, registerCodeHighlighting } from "@lexical/code"
+import { CodeHighlightNode, CodeNode } from "@lexical/code"
@@
-import { ProvisionalParagraphExtension } from "../extensions/provisional_paragraph_extension"
+import { ProvisionalParagraphExtension } from "../extensions/provisional_paragraph_extension"
+import { CodeHighlightingExtension } from "../extensions/code_highlighting_extension"
@@
- ProvisionalParagraphExtension,
+ ProvisionalParagraphExtension,
+ CodeHighlightingExtension,
@@
- this.#registerCodeHiglightingComponents()
+ this.#registerCodeLanguagePicker()
@@
- #registerCodeHiglightingComponents() {
- registerCodeHighlighting(this.editor)
+ #registerCodeLanguagePicker() {
let codeLanguagePicker = this.querySelector("lexxy-code-language-picker")
codeLanguagePicker ??= createElement("lexxy-code-language-picker")
this.append(codeLanguagePicker)
新規ファイル src/extensions/code_highlighting_extension.js では、Lexical の defineExtension を利用して registerCodeHighlighting を拡張として提供します。enabled プロパティはリッチテキストが有効なエディタでのみ拡張を有効化します。
import { defineExtension } from "lexical"
import { registerCodeHighlighting } from "@lexical/code"
import LexxyExtension from "./lexxy_extension"
export class CodeHighlightingExtension extends LexxyExtension {
get enabled() {
return this.editorElement.supportsRichText
}
get lexicalExtension() {
return defineExtension({
name: "lexxy/code-highlighting",
register(editor) {
return registerCodeHighlighting(editor)
}
})
}
}
テスト用フィクスチャ initial-value-code-block.html と Playwright テスト code_block_initial_value.test.js が追加され、初期ロード時に CSS コードブロックがトークン化されることを自動検証します。
結論: 拡張システム経由での登録に置き換えるだけで、既存のコードベースへの影響は最小限に抑えられ、初期値のハイライトが即座に機能します。
設計判断
このセクションの主張: 拡張システムを利用することで、登録タイミングの保証とリソース管理が統一されました。
registerCodeHighlighting() をエディタ生成後に手動呼び出す方式は、他の拡張やプラグインが同様のフックを利用する際に順序依存のバグを誘発しやすい構造です。拡張として提供すれば、Lexical の register フックが初期ステート適用前に必ず実行され、順序問題が根本的に解消されます。また、拡張システムが teardown 関数の所有権を持つようになるため、後処理の漏れも防げます。
議論の中では新しい設定キー codeHighlighting の導入案もありましたが、既存の拡張インターフェースを拡張する形が後方互換性とコードベースのシンプルさの観点で採用されました。これにより、既存のエディタ実装やテストコードを大幅に変更する必要がなくなります。
結論: 設計は「拡張の早期登録 = 安定した初期状態」の原則に従い、コードハイライト機能を他の拡張と同等のライフサイクルで管理する方向に統一されました。
まとめ
今回の修正は、初期値から読み込まれたコードブロックが描画時にハイライトされない回帰を、拡張システム経由でのコードハイライト登録に置き換えることで根本的に解決しました。追加のロジックは最小限で、既存機能への影響はなく、今後の拡張やメンテナンスが容易になる設計判断がなされています。