対象リポジトリ
遭遇した事象
herbにはdebug modeがある。これをonにすることでerb出力が可視化されて便利。


とても便利なのだがどうやら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に手伝ってもらった。
ソースを読んで解決策を考えてみる
このdebug spanを出力するのは Herb::Engine::DebugVisitor クラスがやっている。
scriptタグをどうやって識別しているのかを見る。
- スタック
@element_stack = []を持つ - HTML要素ごとに処理される
visit_html_element_nodeメソッド内でスタックを積む - erb出力ごとに処理される
visit_erb_content_nodeメソッド内でin_excluded_context?でチェック in_excluded_context?で現在のスタックを見てscriptタグが含まれているかを確認する
ベースはvisitorパターンで辿ったHTML要素をスタックに積んで、それをコンテキストとして利用していると理解した。
さて、HTML要素はスタックに積むが javascript_tag do といったERBBlockに対してはスタック操作はなされていない。同様の操作を行えば識別可能になるのではないか。
PRを作る
土日に入ったタイミングでIssueを上げたためかレスポンスはまだ来ていなかった。解決策のPoCにしてもコードを見せるのが手っ取り早いよなってことでPRを作成した。もちろん全ての英作文はLLMとやっている。
ソースを読んで分かったこと
herbはhtml/erbのパーサーであるのだけど、その抽象構文木のノードに関わる実装はtemplates以下にあるテンプレートから動的に生成されていた。
- herb/templates/lib/herb/ast/nodes.rb.erb at a43c7e6f138cdaa759468f11427b98ecd5e131f6 · marcoroth/herb · GitHub
- herb/templates/lib/herb/visitor.rb.erb at a43c7e6f138cdaa759468f11427b98ecd5e131f6 · marcoroth/herb · GitHub
これ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は少し手直しされてマージされていた🙌