ruby2html 1.6.6 โ 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +197 -0
- data/LICENSE +21 -0
- data/README.md +155 -225
- data/ext/ruby2html/attributes.c +200 -0
- data/ext/ruby2html/buffer.h +43 -0
- data/ext/ruby2html/content.c +120 -0
- data/ext/ruby2html/extconf.rb +36 -9
- data/ext/ruby2html/html_escape.c +252 -0
- data/ext/ruby2html/ruby2html.c +15 -107
- data/ext/ruby2html/ruby2html.h +32 -0
- data/ext/ruby2html/tag_render.c +179 -0
- data/lib/gem/ruby2html/component.rb +118 -0
- data/lib/gem/ruby2html/component_helper.rb +36 -15
- data/lib/gem/ruby2html/html_beautifier_middleware.rb +55 -9
- data/lib/gem/ruby2html/rails_components/base_component.rb +10 -1
- data/lib/gem/ruby2html/rails_components/button_to.rb +3 -3
- data/lib/gem/ruby2html/rails_components/form_with.rb +41 -67
- data/lib/gem/ruby2html/rails_components/image_tag.rb +3 -3
- data/lib/gem/ruby2html/rails_components/link_to.rb +3 -3
- data/lib/gem/ruby2html/rails_components/output_capture.rb +41 -0
- data/lib/gem/ruby2html/rails_components/template_collector.rb +62 -0
- data/lib/gem/ruby2html/rails_components/template_compiler.rb +92 -0
- data/lib/gem/ruby2html/rails_components/template_helpers.rb +77 -0
- data/lib/gem/ruby2html/rails_helper.rb +8 -4
- data/lib/gem/ruby2html/railtie.rb +104 -25
- data/lib/gem/ruby2html/render/pure_ruby.rb +202 -0
- data/lib/gem/ruby2html/render.rb +60 -98
- data/lib/gem/ruby2html/version.rb +1 -1
- data/lib/gem/ruby2html.rb +28 -7
- metadata +22 -78
- data/lib/ruby2html/ruby2html.so +0 -0
- data/ruby2html.gemspec +0 -43
data/README.md
CHANGED
|
@@ -1,159 +1,120 @@
|
|
|
1
|
-
# Ruby2html
|
|
1
|
+
# Ruby2html
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://github.com/sebyx07/ruby2html/actions/workflows/ci.yml)
|
|
4
|
+
[](https://rubygems.org/gems/ruby2html)
|
|
5
|
+
[](https://developerz.ai)
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
Write views in pure Ruby; get escaped, compact HTML. A C extension handles escaping, attribute serialization and tag rendering. Works standalone, as Rails `.html.rb` templates, inside ERB, and in ViewComponent.
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
```ruby
|
|
10
|
+
div class: 'card', 'data-controller': 'card' do
|
|
11
|
+
h2 @product.title
|
|
12
|
+
p @product.description
|
|
13
|
+
a 'Details', href: product_path(@product), class: 'btn'
|
|
14
|
+
end
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
```html
|
|
18
|
+
<div class="card" data-controller="card"><h2>Lamp</h2><p>Warm & bright</p><a href="/products/1" class="btn">Details</a></div>
|
|
19
|
+
```
|
|
8
20
|
|
|
9
|
-
##
|
|
21
|
+
## Why ruby2html
|
|
10
22
|
|
|
11
|
-
|
|
23
|
+
- **Fastest Ruby HTML renderer we've measured.** The page benchmark renders 1.35x faster than Erubi (Rails' ERB engine) and 2.4โ16x faster than Phlex 2, with 40โ90% fewer allocations; in a full Rails request it leads ERB, Slim and Phlex ([numbers](#performance)).
|
|
24
|
+
- **Light.** No runtime dependencies, a 32 KB gem, and `require 'ruby2html'` costs about 0.4 MB and 19 ms. Prism, the Rails glue and the optional beautifier load only when used, or at boot in eager-loading production apps.
|
|
25
|
+
- **Plain Ruby views.** `.html.rb` templates, ERB interop, ViewComponent, or the built-in `Ruby2html::Component`. Escaped by default.
|
|
26
|
+
- **Runs everywhere.** CRuby โฅ 3.1 with a C extension (SIMD escaping on x86_64 and arm64), and JRuby/TruffleRuby through a byte-identical pure-Ruby implementation. Rails 7.2+.
|
|
27
|
+
- **AI-ready.** One-file reference for coding agents: [`llms.txt`](llms.txt).
|
|
12
28
|
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
Supports all maintained CRuby versions (>= 3.1), JRuby (via the pure-Ruby implementation), TruffleRuby, and Rails 7.2+; the CI matrix is generated automatically.
|
|
13
32
|
|
|
14
33
|
```ruby
|
|
34
|
+
# Gemfile
|
|
15
35
|
gem 'ruby2html'
|
|
16
36
|
```
|
|
17
37
|
|
|
18
|
-
|
|
38
|
+
```bash
|
|
39
|
+
bundle install # compiles the native extension (needs a C compiler); pure-Ruby fallback otherwise
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## AI agents
|
|
43
|
+
|
|
44
|
+
**This project is maintained by [developerz.ai](https://developerz.ai).** Its maintainer agent triages issues, reviews every pull request, merges every pull request once its author marks it ready and CI passes, watches project health daily, and opens release PRs. The policy is in [`.dz/maintainer/maintainer.yml`](.dz/maintainer/maintainer.yml). Breaking changes and major releases stay with the owner.
|
|
19
45
|
|
|
20
|
-
|
|
46
|
+
- **Using ruby2html in your app?** Point your agent at [`llms.txt`](llms.txt), the complete reference in one file (DSL rules, escaping, Rails, components, forms, pitfalls). Paste this into your app's `CLAUDE.md` / `AGENTS.md`:
|
|
47
|
+
```markdown
|
|
48
|
+
## ruby2html views
|
|
49
|
+
`*.html.rb` templates and `html do ... end` blocks use the ruby2html gem. Before editing them read
|
|
50
|
+
https://github.com/sebyx07/ruby2html/blob/main/llms.txt
|
|
51
|
+
```
|
|
52
|
+
- **Working on this repo?** [`AGENTS.md`](AGENTS.md) is the only agent-instructions file (`CLAUDE.md` is a symlink to it): commands, code map, invariants, how to verify a change, CI/release automation.
|
|
53
|
+
- **Claude Code**: project skills in `.claude/skills/` (`release`, `benchmark`, `add-html-tag`, `review`, `debug-render`, `upgrade-deps`), read-only reviewer subagents in `.claude/agents/` (`perf-reviewer`, `xss-auditor`), and `.claude/settings.json` with a safe permission allowlist and a non-blocking recompile-reminder hook.
|
|
21
54
|
|
|
22
|
-
|
|
55
|
+
## Usage
|
|
23
56
|
|
|
24
|
-
|
|
57
|
+
### DSL basics
|
|
25
58
|
|
|
26
|
-
|
|
59
|
+
- Every HTML5 tag is a method (`div`, `p`, `ul`, `li`, `img`, `input`, ...), plus `turbo_frame` / `turbo_stream`.
|
|
60
|
+
- `tag content, **attrs` or `tag(**attrs) { nested tags }`. Text and attribute values are HTML-escaped; numbers and symbols are converted (`td 42`).
|
|
61
|
+
- Raw HTML only from `html_safe` strings (Rails helpers, `'<hr>'.html_safe`), as content or via `plain`. `plain 'text'` escapes.
|
|
62
|
+
- Attributes: `'data-id': 1` or `data: { id: 1 }`, `class: ['btn', active && 'on']`, `disabled: true` โ bare `disabled`, `nil`/`false` omitted.
|
|
27
63
|
|
|
28
|
-
|
|
64
|
+
Full rules and pitfalls: [`llms.txt`](llms.txt).
|
|
29
65
|
|
|
30
|
-
|
|
66
|
+
### Rails views (`.html.rb`)
|
|
67
|
+
|
|
68
|
+
File: `app/views/products/index.html.rb`
|
|
31
69
|
|
|
32
70
|
```ruby
|
|
33
71
|
div class: 'container' do
|
|
34
|
-
h1 '
|
|
35
|
-
link_to 'Home
|
|
72
|
+
h1 'Products', class: 'main-title'
|
|
73
|
+
link_to 'Home', root_path, class: 'btn btn-primary', 'data-turbo': false
|
|
36
74
|
|
|
37
75
|
@products.each do |product|
|
|
38
|
-
h2 class: 'item-title', id: "product-#{product
|
|
39
|
-
|
|
40
|
-
end
|
|
41
|
-
p class: 'item-description' do
|
|
42
|
-
product.description
|
|
43
|
-
end
|
|
76
|
+
h2 product.title, class: 'item-title', id: "product-#{product.id}"
|
|
77
|
+
p product.description, class: 'item-description'
|
|
44
78
|
end
|
|
45
79
|
end
|
|
46
80
|
|
|
47
81
|
plain '<div>Inline html</div>'.html_safe
|
|
48
|
-
|
|
49
82
|
render partial: 'shared/navbar'
|
|
50
83
|
```
|
|
51
84
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
File: `config/environments/development.rb` or `config/environments/test.rb`
|
|
55
|
-
```ruby
|
|
56
|
-
config.middleware.use Ruby2html::HtmlBeautifierMiddleware
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
#### Or use your current .erb views
|
|
85
|
+
Controller instance variables are available. Partials can be `.html.rb` too. `link_to`, `image_tag`, `button_to`, `form_with` and `render` write their output directly; other helpers return a value you must output (`h1 t('.title')`, `plain some_safe_html_helper`).
|
|
60
86
|
|
|
61
|
-
###
|
|
62
|
-
|
|
63
|
-
File: `app/controllers/application_controller.rb`
|
|
87
|
+
### Inside ERB
|
|
64
88
|
|
|
65
89
|
```ruby
|
|
66
|
-
# frozen_string_literal: true
|
|
67
|
-
|
|
68
90
|
class ApplicationController < ActionController::Base
|
|
69
|
-
include Ruby2html::RailsHelper #
|
|
91
|
+
include Ruby2html::RailsHelper # exposes the html(context) helper to views
|
|
70
92
|
end
|
|
71
93
|
```
|
|
72
94
|
|
|
73
|
-
File: `app/views/your_view.html.erb`
|
|
74
|
-
|
|
75
|
-
Replace your ERB with beautiful Ruby code:
|
|
76
|
-
|
|
77
95
|
```erb
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
@items.each do |item|
|
|
86
|
-
h2 class: 'item-title', id: "item-#{item[:id]}" do
|
|
87
|
-
item.title
|
|
88
|
-
end
|
|
89
|
-
p class: 'item-description' do
|
|
90
|
-
item.description
|
|
91
|
-
end
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
plain "<div>Inline html</div>".html_safe
|
|
95
|
-
|
|
96
|
-
render partial: 'shared/navbar'
|
|
96
|
+
<h1>Legacy ERB header</h1>
|
|
97
|
+
<%= html(self) do
|
|
98
|
+
div class: 'ruby2html-section' do
|
|
99
|
+
h2 'This section is powered by Ruby2html'
|
|
100
|
+
@items.each { |item| p item[:title] }
|
|
97
101
|
end
|
|
98
|
-
%>
|
|
102
|
+
end %>
|
|
99
103
|
```
|
|
100
104
|
|
|
101
|
-
|
|
105
|
+
ERB, Slim and Ruby2html views, partials and components can be mixed freely, so adoption can be gradual.
|
|
102
106
|
|
|
103
|
-
|
|
104
|
-
ruby 3.3.4 (2024-07-09 revision be1089c8ec) +YJIT [x86_64-linux]
|
|
105
|
-
Warming up --------------------------------------
|
|
106
|
-
GET /benchmark/html (ERB)
|
|
107
|
-
40.000 i/100ms
|
|
108
|
-
GET /benchmark/ruby (Ruby2html templates .html.rb)
|
|
109
|
-
12.000 i/100ms
|
|
110
|
-
GET /benchmark/ruby (Ruby2html + view components)
|
|
111
|
-
12.000 i/100ms
|
|
112
|
-
GET /benchmark/slim (Slim)
|
|
113
|
-
46.000 i/100ms
|
|
114
|
-
GET /benchmark/phlex (Phlex)
|
|
115
|
-
34.000 i/100ms
|
|
116
|
-
Calculating -------------------------------------
|
|
117
|
-
GET /benchmark/html (ERB)
|
|
118
|
-
414.030 (ยฑ 2.4%) i/s - 24.840k in 60.032818s
|
|
119
|
-
GET /benchmark/ruby (Ruby2html templates .html.rb)
|
|
120
|
-
124.973 (ยฑ 3.2%) i/s - 7.500k in 60.071485s
|
|
121
|
-
GET /benchmark/ruby (Ruby2html + view components)
|
|
122
|
-
123.211 (ยฑ 4.1%) i/s - 7.380k in 60.000731s
|
|
123
|
-
GET /benchmark/slim (Slim)
|
|
124
|
-
431.525 (ยฑ 9.0%) i/s - 25.668k in 60.103492s
|
|
125
|
-
GET /benchmark/phlex (Phlex)
|
|
126
|
-
328.925 (ยฑ 7.0%) i/s - 19.618k in 60.019961s
|
|
127
|
-
|
|
128
|
-
Comparison:
|
|
129
|
-
GET /benchmark/slim (Slim): 431.5 i/s
|
|
130
|
-
GET /benchmark/html (ERB): 414.0 i/s - same-ish: difference falls within error
|
|
131
|
-
GET /benchmark/phlex (Phlex): 328.9 i/s - 1.31x slower
|
|
132
|
-
GET /benchmark/ruby (Ruby2html templates .html.rb): 125.0 i/s - 3.45x slower
|
|
133
|
-
GET /benchmark/ruby (Ruby2html + view components): 123.2 i/s - 3.50x slower
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
### With ViewComponents
|
|
137
|
-
|
|
138
|
-
Ruby2html seamlessly integrates with ViewComponents, offering flexibility in how you define your component's HTML structure. You can use the `call` method with Ruby2html syntax, or stick with traditional `.erb` template files.
|
|
139
|
-
|
|
140
|
-
File: `app/components/application_component.rb`
|
|
107
|
+
### ViewComponent
|
|
141
108
|
|
|
142
109
|
```ruby
|
|
143
|
-
# frozen_string_literal: true
|
|
144
|
-
|
|
145
110
|
class ApplicationComponent < ViewComponent::Base
|
|
146
111
|
include Ruby2html::ComponentHelper
|
|
147
112
|
end
|
|
148
113
|
```
|
|
149
114
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
File: `app/components/greeting_component.rb`
|
|
115
|
+
Inline `call`:
|
|
153
116
|
|
|
154
117
|
```ruby
|
|
155
|
-
# frozen_string_literal: true
|
|
156
|
-
|
|
157
118
|
class GreetingComponent < ApplicationComponent
|
|
158
119
|
def initialize(name)
|
|
159
120
|
@name = name
|
|
@@ -161,173 +122,142 @@ class GreetingComponent < ApplicationComponent
|
|
|
161
122
|
|
|
162
123
|
def call
|
|
163
124
|
html do
|
|
164
|
-
h1 class: 'greeting', 'data-user': @name
|
|
165
|
-
|
|
166
|
-
end
|
|
167
|
-
p class: 'welcome-message' do
|
|
168
|
-
'Welcome to the wonderful world of Ruby2html!'
|
|
169
|
-
end
|
|
125
|
+
h1 "Hello, #{@name}!", class: 'greeting', 'data-user': @name
|
|
126
|
+
p 'Welcome to Ruby2html!', class: 'welcome-message'
|
|
170
127
|
end
|
|
171
128
|
end
|
|
172
129
|
end
|
|
173
130
|
```
|
|
174
131
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
File: `app/components/farewell_component.rb`
|
|
132
|
+
Or a sidecar template, `app/components/farewell_component.html.rb`:
|
|
178
133
|
|
|
179
134
|
```ruby
|
|
180
|
-
# frozen_string_literal: true
|
|
181
|
-
|
|
182
|
-
class FarewellComponent < ApplicationComponent
|
|
183
|
-
def initialize(name)
|
|
184
|
-
@name = name
|
|
185
|
-
end
|
|
186
|
-
end
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
File: `app/components/farewell_component.html.rb`
|
|
190
|
-
|
|
191
|
-
```rb
|
|
192
135
|
div class: 'farewell' do
|
|
193
|
-
h1 class: 'farewell-message'
|
|
194
|
-
"Goodbye, #{@name}! ๐"
|
|
195
|
-
end
|
|
196
|
-
p class: 'farewell-text' do
|
|
197
|
-
'We hope to see you again soon!'
|
|
198
|
-
end
|
|
136
|
+
h1 "Goodbye, #{@name}!", class: 'farewell-message'
|
|
199
137
|
end
|
|
200
138
|
```
|
|
201
139
|
|
|
202
|
-
|
|
203
|
-
- Use Ruby2html syntax for new components or when refactoring existing ones
|
|
204
|
-
- Keep using familiar ERB templates where preferred
|
|
205
|
-
- Mix and match approaches within your application as needed
|
|
206
|
-
|
|
207
|
-
### More Component Examples
|
|
140
|
+
Public component methods can be called from the template and may emit tags themselves (see `app/components/first_component.rb`).
|
|
208
141
|
|
|
209
|
-
|
|
142
|
+
### `Ruby2html::Component` (no ViewComponent required)
|
|
210
143
|
|
|
211
144
|
```ruby
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
def initialize
|
|
216
|
-
@item = 'Hello, World!'
|
|
145
|
+
class CardComponent < Ruby2html::Component
|
|
146
|
+
def initialize(title:)
|
|
147
|
+
@title = title
|
|
217
148
|
end
|
|
218
149
|
|
|
219
|
-
def
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
div class: 'content-wrapper' do
|
|
225
|
-
h2 'A subheading'
|
|
226
|
-
end
|
|
227
|
-
p class: 'greeting-text', 'data-testid': 'greeting' do
|
|
228
|
-
@item
|
|
229
|
-
end
|
|
150
|
+
def view_template
|
|
151
|
+
div class: 'card' do
|
|
152
|
+
h2 @title
|
|
153
|
+
content # block passed to render
|
|
154
|
+
link_to 'More', helpers.root_path
|
|
230
155
|
end
|
|
231
156
|
end
|
|
232
157
|
end
|
|
158
|
+
|
|
159
|
+
# any view or controller
|
|
160
|
+
render CardComponent.new(title: 'Hi')
|
|
233
161
|
```
|
|
234
162
|
|
|
235
|
-
|
|
163
|
+
Tag methods run on the component itself (no instance-variable copying); nested ruby2html components write into the parent's buffer.
|
|
236
164
|
|
|
237
|
-
|
|
238
|
-
# frozen_string_literal: true
|
|
165
|
+
### Forms
|
|
239
166
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
end
|
|
246
|
-
link_to 'Home', root_path, class: 'nav-link', 'data-turbo-frame': false
|
|
247
|
-
end
|
|
248
|
-
end
|
|
167
|
+
```ruby
|
|
168
|
+
form_with model: @user do |f|
|
|
169
|
+
f.label :email
|
|
170
|
+
f.text_field :email
|
|
171
|
+
f.submit 'Save'
|
|
249
172
|
end
|
|
250
173
|
```
|
|
251
174
|
|
|
252
|
-
|
|
175
|
+
### Without Rails
|
|
176
|
+
|
|
253
177
|
```ruby
|
|
254
|
-
|
|
178
|
+
require 'ruby2html'
|
|
179
|
+
|
|
180
|
+
Ruby2html::Render.new do # optional context object: its public methods and ivars become available
|
|
255
181
|
html do
|
|
256
|
-
head
|
|
257
|
-
|
|
258
|
-
end
|
|
259
|
-
body do
|
|
260
|
-
h1 'Hello, World!'
|
|
261
|
-
end
|
|
182
|
+
head { title 'Ruby2html Example' }
|
|
183
|
+
body { h1 'Hello, World!' }
|
|
262
184
|
end
|
|
263
|
-
end
|
|
264
|
-
|
|
265
|
-
puts renderer.render # => "<html><head><title>Ruby2html Example</title></head><body><h1>Hello, World!</h1></body></html>"
|
|
185
|
+
end.render
|
|
186
|
+
# => "<html><head><title>Ruby2html Example</title></head><body><h1>Hello, World!</h1></body></html>"
|
|
266
187
|
```
|
|
267
188
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
One of the best features of Ruby2html is that you don't need to rewrite all your views at once! You can adopt it gradually, mixing Ruby2html with your existing ERB templates. This allows for a smooth transition at your own pace.
|
|
271
|
-
|
|
272
|
-
### Mixed usage example
|
|
189
|
+
### Pretty HTML in development
|
|
273
190
|
|
|
274
|
-
|
|
191
|
+
```ruby
|
|
192
|
+
# Gemfile (development group): gem 'htmlbeautifier'
|
|
193
|
+
# config/environments/development.rb
|
|
194
|
+
config.middleware.use Ruby2html::HtmlBeautifierMiddleware
|
|
195
|
+
```
|
|
275
196
|
|
|
276
|
-
|
|
277
|
-
<h1>Welcome to our gradually evolving page!</h1>
|
|
197
|
+
## Performance
|
|
278
198
|
|
|
279
|
-
|
|
199
|
+
Benchmarks run in CI on every push to `main` and every pull request ([`.github/workflows/benchmark.yml`](.github/workflows/benchmark.yml)): the latest run's summary page has the current table, and PRs get a sticky comment with the delta against `main`. Run them locally with `bundle exec rake bench` (standalone scripts in `bench/`) or `bundle exec rspec spec/benchmarks/` (request-level through the demo app, vs ERB, Slim, Phlex and ViewComponent).
|
|
280
200
|
|
|
281
|
-
|
|
282
|
-
html(self) do
|
|
283
|
-
div class: 'ruby2html-section' do
|
|
284
|
-
h2 "This section is powered by Ruby2html!"
|
|
285
|
-
p "Isn't it beautiful? ๐"
|
|
286
|
-
end
|
|
287
|
-
end
|
|
288
|
-
%>
|
|
201
|
+
### Snapshot at 2.0.0 release (2026-10-31)
|
|
289
202
|
|
|
290
|
-
|
|
203
|
+
This is a dated snapshot. CI has the live numbers. Measured on Ruby 3.4.7 x86_64 on a busy machine (load average about 7), so read the ratios rather than the absolute figures.
|
|
291
204
|
|
|
292
|
-
|
|
293
|
-
<!-- More legacy ERB code -->
|
|
294
|
-
</footer>
|
|
295
|
-
```
|
|
205
|
+
**Page render** (`bench/render_bench.rb`, no Rails): a header with nav, 100 list items with links, images, attributes and escaped text, about 34 KB of HTML.
|
|
296
206
|
|
|
297
|
-
|
|
207
|
+
| Engine | Renders/s | Time per render | Allocations per render |
|
|
208
|
+
|---|---|---|---|
|
|
209
|
+
| **ruby2html 2.0 (C extension)** | **2,676** | **0.37 ms** | **419** |
|
|
210
|
+
| Erubi 1.x (Rails' ERB engine) | 1,988 (1.35x slower) | 0.50 ms | 713 |
|
|
211
|
+
| ruby2html 2.0 (pure Ruby: JRuby, no compiler) | ~820 | ~1.2 ms | 1,029 |
|
|
212
|
+
| Phlex 2.4, enlarged attribute cacheยน | ~1,250 (โ2.4x slower) | ~0.8 ms | 831 |
|
|
213
|
+
| Phlex 2.4, defaults | 164 (16x slower) | 6.1 ms | 6,410 |
|
|
214
|
+
| ruby2html 1.6.6 | ~620 | ~1.6 ms | 3,531 |
|
|
298
215
|
|
|
299
|
-
-
|
|
300
|
-
- Gradually introduce Ruby2html in specific sections
|
|
301
|
-
- Use Ruby2html in new components while maintaining older ones
|
|
302
|
-
- Refactor your views at your own pace
|
|
216
|
+
ยน Phlex 2's default attribute cache (2 KB, FIFO) misses on per-row values such as ids and hrefs. `Phlex::ATTRIBUTE_CACHE.expand(1_000_000)` is its best case. Phlex 2 also checks URLs for `javascript:`; ruby2html leaves URL validation to you.
|
|
303
217
|
|
|
304
|
-
|
|
218
|
+
**Full Rails request** (`spec/benchmarks/requests_spec.rb`; Rails 8.1, a 7 ms request where most of the time is Rails itself; differences are within ยฑ15% error):
|
|
305
219
|
|
|
306
|
-
|
|
220
|
+
| Engine | Requests/s |
|
|
221
|
+
|---|---|
|
|
222
|
+
| **`Ruby2html::Component`** | **144.5** |
|
|
223
|
+
| **ViewComponent 4 + ruby2html** | **142.2** |
|
|
224
|
+
| **`.html.rb` template** | **139.6** |
|
|
225
|
+
| Slim | 128.3 |
|
|
226
|
+
| ERB | 122.6 |
|
|
227
|
+
| Phlex 2.4 | 116.6 |
|
|
307
228
|
|
|
308
|
-
|
|
229
|
+
**HTML escaping** (SIMD vs scalar): up to 5.9x faster on 10 KB clean text, about 2x on 200-byte strings; short strings take the scalar path.
|
|
309
230
|
|
|
310
|
-
|
|
231
|
+
**Footprint:**
|
|
311
232
|
|
|
312
|
-
|
|
233
|
+
| | Ruby/C lines | Runtime dependencies | `require` cost |
|
|
234
|
+
|---|---|---|---|
|
|
235
|
+
| **ruby2html** | 1.1k Ruby + 0.8k C | none | ~0.4 MB, ~19 ms |
|
|
236
|
+
| phlex + phlex-rails | 6.2k | refract, zeitwerk, railties | ~1.4 MB, ~30 ms (phlex alone) |
|
|
237
|
+
| view_component | 5.3k | actionview, activesupport, prism | |
|
|
238
|
+
| slim | 7.3k | temple, tilt | |
|
|
313
239
|
|
|
314
|
-
|
|
240
|
+
How it is fast:
|
|
315
241
|
|
|
316
|
-
|
|
242
|
+
- **Tag methods in C**: each tag writes open tag, content/block output and close tag straight into one shared buffer (no nested buffers, no `*args`/`**kwargs` allocation without keywords).
|
|
243
|
+
- **HTML escaping**: 16-byte SSE2/NEON scan for `& < > " '`; clean strings are one `memcpy`, dirty strings get their exact size computed once.
|
|
244
|
+
- **Attributes**: written directly into the buffer, no intermediate strings, no cache to thrash or leak.
|
|
245
|
+
- **Lazy loading**: `require 'ruby2html'` loads only the renderer. Production Rails apps load everything at boot, so forked workers share it copy-on-write.
|
|
246
|
+
- **Templates and components**: `.html.rb` templates are rewritten at compile time (Prism) to run in the view itself; `Ruby2html::Component` runs tag methods on the component. No instance-variable copying or `method_missing` per render.
|
|
247
|
+
- A pure-Ruby implementation with identical output is used when the extension is unavailable.
|
|
317
248
|
|
|
318
|
-
##
|
|
249
|
+
## Development
|
|
319
250
|
|
|
320
|
-
|
|
251
|
+
```bash
|
|
252
|
+
bundle install
|
|
253
|
+
bundle exec rake compile # build the C extension
|
|
254
|
+
CI=1 bundle exec rspec # specs (CI=1 skips the slow benchmark spec)
|
|
255
|
+
bundle exec rubocop
|
|
256
|
+
bin/rails server # demo app
|
|
257
|
+
```
|
|
321
258
|
|
|
322
|
-
|
|
259
|
+
The repository is both the gem (`lib/gem/`, `ext/ruby2html/`) and a Rails demo app (`app/`, `config/`). See [CONTRIBUTING.md](CONTRIBUTING.md), [docs/architecture.md](docs/architecture.md), [docs/release.md](docs/release.md) and [CHANGELOG.md](CHANGELOG.md).
|
|
323
260
|
|
|
324
|
-
|
|
325
|
-
- Seamless Rails integration ๐ค๏ธ
|
|
326
|
-
- ViewComponent support with flexible template options ๐งฉ
|
|
327
|
-
- Automatic HTML beautification ๐
|
|
328
|
-
- Easy addition of custom attributes and data attributes ๐ท๏ธ
|
|
329
|
-
- Gradual adoption - mix with existing ERB templates ๐ข
|
|
330
|
-
- Improved readability and maintainability ๐
|
|
331
|
-
- Full access to Ruby's power in your views ๐ช
|
|
261
|
+
## License
|
|
332
262
|
|
|
333
|
-
|
|
263
|
+
MIT. See [LICENSE](LICENSE).
|