プロジェクト背景と要件分析
工具迷(gongjumi.com)の開発者として、常にユーザーにより良いオンラインツール体験を提供することを目指しています。最近、サイトのコード表示機能をアップグレードする際、ネイティブのtextarea
コンポーネントがコードの入力・出力処理時に明らかな UX 上の問題を抱えていることに気付きました。
工具迷では、YAML フォーマッター、SQL から Java Entity ジェネレーター、TypeScript ビューティファイアなど、多数のコード関連ツールを提供しています。これらのツールはすべて、ユーザーがコードを入力し、処理結果を表示する必要があります。しかし、通常の textarea には構文ハイライト機能がなく、複雑なコード内の問題箇所を素早く特定するのが難しく、全体的な体験を損なっています。
技術選定:なぜ highlight.js を選んだのか
数ある構文ハイライトライブラリの中で highlight.js を選択した主な理由は以下の通りです:
豊富な言語サポート
highlight.js は JavaScript、Python、Java といった主要言語から、マイナーな関数型言語まで、190 を超えるプログラミング言語をサポートしています。多様なコード形式を扱う工具迷にとって、この幅広い対応力は非常に重要です。
多彩なテーマライブラリ
GitHub スタイル、VS Code スタイル、Atom スタイルなど、豊富なテーマを提供しています。これにより、工具迷のデザインと調和するコード表示テーマを自由に選べます。
優れたエコシステム互換性
Vue.js エコシステムとの親和性が高く、統合も比較的シンプルです。さらに、自動言語検出機能を備えており、ユーザーが言語を指定しなくても適切なハイライトを適用できます。
パフォーマンス上の利点
Monaco Editor のような重量級エディタに比べ、highlight.js は軽量で読み込みが速く、工具迷のように素早いレスポンスを求められるオンラインツールに最適です。
実装詳細
最初の試み:二層オーバーレイ
まず試したのは、透過 textarea の背後にハイライト表示用の div を重ねる二層構造でした:
<div class="editor-container">
<div class="code-highlight" v-html="highlightedCode"></div>
<textarea v-model="code" class="code-textarea"></textarea>
</div>
この方法では、ユーザーは透明な textarea で入力しつつ背後でハイライト表示が行われますが、以下の問題がありました:
- 長いコードや改行の多い場合に、ハイライト層と入力層がズレる
- スクロール同期が困難で、一方をスクロールしてももう一方が追従しない
- フォント・行間・パディングなどスタイルの完全一致が難しい
CSS Grid レイアウトによる最適化
ズレの解消には CSS Grid を使い、同じグリッド領域に配置してみました:
.editor-container {
display: grid;
grid-template-areas: "editor";
}
.code-highlight, .code-textarea {
grid-area: editor;
}
これで配置は揃いましたが、スクロール同期やスタイル整合性の課題は残りました。
最終的な解決策:contenteditable な div
試行錯誤の末、contenteditable
属性を持つ div を使う方法がベストと判明しました:
<div
contenteditable="true"
class="editable-code hljs"
v-html="highlightedCode"
@input="handleInput"
/>
このアプローチの利点は:
- highlight.js のネイティブ CSS がそのまま使えるので追加スタイル不要
- 複雑な重ね構造がなく、ズレや同期問題を根本的に回避
- 編集と表示が統一され、UX が格段に向上
コアコード実装
コンポーネントインターフェース設計
工具迷の各ツールで再利用しやすいよう、以下のような Props を設計しました:
interface Props {
modelValue: string // 双方向バインドされるコード内容
language?: string // 言語指定(任意)
readonly?: boolean // 読み取り専用モード
placeholder?: string // プレースホルダテキスト
rows?: number // 表示行数
}
構文ハイライトロジック
ハイライトの核心は、highlight.js API の呼び出しと例外処理です:
const updateHighlight = () => {
try {
if (props.language && hljs.getLanguage(props.language)) {
// 指定言語でハイライト
const result = hljs.highlight(localValue.value, {
language: props.language
});
highlightedCode.value = result.value;
} else {
// 自動検出
const result = hljs.highlightAuto(localValue.value);
highlightedCode.value = result.value;
}
} catch (error) {
console.error('構文ハイライトエラー:', error);
// フォールバックでエスケープ済みコードを表示
highlightedCode.value = escapeHtml(localValue.value);
}
};
編集モードと読み取り専用モード
入力用と結果表示用の両シナリオに対応するため、以下のようにモードを分岐しています:
<template>
<!-- 読み取り専用モード -->
<pre v-if="readonly" class="readonly-code hljs" v-html="highlightedCode"></pre>
<!-- 編集モード -->
<div v-else contenteditable="true" class="editable-code hljs" @input="handleInput"></div>
</template>
主要な技術的詳細
テーマ統合
サイト全体のデザインに合わせて GitHub スタイルテーマを選択し、一部カラーを微調整しました:
// highlight.js テーマをインポート
import 'highlight.js/styles/github.css';
ユーザーインタラクションの改善
実装にあたり、以下の特殊な操作を処理しました:
- Tab キー処理:フォーカス移動ではなく、インデント用スペースを挿入
- 貼り付け処理:フォーマット情報を除去し、プレーンテキストのみを保持
- 入力のデバウンス:高速入力時の過度なハイライト更新を抑制
カーソル位置の保持
contenteditable と innerHTML 更新でカーソルがリセットされないよう、保存・復元ロジックを導入しました:
const handleInput = () => {
// 現在のカーソル位置を保存
const cursorPosition = saveCursorPosition();
// コンテンツとハイライトを更新
updateContent();
updateHighlight();
// DOM 更新後にカーソルを復元
nextTick(() => {
restoreCursorPosition(cursorPosition);
});
};
実際の適用効果
SQL から Java Entity ツールのアップグレード
工具迷で人気の「SQL から Java Entity ジェネレーター」は、従来は textarea に SQL を入力し、プレーンテキストで結果を表示していました。highlight.js 統合後は:
- 入力欄で SQL 構文を完全にハイライトし、キーワードやフィールド名、データ型を瞬時に識別可能に
- 出力される Java コードにもプロフェッショナルな構文ハイライトを適用
- UI が洗練され、ユーザーから高評価を獲得
JSON フォーマッターの改善
JSON フォーマッターは工具迷で最も使用頻度の高いツールの一つです。構文ハイライトにより、構造の階層が視覚的に把握しやすくなり、エラー箇所の特定がより迅速になりました。
TypeScript ビューティファイア
TypeScript ビューティファイアでは、単なる<pre>
+<code>
表示から、本格的なコードエディタコンポーネントへと大幅に進化し、IDE 類似の体験を提供できるようになりました。
パフォーマンス最適化とベストプラクティス
オンデマンド言語読み込み
highlight.js は 190 以上の言語をサポートしますが、工具迷では一部の主要言語のみを使用します。バンドルサイズ削減のため、必要な言語だけを読み込む戦略を採用しました:
// コアと必要な言語のみをインポート
import hljs from 'highlight.js/lib/core';
import javascript from 'highlight.js/lib/languages/javascript';
import python from 'highlight.js/lib/languages/python';
import java from 'highlight.js/lib/languages/java';
import sql from 'highlight.js/lib/languages/sql';
hljs.registerLanguage('javascript', javascript);
hljs.registerLanguage('python', python);
hljs.registerLanguage('java', java);
hljs.registerLanguage('sql', sql);
コンポーネントライフサイクル管理
パフォーマンスとメモリ管理のため、適切なライフサイクル処理を行います:
- マウント時に highlight.js を初期化
- 更新時に構文ハイライトを再実行
- アンマウント時にリソースを解放
レスポンシブデザインの考慮点
工具迷のユーザーはモバイル利用者も多いため、エディタのモバイル対応が必須です:
- 適切なフォントサイズと行間設定
- タッチ操作に配慮したインタラクション
- スクロールやズームの挙動を最適化
学んだ教訓
よくある問題と解決策
レイヤーのずれ
最初に直面した問題です。解決策は二層構造を諦め、contenteditable div を使うことです。どうしても二層構造を使う場合は、絶対配置ではなく CSS Grid を推奨します。
カーソルのジャンプ問題
頻繁な DOM 更新でカーソル位置が乱れる問題は、保存・復元ロジックの実装とデバウンス技術で解決しました。
スタイル競合
highlight.js のスタイルがサイト既存のスタイルと衝突する場合は、CSS スコープや CSS Modules を使ってスタイルを分離することをおすすめします。
デバッグのヒント
- ブラウザ開発者ツールの Elements パネルで DOM の変化を確認
- Console で highlight.js のエラーや警告をモニタリング
- 異なる言語やコード長で動作をテスト
- 複数ブラウザ・デバイスで表示を検証
今後の展開計画
機能強化
ユーザーフィードバックと利用データをもとに、さらに実用的な機能を追加予定です:
- 行番号表示:長いコードでも行を特定しやすくする
- コード折り畳み:関数やクラス単位で展開・折り畳み可能にする
- 検索&置換:コード内のテキストを素早く検索・置換
- フォーマット統合:Prettier などを使ったワンクリック整形機能
さらなるパフォーマンスチューニング
- 仮想スクロールを実装し、大規模ファイルに対応
- ハイライトアルゴリズムを最適化し、大ファイル処理の遅延を低減
- キャッシュ機構を追加し、構文解析の重複を防止
プロジェクトのまとめと振り返り
技術的成果
highlight.js を統合することで、現代的なフロントエンドコードエディタの内部構造を深く理解し、複雑なユーザーインタラクションの処理技術とコンポーネント駆動開発のノウハウを習得できました。
ユーザーへの価値
最も重要なのは、今回のアップグレードがユーザーに確かな価値をもたらしたことです。構文ハイライト機能により、エラー検出や複雑なコード理解の効率が飛躍的に向上し、ユーザー満足度が明確に上がりました。
アーキテクチャ的考察
このプロジェクトを通じて、コンポーネントベース設計の重要性を再認識しました。優れたコードエディタコンポーネントは、現状の要件を満たすだけでなく、将来の機能拡張や他プロジェクトでの再利用にも大きな価値を提供します。
オンラインツールサイトである工具迷において、ユーザー体験の細部にまでこだわることは不可欠です。コード構文ハイライトという一見小さな機能も、ユーザー理解の深さと製品品質への飽くなき追求を象徴しています。