JSONL / NDJSON ファイルのスキャンに JSON プリプロセッサを適用
.jsonl および .ndjson ファイルのスキャンに JSON プリプロセッサ を適用するよう変更され、大容量ファイルのスキャン時間が数秒〜数十秒から数十ミリ秒単位に短縮されました。
背景
Tailwind CSS のスキャナはファイル内容からクラス名候補を抽出するとき、[ や { に遭遇するたびに「サブマシン」と呼ばれる内部状態機械を生成します。この仕組みは %w[…](Ruby)や className={clsx({flex: true})}(JSX)のような構文を正しく解析するために導入されたものです。
JSON 系ファイルはブラケットやブレースが非常に多いため、このサブマシン生成が大量に発生します。#17125 では .json ファイルに対してこの問題を解消する JSON プリプロセッサが導入されました。プリプロセッサはスキャン前にブラケット・ブレースをスペースに置換することで、エクストラクタが大量のサブマシンを生成しないようにします。
.jsonl(JSON Lines)や .ndjson(Newline Delimited JSON)はその名の通り JSON を1行ずつ並べたフォーマットであり、構造的には .json と同じく大量のブラケット・ブレースを含みます。しかし .json への対応時にはこれらの拡張子が対象外となっており、大容量ファイルをスキャンすると著しい遅延が発生していました。
技術的な変更
変更は crates/oxide/src/scanner/mod.rs 内の pre_process_input 関数、1行のみです。
変更前:
"json" => Json.process(&content),
変更後:
"json" | "jsonl" | "ndjson" => Json.process(&content),
pre_process_input は拡張子をキーにしてプリプロセッサを選択するパターンマッチです。"json" のパターンに "jsonl" と "ndjson" をOR条件で追加しただけで、Json.process の実装自体には一切手を加えていません。
PRの手動計測によると、M3 Max 環境で 5MB〜15MB の JSONL ファイルをスキャンした場合の所要時間は 2〜3秒 から 20ms 未満に、低スペックの Linux 環境では約 15MB のファイルで約 90秒 から約 300ms に短縮されたと報告されています。
設計判断
既存の JSON プリプロセッサを再利用する方式 が採用されました。JSONL / NDJSON は JSON のサブセットであり、ブラケット・ブレースの扱い方は通常の JSON と同一です。そのため、Json.process をそのまま流用することでコードの重複なく対応できています。
PR の説明では、JSONL / NDJSON にはクラス名がほぼ含まれない可能性が高いとして、バイナリ拡張子リストに追加してスキャン対象から除外するという代替案も検討されています。ただし完全な除外は誤検知を生む可能性があるため、より安全な「プリプロセッサによる高速化」が選択されました。
まとめ
この変更は1行の修正でありながら、実環境での大容量 JSONL ファイルに対するスキャン性能を桁違いに改善します。pre_process_input の拡張子マッチングという設計が、新たなフォーマットへの対応を最小コストで実現できる構造になっていることを示す好例です。