OSS活動記 #1 - herb - javascript_tag内のerb出力にdebug spanが付与されるのをなんとかしたい

対象リポジトリ

github.com

遭遇した事象

herbにはdebug modeがある。これをonにすることでerb出力が可視化されて便利。

debug modeがonになっている様子

とても便利なのだがどうやらjavascript_tagヘルパー内のerb出力にも作用して余計なspanタグを付与してしまうみたい。

<%= javascript_tag do %>
  alert('<%= 1 %>');
<% end %>

実装を確認したところ、通常のscriptタグのケアはなされていた。以下は問題ない。

<script>
  alert('<%= 1 %>');
</script>

scriptタグを使うべきか?でいうと、例えばCSPを有効にしてscriptタグにnonceを付与したい場合はjavascript_tagが使えないと問題である。

ActionView::Helpers::JavaScriptHelper

scriptタグがケアされているのだからjavascript_tagヘルパーも同様にケアされるべきだろうと判断した。

Issueを上げる

というわけでIssueはこんな感じ。基本的に英作文はLLMに手伝ってもらった。

github.com

ソースを読んで解決策を考えてみる

このdebug spanを出力するのは Herb::Engine::DebugVisitor クラスがやっている。

herb/lib/herb/engine/debug_visitor.rb at a43c7e6f138cdaa759468f11427b98ecd5e131f6 · marcoroth/herb · GitHub

scriptタグをどうやって識別しているのかを見る。

  1. スタック @element_stack = [] を持つ
  2. HTML要素ごとに処理される visit_html_element_node メソッド内でスタックを積む
  3. erb出力ごとに処理される visit_erb_content_node メソッド内で in_excluded_context? でチェック
  4. in_excluded_context? で現在のスタックを見てscriptタグが含まれているかを確認する

ベースはvisitorパターンで辿ったHTML要素をスタックに積んで、それをコンテキストとして利用していると理解した。

さて、HTML要素はスタックに積むが javascript_tag do といったERBBlockに対してはスタック操作はなされていない。同様の操作を行えば識別可能になるのではないか。

PRを作る

土日に入ったタイミングでIssueを上げたためかレスポンスはまだ来ていなかった。解決策のPoCにしてもコードを見せるのが手っ取り早いよなってことでPRを作成した。もちろん全ての英作文はLLMとやっている。

github.com

ソースを読んで分かったこと

herbはhtml/erbのパーサーであるのだけど、その抽象構文木のノードに関わる実装はtemplates以下にあるテンプレートから動的に生成されていた。

これRubyパーサーのPrismで見たアプローチだなってなった。

あとherb ASTからerb出力するためのRubyコード生成は Herb::Engine , Herb::Engine::Compiler あたりが主にその責務を持つ。

visitorを複数注入できるのでdebug modeであれば Herb::Engine::DebugVisitor が注入されるし、任意のvisitorを実行時に追加可能な設計にもなっていた。

おわりに

まだPRを作っただけなので動きがあったらまたブログ記事に書き残すか追記をしようかと思っています。個人的に障壁が高かったOSS活動に伴う英作文や、あとソースコードリーディングに関してもLLMの力を借りることで難なくできるようになっていい時代になったものだなぁという感想でした。

追記(2025-12-07 22:36)

PRは少し手直しされてマージされていた🙌