OSS活動記 #2 - herb - head以下に出力するcontent_forへのdebug spanをなんとかする

対象リポジトリ

github.com

Issue

github.com

どういった問題が発生しているのか

個別のテンプレートとして models/index.html.erb がある。

<% content_for :title do %>
  <%= :title %>
<% end %>

レイアウトのテンプレート layouts/application.html.erb は以下の通り。

<!DOCTYPE html>
<html>
  <head>
    <title><%= content_for(:title) %></title>
  </head>
</html>

個別のテンプレートごとに任意の <title> が設定できるが、この <title> に debug span が展開されてしまう。debug span とは reactionview の debug mode において erb 出力を追跡するための情報を可視化するための <span> であって、herb ライブラリ内の処理で動的に追加されるタグ。

そもそも <title> には <span> はおろかテキストコンテントしか子要素として受け付けない。

https://html.spec.whatwg.org/multipage/semantics.html#the-title-element

よくよく調べると <head> 以下に <span> が存在することは仕様上ないので <head> 以下に debug span が展開されることは HTML の構造を破壊しうるので好ましくない。実際 herb ライブラリ内の処理では特定タグ以下では debug span は出力しないようにされている。

https://github.com/marcoroth/herb/blob/999337503477819a9018a149c49f6219ce2fbfed/lib/herb/engine/debug_visitor.rb#L301

ところが content_for で出力する場合、現状の処理では個別のテンプレートとレイアウトのテンプレートは別々に解析される。そのため <head> 以下に出力されうる erb 出力なのかを識別することが困難になっている。

解決策を提案してみる

問題の解像度が上がったところで、現時点でどう解決すべきかが一意ではなさそうなので質問がてら解決策をいくつか提案してみた。

https://github.com/marcoroth/herb/issues/605#issuecomment-3649595653

自分が提案した案は3つ。

  1. content_for 以下では debug span は出力しない
  2. debug span の出力を opt out できるようにする
  3. (1) + debug span の出力を opt in できるようにする

ここでいう ( opt out | opt in ) は、例えば <%# herb:disable-debug-span-in-content_for-block %> みたいな erb コメントを添えることで debug span の出力をコントロール可能にするという感じ。

それに対する作者の回答が来て、

  • 確かに現時点だと理想的な解決は難しいね
  • erb コメントを添えるのはフォーマッター的に難しい問題もあるので避けたい
  • やるなら (2) の案かもしれない

という感じに受け取った(意訳、正確には Issue のやりとりを読んで)

opt out する syntax も考える必要がある。いくつか作者からも提案してもらった(ジャストアイデアっぽいが)。その中で一番筋が良さそうなのが以下。

<% content_for :title do %>
  <%= :title # herb:debug disable %>
<% end %>

erb 出力単位で opt out できるのは良さそう。

erb 出力タグ内コメントの実用可能性を探る

そもそも erb としてそこにコメント置けるんだっけ?と思ったので試してみたところ Rails 上では 500 エラーになる。Rails デフォルトの ERB エンジンでも同様にエラー。

erb の出力というのは erb テンプレート → テンプレートの内容を文字列として出力する ruby コード → 文字列 という順番に処理される。ruby コードを確認してみる。

以下の erb テンプレートがあるとする。

<%= :title # herb:debug disable %>

この部分の ruby コードを確認するとこうなっていた。

@output_buffer.append=(:title # herb:debug disable);

閉じタグがコメントアウトされて ruby コードが壊れている。それはそうとして erb の実装が素朴すぎてスゴい。

いい感じに erb 出力とコメントを同居できないか探ってみる。

以下はいけた。

<%=
  # herb:debug disable
  :title
%>

以下だと Rails デフォルトの ERB エンジンは大丈夫だけど herb で ruby コードが壊れる結果となった。

<%=
  :title
  # herb:debug disable
%>

書き方をミスるとパースエラーではなく生成される ruby コードで syntax エラーになるのは微妙すぎる(ユーザーフレンドリーなエラーではない)

というわけで erb 出力タグ内コメントは「無し」っぽいなと思ったので作者にコメントを返した。

https://github.com/marcoroth/herb/issues/605#issuecomment-3650154958

唯一の妥協できそうな案をPRとして作成する

コメントを書く中で、これまでの会話を踏まえると妥協できそうな案がこれぐらいじゃないか?と思った。

<% content_for :title do # herb:debug disable %>
  <%= :title %>
<% end %>

これであれば erb ブロック単位で debug span を制御できる。元々の Issue もこれがあればワークアラウンドとして機能しそう。

というわけで PR を作ってみた。

github.com

作者がこの方針を気に入るかどうかは分からない。気に入らなかったら Reject でいいでしょう。

herb 本体側のパース精度が改善されればより適切な解析ができるだろうし、それまで塩漬けでいいかもしれないって思った。

この取り組みを通じて感じた課題

現時点の herb の課題という観点で以下を思った。

  • herb 本体側のパース精度はまだまだっぽい、この部分の改善が必要そう
  • HTML 仕様に準拠した HTML 構造のチェックがまだ甘そう

ここら辺をどう改善できるかというのは引き続き追いかけたい。