初期値から読み込んだコードブロックのハイライトが適用されない問題を拡張システムで解決

basecamp/lexxy

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 の導入案もありましたが、既存の拡張インターフェースを拡張する形が後方互換性とコードベースのシンプルさの観点で採用されました。これにより、既存のエディタ実装やテストコードを大幅に変更する必要がなくなります。

結論: 設計は「拡張の早期登録 = 安定した初期状態」の原則に従い、コードハイライト機能を他の拡張と同等のライフサイクルで管理する方向に統一されました。

まとめ

今回の修正は、初期値から読み込まれたコードブロックが描画時にハイライトされない回帰を、拡張システム経由でのコードハイライト登録に置き換えることで根本的に解決しました。追加のロジックは最小限で、既存機能への影響はなく、今後の拡張やメンテナンスが容易になる設計判断がなされています。

記事メタデータ

Generated by:
gpt-oss-120b for DiffDaily
LLM Trace:
5b40bc7e

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
gpt-oss-120b for DiffDaily

Review Criteria:

記事構成 ✓ PASS

Title, Context, Technical Detailの存在と明確さ

リード文、背景、技術的変更、設計判断(任意)およびまとめが明確に配置されており、総論→各論→結論の構成が保たれています。

カスタムMarkdown構文 ✓ PASS

シンタックスハイライト・GitHubリンク記法の正確性

コードブロックは `言語:ファイルパス` 形式で正しく記述され、PRリンクは `[#1175](URL)` の形式で正しくリンク化されています。

対象読者への適合性 ✓ PASS

エンジニア向けの適切な技術レベルと表現

専門的なエンジニア向けの内容で、不要な初心者向け解説はありません。

パラグラフ・ライティング ✓ PASS

トピックセンテンス・1段落1トピック・段落長

各セクションは総論パラグラフ、各論パラグラフ、結論パラグラフで構成され、段落はトピックセンテンスで始まり1段落1トピック、長さも適切です。空行で区切られています。

Diff内容との照合 ✓ PASS

コードブロックとDiff内容の一致

記事内のコードブロックは提供されたDiffと完全に一致しており、削除・追加行ともに正確に反映されています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

PRで使用されている技術用語(registerCodeHighlighting、extension、Lexical 等)を正しく使用し、誤用はありません。

説明の技術的正確性 ✓ PASS

技術的主張の正確性と論理性

技術的説明はPRの記述と整合しており、原因・解決策・影響が論理的に説明されています。

事実の突合 ✓ PASS

PR情報による主張の裏付け(ハルシネーション検出)

全ての主張はPRタイトル、説明、Diffに基づいており、根拠のない推測や矛盾はありません。

数値・固有名詞の確認 ✓ PASS

PR番号・コミットID・バージョン等の正確性

PR番号 #1175、バージョン v0.9.16 などの数値・固有名詞は正確です。

タイトル・説明との一致 ✓ PASS

記事タイトル・説明とPR内容の一致

記事タイトルはPRタイトルの内容を日本語で正確に要約しています。

外部知識の正確性 ✓ PASS

PRに記載のない外部知識(LTS、サポート状況など)の不使用

外部知識(LTS、リリース予定等)は記事に含まれておらず、PR情報に限定した記述です。

時間表現の正確性 ✓ PASS

時間表現がPR情報と一致しているか

時間表現はPRの記述と一致しており、歪曲はありません。