orb_template 0.1.1 → 0.1.2
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/README.md +58 -36
- data/lib/orb/patterns.rb +2 -1
- data/lib/orb/rails_template.rb +26 -1
- data/lib/orb/tokenizer2.rb +35 -1
- data/lib/orb/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 72cfaec138706b6a2a92cec82fc558a6dfe13a56e21dbad5fa18fd5234104a08
|
|
4
|
+
data.tar.gz: 8fc11d77077c47693176181594c1ade6e39de19300ad66acfddbe9383bc8da3f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3cd200ecc1e3292f31fa5d90120a97850b5e6494bc157ba77cad66ea77662be835723fb2fe6a1bc7056052ee120c8e73bde4abf62177e36acebc6d11e5eebbd2
|
|
7
|
+
data.tar.gz: eb9a1b1edb4e41cbbfeac8ff9f2230fb05ad1c9ea932c0a4bd2cdddb16697508394758a1b752121e1b2ea096f84dcdc9a6a1a245e87193657fb210d91be4a2a2
|
data/README.md
CHANGED
|
@@ -2,12 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://badge.fury.io/rb/orb_template)
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
5
|
https://github.com/user-attachments/assets/8380b9a8-2063-40f3-a9b6-1b5d623d6f31
|
|
8
6
|
|
|
9
|
-
|
|
10
|
-
|
|
11
7
|
**ORB** is a template language for Ruby with the express goal of providing a first-class DSL for rendering [ViewComponents](https://viewcomponent.org). It is heavily inspired by [React JSX](https://react.dev/learn/writing-markup-with-jsx) and [Surface](https://surace-ui.org).
|
|
12
8
|
|
|
13
9
|
## Show me an Example
|
|
@@ -63,7 +59,6 @@ https://github.com/user-attachments/assets/8380b9a8-2063-40f3-a9b6-1b5d623d6f31
|
|
|
63
59
|
- [Editor Support](#editor-support)
|
|
64
60
|
- [Roadmap](#roadmap)
|
|
65
61
|
|
|
66
|
-
|
|
67
62
|
## Installation
|
|
68
63
|
|
|
69
64
|
In your `Gemfile`, add:
|
|
@@ -78,13 +73,13 @@ then run:
|
|
|
78
73
|
bundle install
|
|
79
74
|
```
|
|
80
75
|
|
|
81
|
-
The gem automatically registers the `ORB` template engine as the renderer for `*.orb` template files
|
|
76
|
+
The gem automatically registers the `ORB` template engine as the renderer for `*.html.orb` view template files.
|
|
82
77
|
|
|
83
78
|
## Motivation
|
|
84
79
|
|
|
85
|
-
There already exist a plethora of fast, battle-proven template
|
|
80
|
+
There already exist a plethora of fast, battle-proven template languages for the Ruby/Rails ecosystem, so why invent another one?
|
|
86
81
|
|
|
87
|
-
ORB was born out of the frustration that instantiating and rendering view components with existing template engines quickly becomes tedious. This hindered adoption of ViewComponents in our projects, impacted velocity,
|
|
82
|
+
ORB was born out of the frustration that instantiating and rendering view components with existing template engines quickly becomes tedious. This hindered adoption of ViewComponents in our projects, impacted velocity, maintenance and new-developer onboarding. These effects were especially pronounced with highly customizable view components with long argument lists, as well as deeply nested components, and component trees - like a Design System / Component Library.
|
|
88
83
|
|
|
89
84
|
A common solution to making the rendering of view components less verbose is to define component-specific view helpers like so:
|
|
90
85
|
|
|
@@ -106,7 +101,7 @@ module ComponentsHelper
|
|
|
106
101
|
end
|
|
107
102
|
```
|
|
108
103
|
|
|
109
|
-
You can then
|
|
104
|
+
You can then use these view helpers in your front-end code, wherever a view components needs to be rendered:
|
|
110
105
|
|
|
111
106
|
```erb
|
|
112
107
|
<%= card(title: "Your friends") do %>
|
|
@@ -128,17 +123,17 @@ The `ORB` template language takes another path and allows you to write component
|
|
|
128
123
|
<Card:Section title="Birthdays today">
|
|
129
124
|
<List>
|
|
130
125
|
<List.Item>
|
|
131
|
-
<Link url={member_path(2)}
|
|
126
|
+
<Link url={member_path(2)} Carl Schwartz (27) />
|
|
132
127
|
</List.Item>
|
|
133
128
|
<List.Item>
|
|
134
|
-
<Link url={member_path(3)}
|
|
129
|
+
<Link url={member_path(3)} Floralie Brain (38) />
|
|
135
130
|
</List.Item>
|
|
136
131
|
</List>
|
|
137
132
|
</Card:Section>
|
|
138
133
|
</Card>
|
|
139
134
|
```
|
|
140
135
|
|
|
141
|
-
Your code becomes more
|
|
136
|
+
Your code becomes more focused, grokable and maintainable. Front-end teams that may already be familiar with JSX or VUE can become productive faster, and can make use of their existing editor tooling for HTML like `Emmet` when writing templates.
|
|
142
137
|
|
|
143
138
|
### Core Values
|
|
144
139
|
|
|
@@ -151,11 +146,10 @@ We believe that any template language should be enjoyable to the user:
|
|
|
151
146
|
- Dynamic: Rendering with a context of local variables and objects is fast.
|
|
152
147
|
- Polite: guide the user with useful error messages when their templates contain problems.
|
|
153
148
|
|
|
154
|
-
|
|
155
149
|
### Conventions
|
|
156
150
|
|
|
157
151
|
- Components rendered by the `ORB` engine live under the configured namespace and omit the `Component` suffix from their class names.
|
|
158
|
-
-
|
|
152
|
+
- HTML view templates have a file extension of `.*.orb`, for example: `my_template.html.orb` for a template named `:my_template` that outputs in `format: :html`.
|
|
159
153
|
|
|
160
154
|
---
|
|
161
155
|
|
|
@@ -164,6 +158,7 @@ We believe that any template language should be enjoyable to the user:
|
|
|
164
158
|
ORB fully supports the HTML5 markup language standard and extends HTML with additional syntax for expressions, dynamic attributes, blocks, components, slots, and comments.
|
|
165
159
|
|
|
166
160
|
### Configuration
|
|
161
|
+
|
|
167
162
|
You can configure the `ORB` engine in your Rails application with an initializer, e.g., `config/initializers/orb.rb`:
|
|
168
163
|
|
|
169
164
|
```ruby
|
|
@@ -174,7 +169,7 @@ This will instruct the `ORB` engine to look for components under the `MyComponen
|
|
|
174
169
|
|
|
175
170
|
### HTML5
|
|
176
171
|
|
|
177
|
-
Regular HTML tags are fully supported by `ORB`. Just write your HTML tags as you are used to and
|
|
172
|
+
Regular HTML tags are fully supported by `ORB`. Just write your HTML tags as you are used to and you are good to go.
|
|
178
173
|
|
|
179
174
|
```html
|
|
180
175
|
<div id="page-banner" class="banner" aria-role="alert">
|
|
@@ -184,7 +179,7 @@ Regular HTML tags are fully supported by `ORB`. Just write your HTML tags as you
|
|
|
184
179
|
|
|
185
180
|
### Expressions
|
|
186
181
|
|
|
187
|
-
`ORB` supports Ruby expressions in the code through double curly-braces (
|
|
182
|
+
`ORB` supports Ruby expressions in the code through double curly-braces (mustache-like syntax). The code inside the curly-braces will be evaluated at render time, and the result will be HTML-safe escaped and inserted into the output.:
|
|
188
183
|
|
|
189
184
|
```html
|
|
190
185
|
<div id="page-banner" class="banner" aria-role="alert">
|
|
@@ -226,38 +221,44 @@ For example, a `Banner` may be conditionally rendered through an `{#if}` block c
|
|
|
226
221
|
|
|
227
222
|
```html
|
|
228
223
|
{#if banner.urgent?}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
224
|
+
<div id={dom_id(banner)} class={banner.classNames}>
|
|
225
|
+
<span class="message">{{banner.message}}</span>
|
|
226
|
+
</div>
|
|
232
227
|
{/if}
|
|
233
228
|
```
|
|
234
229
|
|
|
235
230
|
Since control flow is such a common thing in templates, `ORB` provides special syntactic sugar for the `{#if}` and `{#for}` blocks through the `:if` and `:for` directives on HTML tags. The above example can thus be rewritten as:
|
|
236
231
|
|
|
237
232
|
```html
|
|
238
|
-
<div id=
|
|
233
|
+
<div id={dom_id(banner)} class={banner.classNames} :if={banner.urgent?}>
|
|
239
234
|
<span class="message">{{banner.message}}</span>
|
|
240
235
|
</div>
|
|
241
236
|
```
|
|
242
237
|
|
|
243
238
|
### Splatted Attributes
|
|
244
|
-
|
|
239
|
+
|
|
240
|
+
`ORB` supports attribute splatting for both HTML tags and view components through the `**attributes` syntax. The attribute name for the splat must be a `Hash`; all key-value pairs will be added as attributes to the tag. For example:
|
|
245
241
|
|
|
246
242
|
```html
|
|
247
|
-
<div **banner_attributes>
|
|
248
|
-
... content ...
|
|
249
|
-
</div>
|
|
243
|
+
<div **banner_attributes>... content ...</div>
|
|
250
244
|
```
|
|
251
245
|
|
|
246
|
+
### Splatted Attribute Expressions
|
|
247
|
+
|
|
248
|
+
You can also use an expression to define the splatted attributes through the `**{expression}` syntax. The expression must evaluate to a `Hash`, and all key-value pairs will be added as attributes to the tag. For example:
|
|
249
|
+
|
|
250
|
+
```html
|
|
251
|
+
<div **{dynamic_attributes}>... content ...</div>
|
|
252
|
+
```
|
|
252
253
|
|
|
253
254
|
### View Components
|
|
254
255
|
|
|
255
|
-
In `ORB` templates, you can render
|
|
256
|
+
In `ORB` templates, you can render ViewComponents as if they were HTML tags. The `Component` suffix on ViewComponents may be omitted to make the markup nicer.
|
|
256
257
|
|
|
257
258
|
For example, if you have a `Button` view component that may be defined as:
|
|
258
259
|
|
|
259
260
|
```ruby
|
|
260
|
-
class MyComponents::Button <
|
|
261
|
+
class MyComponents::Button < ViewComponent::Base
|
|
261
262
|
def initialize(url: "#", **options)
|
|
262
263
|
@url = url
|
|
263
264
|
@options = options
|
|
@@ -271,12 +272,14 @@ class MyComponents::Button < ::ViewComponent::Base
|
|
|
271
272
|
end
|
|
272
273
|
```
|
|
273
274
|
|
|
274
|
-
you can render the component in an ORB template `
|
|
275
|
+
you can render the component in an ORB template `show.html.orb` as:
|
|
275
276
|
|
|
276
277
|
```jsx
|
|
277
278
|
<Button url="/click_me">I am a button</Button>
|
|
278
279
|
```
|
|
279
280
|
|
|
281
|
+
Tip: you can also define the markup for a component inline as an `orb_template <<-ORB ... ORB` block to use the `ORB` template language for the component's own view template.
|
|
282
|
+
|
|
280
283
|
### ViewComponent Slots
|
|
281
284
|
|
|
282
285
|
`ORB` also provides a DSL for invoking a component slot and passing content to the slot through the `Component:Slot` syntax. For example, if you have a `Card` component that defines a `Sections` slot via `renders_many :sections, Card::Section`, you can invoke and fill the slot in an `ORB` template like this:
|
|
@@ -289,6 +292,8 @@ you can render the component in an ORB template `button.html.orb` as:
|
|
|
289
292
|
</Card>
|
|
290
293
|
```
|
|
291
294
|
|
|
295
|
+
Tip: Slot names are case-insensitive, so `<Card:section> ... </Card:section>` also works.
|
|
296
|
+
|
|
292
297
|
### Namespaces
|
|
293
298
|
|
|
294
299
|
Sometimes, you may want to organize your components in sub-namespaces, or use components from other libraries. `ORB` supports this through dot notation in the tag names. For example, if you have a `MyComponents::Admin::Button` component, you can render it in an `ORB` template like this:
|
|
@@ -317,14 +322,14 @@ Namespaces defined in this way will be searched in order of definition when reso
|
|
|
317
322
|
|
|
318
323
|
**Public comments** are sent to the browser, and can be read by users inspecting the page source. ORB considers default HTML comments `<!-- -->` to be public comments.
|
|
319
324
|
|
|
320
|
-
```
|
|
325
|
+
```html
|
|
321
326
|
<!-- I will be sent to the browser -->
|
|
322
327
|
<p>Hello World!</p>
|
|
323
328
|
```
|
|
324
329
|
|
|
325
330
|
**Private comments**, unlike public comments, won't be sent to the browser. Use private comments to mark up your ORB template with annotations that you do not wish users to see.
|
|
326
331
|
|
|
327
|
-
```
|
|
332
|
+
```ruby-orb
|
|
328
333
|
{!-- I won't be sent to the browser --}
|
|
329
334
|
<p>Hello World!</p>
|
|
330
335
|
```
|
|
@@ -337,6 +342,24 @@ Namespaces defined in this way will be searched in order of definition when reso
|
|
|
337
342
|
|
|
338
343
|
Your favorite editor is not listed? Feel free to contribute an extension/plugin for your editor of choice!
|
|
339
344
|
|
|
345
|
+
### Visual Studio Code
|
|
346
|
+
|
|
347
|
+
To enable `Emmet` support for ORB, add this to your `settings.json`:
|
|
348
|
+
|
|
349
|
+
```json
|
|
350
|
+
"emmet.includeLanguages": {
|
|
351
|
+
"ruby-orb": "html",
|
|
352
|
+
}
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
To enable `Tailwindcss` support for ORB, add this to your `settings.json`:
|
|
356
|
+
|
|
357
|
+
```json
|
|
358
|
+
"tailwindCSS.includeLanguages": {
|
|
359
|
+
"ruby-orb": "html"
|
|
360
|
+
}
|
|
361
|
+
```
|
|
362
|
+
|
|
340
363
|
## Roadmap
|
|
341
364
|
|
|
342
365
|
- [x] **Step 1: Make it work**
|
|
@@ -361,6 +384,7 @@ Your favorite editor is not listed? Feel free to contribute an extension/plugin
|
|
|
361
384
|
- [ ] **Step 2: Make it nice**
|
|
362
385
|
- [x] improved errors with super helpful error messages and locations throughout the entire stack, possibly custom rendered error pages
|
|
363
386
|
- [x] `**attribute` splats for html tags, components and slots
|
|
387
|
+
- [x] `**{expression}` splats for html tags, components and slots
|
|
364
388
|
- [x] `:if` directive
|
|
365
389
|
- [x] `:for` directive
|
|
366
390
|
- [x] verbatim tags
|
|
@@ -391,12 +415,12 @@ Your favorite editor is not listed? Feel free to contribute an extension/plugin
|
|
|
391
415
|
- [ ] support additional block constructs
|
|
392
416
|
- [ ] support additional language constructs
|
|
393
417
|
|
|
394
|
-
|
|
395
418
|
> This library is in beta stage and demonstrates the technical aspects of a custom DSL for rendering ViewComponent objects in an HTML-like manner. It is meant as a kick-off point for further discussion on the definition and implementation of the template language. It may contain critical bugs that could compromise the security and integrity of your application. Additionally, the API and DSL are likely to change as the library evolves to a stable state. Don't say we didn't warn you!
|
|
396
419
|
|
|
397
420
|
## Development
|
|
398
421
|
|
|
399
422
|
To set up your development environment, follow these steps:
|
|
423
|
+
|
|
400
424
|
1. Clone the repository:
|
|
401
425
|
|
|
402
426
|
```bash
|
|
@@ -414,20 +438,18 @@ To set up your development environment, follow these steps:
|
|
|
414
438
|
|
|
415
439
|
```bash
|
|
416
440
|
make test
|
|
417
|
-
|
|
441
|
+
```
|
|
418
442
|
|
|
419
443
|
4. Start the development server for the test application:
|
|
420
444
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
445
|
+
```bash
|
|
446
|
+
bin/rails server
|
|
447
|
+
```
|
|
425
448
|
|
|
426
449
|
## Contributing
|
|
427
450
|
|
|
428
451
|
This project is intended to be a safe, welcoming space for collaboration. Contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct. We recommend reading the [contributing guide](./docs/CONTRIBUTING.md) as well.
|
|
429
452
|
|
|
430
|
-
|
|
431
453
|
## License
|
|
432
454
|
|
|
433
455
|
ORB is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/lib/orb/patterns.rb
CHANGED
data/lib/orb/rails_template.rb
CHANGED
|
@@ -39,7 +39,32 @@ module ORB
|
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
# Pipe through the ORB Temple engine
|
|
42
|
-
ORB::Temple::Engine.new(options).call(source)
|
|
42
|
+
code = ORB::Temple::Engine.new(options).call(source)
|
|
43
|
+
|
|
44
|
+
# Validate generated code with Prism to catch syntax errors BEFORE Rails does.
|
|
45
|
+
# This is a workaround for a Rails 8.1.1 bug where SyntaxErrorProxy fails
|
|
46
|
+
# is_a?(Exception) checks in ActiveSupport::ErrorReporter#report.
|
|
47
|
+
# See: rails-syntax-error-bug.md for details.
|
|
48
|
+
#
|
|
49
|
+
# Only run in development mode to avoid performance impact in production.
|
|
50
|
+
# In production, syntax errors will still be caught but with less friendly display.
|
|
51
|
+
if defined?(Prism) && defined?(Rails) && Rails.env.local?
|
|
52
|
+
result = Prism.parse(code)
|
|
53
|
+
if result.failure?
|
|
54
|
+
first_error = result.errors.first
|
|
55
|
+
error_line = first_error.location.start_line
|
|
56
|
+
message = first_error.message
|
|
57
|
+
|
|
58
|
+
# Return code that raises the error when executed.
|
|
59
|
+
# This way Rails' normal error handling will kick in, providing proper
|
|
60
|
+
# extracted source display and backtrace. We add newlines to position
|
|
61
|
+
# the error at the correct line number in the template.
|
|
62
|
+
escaped_message = message.gsub('\\', '\\\\\\\\').gsub("'", "\\\\'")
|
|
63
|
+
return "#{'\\n' * (error_line - 1)}raise ORB::CompilerError.new('#{escaped_message}', #{error_line})"
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
code
|
|
43
68
|
end
|
|
44
69
|
|
|
45
70
|
# See https://github.com/rails/rails/pull/47005
|
data/lib/orb/tokenizer2.rb
CHANGED
|
@@ -139,6 +139,7 @@ module ORB
|
|
|
139
139
|
end
|
|
140
140
|
|
|
141
141
|
# Read next token in :tag_open_content state
|
|
142
|
+
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
142
143
|
def next_in_tag_open_content
|
|
143
144
|
if @source.scan(NEWLINE) || @source.scan(CRLF) || @source.scan(BLANK)
|
|
144
145
|
move_by_matched
|
|
@@ -163,7 +164,13 @@ module ORB
|
|
|
163
164
|
transition_to(:initial)
|
|
164
165
|
elsif @source.scan(START_TAG_START)
|
|
165
166
|
syntax_error!("Unexpected start of tag")
|
|
166
|
-
elsif @source.scan(
|
|
167
|
+
elsif @source.scan(Patterns::SPLAT_EXPRESSION_START)
|
|
168
|
+
@attributes << [nil, :splat, nil]
|
|
169
|
+
clear_braces
|
|
170
|
+
clear_buffer
|
|
171
|
+
move_by_matched
|
|
172
|
+
transition_to(:splat_attribute_expression)
|
|
173
|
+
elsif @source.scan(Patterns::SPLAT_ATTRIBUTE)
|
|
167
174
|
splat = @source.matched
|
|
168
175
|
@attributes << [nil, :splat, splat]
|
|
169
176
|
move_by_matched
|
|
@@ -173,6 +180,7 @@ module ORB
|
|
|
173
180
|
syntax_error!("Unexpected '#{@source.peek(1)}'")
|
|
174
181
|
end
|
|
175
182
|
end
|
|
183
|
+
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
176
184
|
|
|
177
185
|
# Read next token in :attribute_name state
|
|
178
186
|
def next_in_attribute_name
|
|
@@ -275,6 +283,32 @@ module ORB
|
|
|
275
283
|
end
|
|
276
284
|
end
|
|
277
285
|
|
|
286
|
+
# Read next token in :splat_attribute_expression state
|
|
287
|
+
def next_in_splat_attribute_expression
|
|
288
|
+
if @source.scan(BRACE_OPEN)
|
|
289
|
+
@braces << "{"
|
|
290
|
+
buffer_matched
|
|
291
|
+
move_by_matched
|
|
292
|
+
elsif @source.scan(BRACE_CLOSE)
|
|
293
|
+
if @braces.any?
|
|
294
|
+
@braces.pop
|
|
295
|
+
buffer_matched
|
|
296
|
+
move_by_matched
|
|
297
|
+
else
|
|
298
|
+
splat_expression = consume_buffer
|
|
299
|
+
current_attribute[2] = "**#{splat_expression.strip}"
|
|
300
|
+
move_by_matched
|
|
301
|
+
clear_braces
|
|
302
|
+
transition_to(:tag_open_content)
|
|
303
|
+
end
|
|
304
|
+
elsif @source.scan(NEWLINE) || @source.scan(CRLF) || @source.scan(BLANK) || @source.scan(OTHER)
|
|
305
|
+
buffer_matched
|
|
306
|
+
move_by_matched
|
|
307
|
+
else
|
|
308
|
+
syntax_error!("Unexpected end of input while reading splat attribute value")
|
|
309
|
+
end
|
|
310
|
+
end
|
|
311
|
+
|
|
278
312
|
# Read next token in :attribute_value_unquoted state
|
|
279
313
|
def next_in_attribute_value_unquoted
|
|
280
314
|
if @source.scan(UNQUOTED_VALUE)
|
data/lib/orb/version.rb
CHANGED