Upgrade to Pro — share decks privately, control downloads, hide ads and more …

The Anatomy of an ERB Rendering Engine @ SF Bay...

The Anatomy of an ERB Rendering Engine @ SF Bay Area Ruby Meetup, July 2026

ERB is one of Ruby’s oldest and most widely used technologies, yet many Ruby developers only have a vague mental model of how it actually works.

In this talk, we take a deep dive into the internals of ERB rendering engines. We start by examining how ERB templates are compiled and executed today, and the design and trade-offs behind traditional engines such as `erb` and `erubi`, especially in context with rendering HTML.

We then explore where this string-based approach breaks down, particularly when it comes to tooling, diagnostics, and more advanced rendering techniques in HTML+ERB templates.

Finally, we look at a different approach: building an HTML-aware ERB engine on top of a syntax tree. Using `Herb::Engine` as a concrete example, we show how adding structural understanding changes what is possible, and what it unlocks next, including better tooling and the foundations for efficient, reactive rendering.

Avatar for Marco Roth

Marco Roth

July 22, 2026

More Decks by Marco Roth

Other Decks in Programming

Transcript

  1. Marco Roth The Anatomy of an ERB Rendering Engine July

    22, 2026 - San Francisco Ruby Meetup July 2026
  2. 🌐 marcoroth.dev Marco Roth ! Full-Stack Developer & Open Source

    Contributor  @marcoroth  @[email protected]  @marcoroth_  @marcoroth.dev
  3. Marco Roth The Anatomy of an ERB Rendering Engine July

    22, 2026 - San Francisco Ruby Meetup July 2026
  4. 1999 eruby Shugo Maeda, C implementation + the eRuby spec

    1999 ERB Masatoshi Seki, pure-Ruby implementation of that spec. In the stdlib since Ruby 1.8 2006 Erubis Makoto Kuwata, fast, extensible (last release: 2011) 2015 Erbse Nick Sutterer, "an updated Erubis with block support", built for Cells: ERB outside ActionView 2016 Erubi Jeremy Evans, minimal, maintained Erubis successor 2025 Herb::Engine HTML-aware, Erubi-compatible
  5. <h1>Hello, <%= name %>!</h1> _buf = ::String.new _buf << '<h1>Hello

    '.freeze _buf << ( name ).to_s _buf << '!</h1>'.freeze _buf.to_s (simplified+formatted)
  6. <h1>Hello, <%= name %>!</h1> _buf = ::String.new _buf << '<h1>Hello

    '.freeze _buf << ( name ).to_s _buf << '!</h1>'.freeze _buf.to_s (simplified+formatted)
  7. <h1>Hello, <%= name %>!</h1> _buf = ::String.new _buf << '<h1>Hello

    '.freeze _buf << ( name ).to_s _buf << '!</h1>'.freeze _buf.to_s (simplified+formatted)
  8. <h1>Hello, <%= name %>!</h1> _buf = ::String.new _buf << '<h1>Hello

    '.freeze _buf << ( name ).to_s _buf << '!</h1>'.freeze _buf.to_s (simplified+formatted)
  9. <h1>Hello, <%= name %>!</h1> _buf = ::String.new _buf << '<h1>Hello

    '.freeze _buf << ( name ).to_s _buf << '!</h1>'.freeze _buf.to_s (simplified+formatted)
  10. <h1>Hello, <%= name %>!</h1> _buf = ::String.new _buf << '<h1>Hello

    '.freeze _buf << ( name ).to_s _buf << '!</h1>'.freeze _buf.to_s (simplified+formatted)
  11. <div> <% if @admin %> <span>Admin</span> </div> ↑ missing <%

    end %>, dies at runtime, no line, no column
  12. <h1>Hello, <%= name %>!</h1> @ DocumentNode └── children: (1 item)

    └── @ HTMLElementNode ├── open_tag: │ └── @ HTMLOpenTagNode (<h1>) │ ├── body: (3 items) │ ├── @ HTMLTextNode ("Hello, ") │ ├── @ ERBContentNode (<%= name %>) │ └── @ HTMLTextNode ("!") │ └── close_tag: └── @ HTMLCloseTagNode (</h1>)
  13. ERB template string ▼ Herb lossless syntax tree ▼ Herb::Engine

    compiler visitor ▼ add_text / add_code / add_expression ▼ generated Ruby
  14. Erubi::Engine _buf = ::String.new Herb::Engine _buf = ::String.new _buf <<

    '<h1>Hello, ' _buf << '<h1>Hello, ' _buf << ::Erubi.h(name) _buf << ::Herb.h(name) _buf << '!</h1>' _buf << '!</h1>' _buf.to_s _buf.to_s (simplified+formatted)
  15. View Template State <ul> <% @items.each do |item| %> <li><%=

    item %></li> <% end %> </ul> @items = [1, 2, 3, 4] Rendered View Log <ul> <li>1</li> <li>2</li> <li>3</li> Initial view rendered @items state changed re-rendering view tracing dependencies render delta for item `4` apply delta to view </ul> <li>4</li>