先日の Kaigi on Rails 2025 で ReActionView のトーク を聞いて、ReActionView を使ってみたくなり、さっそく実際のプロジェクトに投入してみました。
この記事ではその前段となる Herb の導入について紹介します。ReActionView については後編で紹介するのでお楽しみに。
今回取り上げる Herb は今年の RubyKaigi 2025 で 発表されたもの だそうです。残念ながらこのトークは聞けていなかったので、Kaigi on Rails 2025 で出会えたのはラッキーでした。
Herb は HTML + ERB 向けのライブラリおよびツール群です。HTML + ERB をもじって Herb と名付けられているようです。クールなネーミングですね 🙂
Linter やフォーマッタ、LSP サーバなどの複数のツールから構成されており、HTML + ERB の品質と開発体験を向上させてくれます。
ちなみに、次回紹介する予定の ReActionView も Herb の上に構成されており、Herb ファミリーの仲間です。
HTML 向けの Linter やフォーマッタは数多く存在しますが、ERB をサポートしているものはあまり多くありません。ERB のタグを理解しつつ、過不足を指摘したり、フォーマットを整えてくれるツールは貴重です。
たとえば次のような ERB があったときに、
<% items.each do |item| %>
<li><%= item.name %></li>
<% end %>次のようにフォーマットしてくれると気持ちいいと思いませんか?
<% items.each do |item| %>
<li><%= item.name %></li>
<% end %>Herb は HTML タグと ERB タグの両方を理解しているため、このような修正を実現できます。
もうひとつの特徴は高速であることです。Linter やフォーマッタが遅いと開発のリズムが崩れてしまい、ストレスになりますよね。Herb はコア部分が C言語で実装されているおかげか、とても高速に動作します。
また、VSCode などに組み込みやすいことを考慮してか、JavaScript 向けのポーティングも存在しており、高速な体験を IDE にも持ち込むことができます。Format on Save を有効にして、保存のたびにフォーマットを掛けてもまったくストレスを感じません。高速に動作するというのは、モダンなツールキットではとても重要な要素ですね。
一方、採用する際にすこし悩んだところには、Herb はとても若いプロジェクトだというものがあります。 2025年 4月に最初のリリースが行われて、まだ半年しか経っていません。まだバージョンも 0.8.2 で、バージョン 1.0 に達しておらず、まだ枯れている、安定しているとは言いきれません。
実際、採用を進める中でいくつも不具合に遭遇をしましたし、修正版を待たないと先に進めないということが何回かありました。
しかし、いくつか信頼できそうなポイントがあります。
僕らの場合は、実際に試してみたところ体験が良かったこともあり、発展途上な部分には目をつぶりつつ、今後に期待しつつ採用してみることにしました。
Herb はちょっとややこしいパッケージ構成になっています。
Herb のコアとなるパーサー部分は C言語で実装されており、これを Ruby と Node.js から利用できるような bindings パッケージが用意されています。
ERB 向けのツールということで、てっきり gem だけで完結すると思い込んでいたのですが、よくよく調べてみるとコマンドラインから Linter やフォーマッタを利用する場合は Node.js 環境を用意する必要があります。herb gem も herb コマンドを提供していますが、これは Linter やフォーマッタとしては利用できないため、素直に npm 経由でインストールしましょう。
Herb の Linter は @herb-tools/linter というパッケージで提供されています。
手元では以下のコマンドでインストールしました。
$ npm install -D @herb-tools/linter @herb-tools/formatter※ 本記事では Node.js のパッケージ管理については解説しません。ご自身の環境に合わせて適宜読み替えてください。
次に herb-lint --init を実行して、設定ファイル(.herb.yml)を生成します。この設定ファイルは 0.8.0 から導入されたものです。
$ npm exec -- herb-lint --init
✓ Created default configuration at /path/to/project/.herb.yml
✓ Configuration initialized at /path/to/project/.herb.yml
✓ VSCode extension recommended in .vscode/extensions.json
Edit this file to customize linter and formatter settings..herb.yml は次のような内容になっています (コメントは省きました)。
version: 0.8.2
linter:
enabled: true
formatter:
enabled: false
indentWidth: 2
maxLineLength: 80設定ファイルでは Linter やフォーマッタの挙動を設定できます。どのルールを有効/無効にするのか、どのファイルを処理対象にするのか、除外にするのかといった設定が可能です。
設定ファイルを生成せずに Lint を実行することもできますが、いずれ細かい調整で必要になるでしょうから、あらかじめ生成しておくとよいでしょう。ここでは特に設定を変更せず、そのまま進めます。
それでは、Lint を実行してみましょう。ふたたび herb-lint コマンドを実行します。特に引数を与えない場合、カレントディレクトリ以下のファイルが Lint 対象となります。ディレクトリやファイルを指定して個別に Lint を実行したり、表示をシンプルにする --simple オプションを指定するといった使い方もできます。
$ npm exec -- herb-lint
$ npm exec -- herb-lint .
$ npm exec -- herb-lint --simple .Lint の実行例として次のような HTML + ERB ファイルを用意してみました。Lint エラーが出るよう、わざと少し崩してあるのですが、どこだかわかりますか?
<html>
<head>
<link href="//fonts.googleapis.com/css?family=Open+Sans" rel="stylesheet" type="text/css" />
<%=csrf_meta_tags %>
</head>
<body>
<div>
Hello world
</body>
</html>このファイルに herb-lint を実行すると、以下のような指摘が得られます。
$ npm exec -- herb-lint --simple
✓ Using Herb config file at /path/to/project/.herb.yml
/path/to/project/app/views/layouts/application.html.erb:
8:4 ✗ Opening tag `<div>` at (8:5) doesn't have a matching closing tag `</div>` in the same scope. (`MISSING_CLOSING_TAG_ERROR`) (parser-no-errors)
Rule offenses:
parser-no-errors (1 offense in 1 file)
Summary:
Checked 89 files
Files 1 with offenses | 88 clean (89 total)
Offenses 1 error | 0 warnings (1 offense across 1 file)
Fixable 1 offense
Start at 12:46:55
Duration 245ms (46 rules)8行目の <div> タグが閉じられていないという指摘が出ています。
これを修正して、もう一度 Lint を実行してみましょう。
$ npm exec -- herb-lint --simple
✓ Using Herb config file at /path/to/project/.herb.yml
/path/to/project/app/views/layouts/application.html.erb:
5:4 ✗ Add whitespace after `<%=`. (erb-require-whitespace-inside-tags) [Correctable]
3:4 ✗ Use `<link>` instead of self-closing `<link />` for HTML compatibility. (html-no-self-closing) [Correctable]
Rule offenses:
erb-require-whitespace-inside-tags (1 offense in 1 file)
html-no-self-closing (1 offense in 1 file)
Summary:
Checked 89 files
Files 1 with offenses | 88 clean (89 total)
Offenses 2 errors | 0 warnings (2 offenses across 1 file)
Fixable 2 offenses | 2 autocorrectable using `--fix`
Start at 12:47:11
Duration 283ms (46 rules)今度は ERB タグのスペース不足 (erb-require-whitespace-inside-tags) やHTML5 では不要になった自己終了タグの使用 (html-no-self-closing) が指摘されています。
こうして指摘を受けた箇所をひとつずつ修正していきます。
また、指摘に [Correctable] と書かれているものは自動修正も可能です。0.8.0 から追加された --fix オプション を使ってみるとよいでしょう。まだ新しい機能なので、試す際は事前にファイルをコミットしておくなど、念の為もとに戻せるようにしておくのが良いかもしれません。手元ではうまく修正が働きました。
さて、次は herb のフォーマッタを試してみましょう。
フォーマッタは @herb-tools/formatter というパッケージで提供されています。
フォーマッタでも設定ファイル (.herb.yml) が利用できます。もし、まだ設定していない場合は herb-format --init コマンドで生成しておきましょう。
$ npm exec -- herb-format --init
✓ Created default configuration at /path/to/project/.herb.yml
✓ Configuration initialized at /path/to/project/.herb.yml
✓ VSCode extension recommended in .vscode/extensions.json
Formatter is enabled by default.
Edit this file to customize linter and formatter settings.なお、 herb-lint 経由で設定ファイルを生成した場合は、デフォルトではフォーマッタが無効になっています。formatter.enabled を true に書き換え、フォーマッタを有効にしましょう。herb-format 経由で設定ファイルを生成した場合は、最初からフォーマッタが有効になっています。
設定ファイルではインデント幅や行の最大長、対象外のファイルなどが設定できます。手元では maxLineLength を 120 に設定しています。
実際にフォーマットする際は herb-format コマンドを呼び出します。便利なオプションとして、フォーマットの必要性を確認する --check オプションがあります。こちらも引数にディレクトリやファイルが指定できます。
$ npm exec -- herb-format
$ npm exec -- herb-format .
$ npm exec -- herb-format --check .フォーマッタはコマンドを実行するだけで、ファイル(群)を自動的にフォーマットしてくれます。
なお、フォーマッタの利用には注意点があります。Herb のフォーマッタは現状では experimental preview という位置づけ です。そのため、注意してほしいという記述があります。利用する際はあらかじめファイルをコミットしておく、小さい変更に限定するなど、もとに戻せるようにしておくことをおすすめします。
実際、我々のプロジェクトでも ERB が壊れてしまうというバグを踏んだことがあります。
Linter とフォーマッタが CLI として提供されているので、CI にもかんたんに組み込めます。
手元のプロジェクトでは GitLab CI に以下の記述をしています。
herb_lint:
stage: test
image: node:22-slim
script:
- npm ci
- npm exec -- herb-lint
herb_format:
stage: test
image: node:22-slim
script:
- npm ci
- npm exec -- herb-format --check※ 説明のため、キャッシュの設定等は省略しています。
VSCode から Herb を利用する場合は Herb LSP という VSCode 拡張 を利用します。herb-lint --init や herb-format --init で設定ファイルを生成する際、同時に .vscode/settings.json も生成されていましたよね。自動的に推奨扱いになっているはずです。
この拡張をインストールすると、自動的に Lint が実行されるようになります。

Problem タブに Lint エラーがリストアップされるので、ひとつずつ解決していきましょう。また、指摘箇所のカーソルを当てると指摘内容がポップアップ表示されます。このあたりは VSCode の他の拡張機能と同様ですね。

VSCode 拡張も .herb.yml を参照しています。そのため、ルールの調整やフォーマッタの有効化などは .herb.yml を編集して行います。VSCode の設定からも変更可能ですが .herb.yml にまとめておくのが無難でしょう。
フォーマッタは VSCode のメニューから “Format Document” を実行しましょう。
一瞬で反映されましたね。手元では “Format on Save” を有効にしているので、.html.erb ファイルを保存するたびに自動的にフォーマットされています。
ここまで Herb の良いところを紹介してきましたが、注意すべき点もいくつかあります。
現状の Herb による Lint は Ruby コードまわりの解析を行っていません。
明らかな文法エラーや <% %> タグ前後のスペース不足といった、比較的かんたんな Lint は行ってくれますが、Ruby コード内部については踏み込んだ解析を行っていません。
こうした指摘だけでも役に立つのですが、すでに Rubocop で .rb ファイルの Lint をしている場合はすこし物足りなく感じます。このような課題は Herb プロジェクトでも認識されており、 今後の課題 としてあげられています。また、Ruby LSP でも ERB に関する議論が行われており、今後ツールの改善が期待できそうです。
我々のプロジェクトでは rubocop-erb を使って Lint するようにしています。いまのところはツールの組み合わせでカバーしていくしかなさそうですね。
Lint と同様、フォーマッタも Ruby コードの取り扱いは不完全です。
たとえば、複数行にまたがる ERB タグについて考えてみましょう。以下の HTML では ERB タグのインデントが足りていませんよね。
<div>
Select a user:
<%= select_tag "users",
options_for_select(users),
class: "form-control input-medium select" %>
</div>これを手直しするために herb-format を実行してみます。すると、以下のように整形されます。
<div>
Select a user:
<%= select_tag "users",
options_for_select(users),
class: "form-control input-medium select" %>
</div>HTML 部分はフォーマットされるが、ERB タグの中身は変更されないため、一箇所を直したにも関わらず、別の箇所が崩れてしまっています。Rubocop と連動しておらず、本格的なフォーマットはできないにしても、インデントの維持はしてもらえるとありがたいですね。
ひとまず 要望を投げてみた のでどうなるか見てみましょう。
条件によってタグの出しわけをする際、次のような書き方をすることがありますよね (個人的には好みではないですが…)。
<% if some_condition %>
<div class="active">
<% else %>
<div class="inactive">
<% end %>自分が関わっているプロジェクトでも、こういう書き方をしている箇所がありました。
こうした HTML に対して herb-lint を実行すると、次のようにタグの不一致が指摘されます。
/path/to/project/app/views/foobar.html.erb:
4:2 ✗ Opening tag `<div>` at (4:3) doesn't have a matching closing tag `</div>` in the same scope. (`MISSING_CLOSING_TAG_ERROR`) (parser-no-errors)
6:2 ✗ Opening tag `<div>` at (6:3) doesn't have a matching closing tag `</div>` in the same scope. (`MISSING_CLOSING_TAG_ERROR`) (parser-no-errors)
81:0 ✗ Found closing tag `</div>` at (81:2) without a matching opening tag in the same scope. (`MISSING_OPENING_TAG_ERROR`) (parser-no-errors)手元のプロジェクトでは修正してしまいましたが、プロジェクトによっては結構大変かもしれませんね。
このケースはパースエラーが発生しているため、この構造を見直すまで Lint やフォーマットが正しく行われません。導入に際して手間がかかるかもしれませんね。
※ .herb.yml で除外ファイルの設定ができるので、部分的な導入も考えられるかもしれませんね
herb-format であちこちのフォーマットを試していたところ、いくつか不具合や気になる点を見つけました。
いずれも修正済みですが、Herb のフォーマット機能は現在 experimental preview という位置づけであるため、こういった不具合に遭遇する可能性は否定できません。
ファイルが壊れるようなフォーマットが行われた場合は、Herb プロジェクトに報告しつつ、除外パターン (formatter.exclude) に追加して問題を回避しましょう。
formatter:
...
exclude:
- 'app/views/kaminari/_limit.html.erb'Herb のフォーマッタは HTML のコメントタグも意味のあるひとつのタグとして扱い、その他のタグと同じルールが適用されます。
次のように、タグの対応関係を表現するためにコメントを使っている箇所ってありますよね。
<div class="container">
blah blah blah
</div><!-- container -->この HTML をフォーマットを掛けると以下のようになります。
<div class="container">
blah blah blah
</div>
<!-- container -->閉じタグとコメントの間に改行と空行が入り、コメントの対応関係がわかりづらくなっています。ちょっとモヤッとしますが、いまのところこれはそのまま受け入れています。
今回は HTML + ERB 向けのツールキットである Herb を紹介しました。Herb は HTML + ERB 向けのツール群を提供しており、開発体験を大きく向上させてくれます。
一方、まだ若いプロジェクトであるため、いくつか注意点もあります。しかし、それを補って余りある魅力のあるツールであるとも感じます。次回紹介する ReActionView もその魅力のひとつです。
フォーマッタについてはすこし不安定な部分があるので、有効にするにはやや覚悟が必要ですが、うまくハマれば大きな助けになるでしょう。HTML と ERB を利用している Ruby プロジェクトでは、ぜひ導入を検討してみてください。