Repository navigation
Releases: sebyx07/ruby2html
Release list
v2.1.0
Added
- JRuby now ships a small Java extension (
Ruby2html::JavaNative) that accelerates HTML escaping — the renderer's hot path — over the pure-Ruby implementation. About 1.9x faster end-to-end rendering on JRuby; output is byte-identical. Distributed as aruby2html-<version>-javaplatform gem with the jar prebuilt (no JDK needed to install); everything else stays pure Ruby.
Changed
Ruby2html::Component#render_inaccepts Rails' keyword options (locals:), as Rails main (8.2) passes them and deprecatesrender_inwithout options.- TruffleRuby now uses the pure-Ruby implementation instead of the C extension. TruffleRuby runs C extensions through Sulong but JITs pure Ruby with Graal, which here renders ~28x faster;
Ruby2html.native?is nowfalseon TruffleRuby. - On CRuby, a C extension that fails to load now prints one warning with the reason (
ruby2html: C extension not loaded (...); using the pure-Ruby renderer) instead of silently rendering about 3x slower. JRuby/TruffleRuby andRUBY2HTML_DISABLE_EXT=1stay silent. - Development and single-version CI jobs now run on the latest stable Ruby (4.0). The CRuby test matrix already tracks the official maintained-versions list automatically (currently 3.3, 3.4, 4.0).
v2.0.0
A rewrite of the renderer for speed, memory and correctness, plus a new component class, Rails 8.1 / ViewComponent 4 support, JRuby/TruffleRuby support and full CI/release automation. Contains breaking changes: read "Upgrading to 2.0" below.
Performance
Measured on Ruby 3.4.7 x86_64 (bench/render_bench.rb, a 100-item page with attributes and escaped text):
- About 4.7x faster than 1.6.6 (≈620 → ≈2,950 renders/s) with about 8x fewer allocations per render (3,531 → 419).
- Faster than Erubi (Rails' ERB engine) and Phlex on the same page; see the README snapshot and the CI benchmark workflow for current numbers.
- HTML escaping uses SSE2 (x86_64) / NEON (arm64): up to 5.9x faster on large clean text, about 2x on typical strings.
require 'ruby2html'costs about 0.4 MB and 19 ms (was about 9.5 MB and 116 ms): Prism, the Rails glue and htmlbeautifier load only when used; in eager-loading (production) Rails apps they load at boot instead.
Added
Ruby2html::Component: a lightweight component base class (no ViewComponent needed) withview_template/call,render_in,helpersandcontent; nested components write straight into the parent's buffer.- Pure-Ruby implementation with byte-identical output (
Ruby2html::PureRuby), used on JRuby, when the extension can't be built, or withRUBY2HTML_DISABLE_EXT=1.Ruby2html.native?andRuby2html::IMPLEMENTATIONtell which one is loaded. Ruby2html.escape_html(obj).- Attributes:
truerenders a bare attribute,falseomits it; nesteddata:/aria:Hashes expand todata-user-id="1"; Arrays render as space-separated tokens (class: ['btn', active && 'on']). .html.rbtemplates are compiled with Prism to run directly in the view/component (no instance-variable copying, nomethod_missing).capture,content_forandprovidewith blocks of ruby2html tags work in.html.rbtemplates.- Support for Rails 8.1, ViewComponent 4, JRuby and TruffleRuby; fiber-safe rendering (no thread/fiber-global state).
llms.txt: complete reference for AI coding tools;AGENTS.md(withCLAUDE.mdsymlinked to it) and Claude Code skills/agents for maintainers.- CI: generated CRuby matrix (all maintained versions), Rails matrix, Linux x86_64/arm64 and macOS builds,
-Werrorgcc/clang builds, sanitizers, JRuby/TruffleRuby, gem install smoke test, benchmarks in every PR; Dependabot; releases published automatically when a version bump merges (RubyGems trusted publishing). - Maintained by developerz.ai: its maintainer agent triages issues, reviews PRs, merges on green, runs daily health and release checkups, and opens release PRs (
.dz/).
Fixed
- Attribute cache keyed by
Object#hash: a hash collision could render another element's attributes; the cache also grew without bound. Removed. link_torendered abutton_toform.form_withemitted its fields as escaped text, ignoredpatch/delete(_method), crashed onsubmitwithout a label, and ignored Rails' CSRF/data-remotesettings. It now delegates to Rails' ownform_withandFormBuilder.- Tags inside
link_to/button_toblocks were emitted before the link instead of inside it. html_safecontent was escaped twice; non-String content (p 42) was silently dropped.- A block's String return value replaced markup the block had already written.
- The
<output>tag was unusable (shadowed byRender#output). - Template backtraces were off by 3 lines; the renderer wasn't restored when a template raised; template locals could clash with internal variables or be rewritten into tag calls (
h2 titlein a partial with atitlelocal). - A rescued exception while writing attributes left a half-written start tag in the output.
HtmlBeautifierMiddlewarebeautified each streamed chunk separately, didn't close the body, and touched gzip/204/304 responses.- The packaged gem shipped a locally compiled
.so;rake builddidn't build a gem.
Upgrading to 2.0
Templates and components:
selfin.html.rbtemplates is now the view (or component), not aRuby2html::Render. Replaceself.div/self.plainwithdiv/plain. Without Prism (Ruby < 3.3 and noprismgem) templates still use the 1.x path.Thread.current[:__ruby2html_renderer__]is gone. The active renderer lives on the component/view; usecomponent.__ruby2html_renderer__.form_withoutput now matches Rails (_method,accept-charset, no forceddata-remote, noutf8field); its builder is a proxy around Rails'FormBuilder.Ruby2html::ComponentHelper#method_missingis now private.
HTML output:
5. Non-String content renders: p 42 → <p>42</p> (1.x: <p></p>); nil renders nothing.
6. html_safe strings passed as content or returned from a block are inserted raw (1.x escaped them).
7. A block's return value is used only if the block wrote nothing, and only for String/Symbol/Numeric (1.x: a returned String replaced everything the block wrote).
8. Attributes: true → bare attribute and false omitted (except data-*, aria-*, draggable, spellcheck, contenteditable, which keep "true"/"false"); Hash values expand; Arrays join with spaces; invalid attribute names raise ArgumentError; Hashes/Arrays nested deeper than 32 levels raise ArgumentError.
9. Tag methods accept at most one positional argument (extra arguments raise ArgumentError).
10. The output buffer is UTF-8; strings in other encodings are transcoded (raising EncodingError if impossible).
Removed / packaging:
11. Ruby2html::ATTRIBUTE_CACHE, Render::METHOD_DEFINITIONS, Render#output and the Render#fast_escape_html / #fast_attributes_to_s / #fast_buffer_append / #fast_render_tag methods are removed. Use Ruby2html.escape_html, the value returned by render, or current_output.
12. Ruby >= 3.1 and (for Rails integration) Rails >= 7.2 are required.
13. htmlbeautifier is no longer a dependency: add gem 'htmlbeautifier' if you use Ruby2html::HtmlBeautifierMiddleware.
14. The gem's require path is lib/gem only.