erb.cr
erb.cr
Rails-compatible ERB for Crystal, compiled at build time. Templates keep ERB syntax and are compiled into native render methods via a run() template processor (the same trick as the stdlib's ECR) — no runtime parsing, no runtime interpolation.
Extracted from action_view.cr, where parity with the real ActionView 8.1.4 is verified with oracle specs (151 examples, 0 failures).
⚠️ EXPERIMENTAL FRAMEWORK, DO NOT USE IN PRODUCTION YET.
Rails semantics
<%= expr %>auto-escapesString; aSafeString(the SafeBuffer analog — Crystal'sStringcannot be subclassed) is appended raw<% stmt %>is control code;<%# ... %>comments are dropped- Erubi trim: a newline directly after a
<% %>code tag is removed - Block helpers work:
<%= helper do %> ... <% end %>keeps the whole call as the escaped expression, the block body appends to the shared output buffer (thecapture/content_formachinery) - Layouts use
<%= yield %>; partials get typed locals and collection rendering with a 0-based<name>_counter
The expression language inside the tags is Crystal, not Ruby.
Installation
dependencies:
erb_cr:
github: OrelSokolov/erb.cr
shards install
Requires Crystal >= 1.21.0; no other dependencies.
Usage
Include ERB::Rendering and compile templates into methods:
require "erb_cr"
class View
include ERB::Rendering
getter title : String
def initialize(@title = "Hello & welcome")
end
compile_template "page", "app/views/page.html.erb"
end
View.new.page.to_s # template compiled into #page at build time
Layouts (the compiled method takes a block for <%= yield %>):
compile_layout "application", "app/views/layouts/application.html.erb"
def render_in_layout(content : String) : String
application { content }.to_s
end
Partials — locals are declared with types at compile time and become real local variables inside the compiled method:
compile_partials({
"post" => {path: "app/views/_post.html.erb", locals: {"post" => Post}},
})
render("post", {"post" => post}) # render partial:, locals:
render("post", collection: posts) # + post_counter, 0-based
render("post", collection: posts, spacer_template: "gap")
Output safety:
"<b>x</b>".html_safe? # => false
"<b>x</b>".html_safe # => ERB::SafeString, appended raw by <%= %>
ERB::SafeString.new("<b>") + " plain" # SafeString#+ keeps the wrapper
Why not stdlib ECR?
ECR does not escape <%= %>, has no safe-string contract, no block helpers, no trim/partials machinery. erb.cr reconstructs the ERB slice of Rails ActionView — including capture/content_for — behind a compile-time pipeline.
License
Part of the rails.cr monorepo experiments; see the repository.
erb.cr
- 0
- 0
- 0
- 2
- 0
- about 3 hours ago
- October 14, 2026
Sat, 17 Oct 2026 09:00:40 GMT