`<code>`要素を廃止し、`<pre>`のinnerHTMLを直接書き換えるシンタックスハイライト実装
コードブロックのシンタックスハイライト処理において、中間要素として挿入していた<code>要素を廃止し、<pre>要素のinnerHTMLを直接更新する実装に変更されました。これによりDOM操作が簡略化され、不要な要素ラッパーが除去されます。
背景
従来の実装では、<pre>要素を保持しつつも、ハイライト済みHTMLを<code>要素にラップして<pre>の子要素として挿入していました。createElementヘルパーで<code>要素を生成し、replaceChildrenで<pre>の内容を置き換えるという2段階の操作が必要でした。しかし、<pre>要素自体が既にDOM上に存在して保持され続けるのであれば、中間の<code>要素は不要な間接層にすぎないと判断されたのがこの変更の背景です。
PR本文でも「<pre>を保持しているのだから、中間の<code>要素を挟まずinnerHTMLを直接置き換えればよい」と明示されており、シンプルさを優先した設計判断が読み取れます。
技術的な変更
src/helpers/code_highlighting_helper.jsのhighlightElement関数が簡略化され、createElementヘルパーの呼び出しとreplaceChildrenによる子要素置換がinnerHTMLの直接代入に置き換わりました。
変更前:
import { createElement } from "./html_helper"
import Prism from "../config/prism"
// ...
const highlightedHtml = Prism.highlight(code, grammar, language)
const codeElement = createElement("code", { "data-language": language, innerHTML: highlightedHtml })
if (highlights.length > 0) {
applyHighlightRanges(codeElement, highlights)
}
preElement.replaceChildren(codeElement)
変更後:
import Prism from "../config/prism"
// ...
const highlightedHtml = Prism.highlight(code, grammar, language)
preElement.innerHTML = highlightedHtml
if (highlights.length > 0) {
applyHighlightRanges(preElement, highlights)
}
変更に伴い、data-language属性の付与先も<code>から<pre>に移りました。テストコードも同様に更新されており、code_highlighting_helper.test.jsでは<code>要素の存在確認を削除してpre.textContentの検証に一本化、code_highlighting_test.rbのシステムテストではassert_selectorのセレクタがcode[data-language='ruby']からpre[data-language='ruby']へ、code span.token.keywordからpre span.token.keywordへそれぞれ変更されています。
createElementヘルパーのインポートが不要になったため、import文も1行削除されており、依存関係が整理されています。
設計判断
innerHTMLの直接代入を採用することで、DOM操作のステップ数が削減されました。従来は「要素生成→属性付与→子要素置換」という3ステップでしたが、変更後は「innerHTML代入」の1ステップです。このPRでは、中間的な<code>要素を省略し、実装のシンプルさを優先する判断がなされました。
applyHighlightRangesの適用対象もpreElementに統一されたことで、ハイライト範囲処理の対象要素がhighlightElement関数の引数と同一になり、コードの一貫性が向上しています。
まとめ
本変更は、<pre>要素が既に保持されているという前提を活かし、不要な中間要素と間接的なDOM操作を取り除いたリファクタリングです。data-language属性やハイライト範囲処理の適用先が<pre>に統一されたことで、ハイライト処理のモデルがシンプルかつ一貫したものになりました。