rux-rails 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +18 -0
- data/LICENSE +21 -0
- data/README.md +303 -0
- data/Rakefile +14 -0
- data/lib/rux-rails.rb +55 -0
- data/lib/rux-rails/components.rb +5 -0
- data/lib/rux-rails/components/audio.rb +18 -0
- data/lib/rux-rails/components/image.rb +18 -0
- data/lib/rux-rails/components/video.rb +18 -0
- data/lib/rux-rails/core_ext/kernel.rb +67 -0
- data/lib/rux-rails/core_ext/kernel_zeitwerk.rb +63 -0
- data/lib/rux-rails/ext/activesupport/dependencies.rb +47 -0
- data/lib/rux-rails/ext/bootsnap/autoload.rb +27 -0
- data/lib/rux-rails/ext/zeitwerk/loader.rb +34 -0
- data/lib/rux-rails/railtie.rb +40 -0
- data/lib/rux-rails/tag_builder.rb +9 -0
- data/lib/rux-rails/tasks/transpile.rake +20 -0
- data/lib/rux-rails/template_handler.rb +10 -0
- data/lib/rux-rails/version.rb +3 -0
- data/lib/rux-rails/visitor.rb +9 -0
- data/rux-rails.gemspec +20 -0
- data/spec/controllers/home_controller_spec.rb +10 -0
- data/spec/dummy/app/assets/config/manifest.js +1 -0
- data/spec/dummy/app/assets/images/cat.png +0 -0
- data/spec/dummy/app/components/button.rb +15 -0
- data/spec/dummy/app/components/button.rux +13 -0
- data/spec/dummy/app/components/home_component.rb +12 -0
- data/spec/dummy/app/components/home_component.rux +10 -0
- data/spec/dummy/app/controllers/application_controller.rb +2 -0
- data/spec/dummy/app/controllers/home_controller.rb +4 -0
- data/spec/dummy/app/views/home/index.html.ruxt +1 -0
- data/spec/dummy/app/views/layouts/application.html.erb +1 -0
- data/spec/dummy/config/application.rb +11 -0
- data/spec/dummy/config/routes.rb +3 -0
- data/spec/dummy/config/secrets.yml +2 -0
- data/spec/dummy/log/test.log +2118 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/32/32pb_0TYQBmjctU3QYUyA67SPPU4r3O3GRrrvvGPmiE.cache +0 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/3K/3KjU_rFDGewKaqHawzGeJe2gEICBn5tcZo3sYuMFzIc.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/5M/5Mgf_F6qKQefTdtCoiuuLF0AR8nP_GPs-u-PcTufYzs.cache +0 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/5x/5xP7khlwunCGtQQ9t_SuYBsA61JaFMr__1jYkIy98XY.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/6m/6mYj-71y_0hKIq32e38vFCEt9LfF87vohAsWW2Wbg0M.cache +0 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/A1/A1cFLBRsUT1Kvkt38lHj0w-Z2vLB9AsYtkDX2XJjz0Q.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/Cn/CnDEkVSwPDYPIPiJFYRSFLGqSCY_pkS_V7Dz-GfmlwY.cache +0 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/DK/DKD3m4yzw3TjiKBihS0UPyPMDs9kI8cp-SU3livdbLs.cache +0 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/IW/IWWGQkVdUgcmpscwHv4rrweteApumkA_mlohZc7x5MQ.cache +0 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/Ir/IrSasvUim90oUR7YrMBuLayhD0MPwEV58KF_CjMUn3Q.cache +0 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/LE/LEJsgTyA6opQbKQ_CTYaKZFGvfcrLz2HvBFYDOCJ1X8.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/MH/MHOHDnE27pZhoY720dRuX3Aci9c3wdtzVFMck4l5I7k.cache +0 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/MI/MImCtRvR-FLuuVSxhJuPGQMGqiRd8YAbtuCF9_7hpYo.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/Nf/NfmBq6JQlKu-xOZEvol2JVtefL8z2CovBlU7pB_62Cs.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/Ng/NgcuRXtzOH2ayPjP4-mEy0TczGm-QQX8cJgFa9Q9V0g.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/Ni/NiNyWknHu11HPZMGF6uzXEGm3uWR8IDRXxbdHaDb-B8.cache +0 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/O7/O7hfUP8Ab39Pr3RFJ-hjEQwzL5g5bV34jqrgfAHttPk.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/Od/OdDm67gkecLUhzycAeoA1oGrrYEOHA7Pjh8yC008ZSE.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/PN/PN84ONAmRFDLuRDczB_bZ_J5bOGZ_wnS3wbUHCwQL6I.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/Qi/QihD5ZjyFdxwcO4hS2LO_CKB5sER91fZwGdG3l90ibY.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/Qp/Qp3zeUqqFCCB7ftjQceTBcEblRXGptQj1Il_RXc0LGo.cache +0 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/RQ/RQEUV9iD5r_MyVa8CQDjmCLLYJxEs2vq6gy3MhdpRc4.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/Sm/Sm1vmN88CL-bAk2LKuGzKuO05wEDo-UGe7OzGjGSVhM.cache +0 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/Uw/UwbxWHbEmhghk4TR5gC2s8Lv4JHDh2JG9xiqVYhXXsU.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/X0/X0Olhu8L-YHuCt7-6IDs85GOR1Qjw2uQenbBT_JvdI4.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/Yd/YdUPIlAOT8anTOlcrIkX7LCq7pVmacbmzb71PRmiyuo.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/Z-/Z-GpjYZu9P-kHgu4EP2mWfIJpBbLQXo2K84rYUvfbzE.cache +0 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/ZI/ZId3kgSFsO8e39IJkB-1aDJqOaX1MqIHuwW0iCkSLGI.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/Zr/Zr0O2TWCAVE8nF3AgLKuhZMhTIPg1D3Z5xwULyBUZKU.cache +0 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/aA/aAjjvDUDBEAs-2nAL4LDASNpmJJ8uGxUB56Q-f3IC8c.cache +0 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/fH/fHqdxZg60xcEnxUyaQYIA6mkvBUILR025tWJoSwfVsY.cache +0 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/fK/fKmXQml02NUF4j7kJ6e4Zcp6TcIHj7NQxrHgy8BgbYQ.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/fu/fuD9esQDi8ZNKnICDWYSwr4hudCvzlZdWzsSMlvmCwM.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/h0/h08cse92uOenHyfLkUVYhzV1fnofHOSIjdOgbP5PJng.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/hq/hqCrs7MASvB0XlwcwyBYRZsMlyUvV3-kEJo0fqWOUHQ.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/kA/kA49hHnh_KnK-HMeoHgMxO1nN02-CP1noUR5_xXvRBQ.cache +0 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/kw/kwNFhW24nxXy_HfVKYdXJyRqhu7ljgl9wR2DcJUo-nA.cache +0 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/mz/mzI4PfMw958wA6D3gAWAUKuRNTqotuxUF_M7z5qvlDc.cache +0 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/n7/n7EZC0292NrKBtK_fDPAZy7iSQcAYYsu8SgtfGw42ck.cache +0 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/oW/oWyMglNdSdtQBhKBrh-uX5gKeT09QXsejojnLyDyF0w.cache +0 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/pb/pbPD_X8-SCVPyisI4GhmmMbvioVDWP0u9Jl-HoRfHrs.cache +0 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/rj/rjai7nOMMVVC5ReIlRqsGAMUHmOxWjpJmJ8kQQnuO7w.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/tO/tOXcpquR94HvQx0bF2pphwzXcUxgaFx3e27nZqsa7pc.cache +0 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/vE/vEsbMAeGNdEnGc_YJFoitBHn5TOLarMYxpidS0J_Hy8.cache +1 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/wN/wNw4kAG0E_RrvLeFAizMmTp_0jh-HGw1ONLtRyw92z0.cache +0 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/wj/wj3J7_iMl77w2K5Vuffalm1YjAaVImB5RZqgZ5_3jjE.cache +0 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/x-/x-NRUZ7BdXDbL3MOxJkPGMIoA3vxIUBueucxvozhT1A.cache +0 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/xZ/xZ_GDDoVsf4lq9-Fjl6wT6g5hwkQw9NyamPPMGHeybY.cache +0 -0
- data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/z5/z5WcmxKsYsoeqz5mkpx6aF6m3Z9k3TBPIanyLqX9bos.cache +1 -0
- data/spec/dummy/tmp/development_secret.txt +1 -0
- data/spec/spec_helper.rb +25 -0
- metadata +171 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: eeccc1d46691172273909d2ac405d00ea46b40a0392b1e68eec1bbdc08b4f288
|
4
|
+
data.tar.gz: c5ede37123f7a3ea9aa0b46924a97f83704377110b5fef70b70b57074135e482
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: cb8a54563f48615528db9d08aa3275d72dd932a91f4f62df80d5b7a70f4c00eac0b2f488f41497be90e6c7e3c06c1ce69d3e6be5e15c41d4519b846c7c7f20b5
|
7
|
+
data.tar.gz: f16f0a4456ee9d6d503e248da29f2cee8f476dde3acb339ab93ae6ddf31e44ebbf9ea56964c46edad685e616c9333561b126eb38fbcf9f42ab0d313bd25b1e76
|
data/Gemfile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
gemspec
|
4
|
+
|
5
|
+
gem 'rux', github: 'camertron/rux' # path: '/Users/cdutro/workspace/rux'
|
6
|
+
|
7
|
+
group :development, :test do
|
8
|
+
gem 'pry-byebug'
|
9
|
+
gem 'rake'
|
10
|
+
end
|
11
|
+
|
12
|
+
group :development do
|
13
|
+
gem 'appraisal'
|
14
|
+
end
|
15
|
+
|
16
|
+
group :test do
|
17
|
+
gem 'rspec-rails'
|
18
|
+
end
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2021 Cameron Dutro
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,303 @@
|
|
1
|
+
## rux-rails [![Build Status](https://travis-ci.com/camertron/rux-rails.svg?branch=master)](https://travis-ci.com/camertron/rux-rails)
|
2
|
+
|
3
|
+
Easily write [rux](https://github.com/camertron/rux) view components in Rails.
|
4
|
+
|
5
|
+
## View Components
|
6
|
+
|
7
|
+
If you haven't already, head over to [viewcomponent.org](https://viewcomponent.org/) or watch Joel Hawksley's excellent 2019 [Railsconf talk](https://www.youtube.com/watch?v=y5Z5a6QdA-M) on writing view components before reading the rest of this README.
|
8
|
+
|
9
|
+
## What's Rux?
|
10
|
+
|
11
|
+
View components are awesome, but they're a little cumbersome to work with. The view itself is a separate file, and you have to write a bunch of `render` statements all over the place. What if you could write HTML directly inside your view components like some fancy Javascript dev?
|
12
|
+
|
13
|
+
Well now you can! [Rux](https://github.com/camertron/rux) makes it possible to write HTML tags and render components _inside_ your view component classes, and rux-rails brings that goodness to Rails.
|
14
|
+
|
15
|
+
## An Example
|
16
|
+
|
17
|
+
Let's take a look at one iteration of Joel's issue badge example. The original code looks like this:
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
module Issues
|
21
|
+
class Badge < ViewComponent::Base
|
22
|
+
include OcticonsHelper
|
23
|
+
|
24
|
+
def initialize(issue:)
|
25
|
+
@issue = issue
|
26
|
+
end
|
27
|
+
|
28
|
+
def template
|
29
|
+
<<~erb
|
30
|
+
<% if @issue.closed? %>
|
31
|
+
<%= render Primer::State, color: :red, title: "Status: Closed" do %>
|
32
|
+
<%= octicon('issue-closed') %> Closed
|
33
|
+
<% end %>
|
34
|
+
<% else %>
|
35
|
+
<%= render Primer::State, color: :green, title: "Status: Open" do %>
|
36
|
+
<%= octicon('issue-opened') Open %>
|
37
|
+
<% end %>
|
38
|
+
<% end %>
|
39
|
+
erb
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
```
|
44
|
+
|
45
|
+
Here's the equivalent written in rux (sorry about the syntax highlighting - Github doesn't know about rux yet):
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
module Issues
|
49
|
+
class Badge < ViewComponent::Base
|
50
|
+
include OcticonsHelper
|
51
|
+
|
52
|
+
def initialize(issue:)
|
53
|
+
@issue = issue
|
54
|
+
end
|
55
|
+
|
56
|
+
def call
|
57
|
+
if @issue.closed?
|
58
|
+
<Primer::State color={:red} title="Status: Closed">
|
59
|
+
{octicon('issue-closed')} Closed
|
60
|
+
</Primer::State>
|
61
|
+
else
|
62
|
+
<Primer::State color={:green}, title="Status: Open">
|
63
|
+
{octicon('issue-opened')} Open
|
64
|
+
</Primer::State>
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
```
|
70
|
+
|
71
|
+
In my humble opinion, the rux version:
|
72
|
+
|
73
|
+
1. is easier to read without all the ERB syntax.
|
74
|
+
1. makes the connection between Ruby and HTML more obvious.
|
75
|
+
1. allows embedding Ruby more naturally with curly braces.
|
76
|
+
1. makes `render` calls implicit.
|
77
|
+
|
78
|
+
## Getting Started
|
79
|
+
|
80
|
+
Integrating rux into your Rails app is pretty straightforward.
|
81
|
+
|
82
|
+
1. Add rux-rails to the development group in your Gemfile, eg:
|
83
|
+
```ruby
|
84
|
+
group :development do
|
85
|
+
gem 'rux-rails', '~> 1.0'
|
86
|
+
end
|
87
|
+
```
|
88
|
+
1. Run `bundle install`
|
89
|
+
1. ... that's it.
|
90
|
+
|
91
|
+
Now any file with a .rux extension your Rails app loads (i.e. `require`s) will get transparently transpiled and loaded.
|
92
|
+
|
93
|
+
### Development Environment
|
94
|
+
|
95
|
+
By default, rux-rails will automatically transpile .rux files on load in the development and test environments only. In addition, any changes you make to your .rux files will get picked up without having to restart your Rails server, much the same way changes to controllers, etc are handled. You can manually enable or disable automatic transpilation per-environment.
|
96
|
+
|
97
|
+
For example, to disable it in development, add the following line to your config/environments/development.rb file:
|
98
|
+
|
99
|
+
```ruby
|
100
|
+
config.rux.transpile = false
|
101
|
+
```
|
102
|
+
|
103
|
+
### Production Environment
|
104
|
+
|
105
|
+
By including rux-rails in the `:development` group in your Gemfile, rux-rails and its monkeypatches to `Kernel` aren't loaded in production (which is a good thing). You could choose to include rux-rails in the default group, but automatic transpilation of .rux files is disabled in the production environment for the same reasons the asset pipeline is disabled. Your view components (and static assets) don't change once deployed, so it's more efficient to precompile (or pre-transpile) them before deploying. Rux-rails comes with a handy rake task that can pre-transpile all your .rux templates:
|
106
|
+
|
107
|
+
```bash
|
108
|
+
bundle exec rake rux:transpile
|
109
|
+
```
|
110
|
+
|
111
|
+
I recommend running this rake task at the same time you run `assets:precompile` and/or `webpacker:compile`. The `rux:transpile` task will produce one .rb file for every .rux file it encounters in your app.
|
112
|
+
|
113
|
+
## Writing Rux Components
|
114
|
+
|
115
|
+
Rux components are just view components that contain rux tags. As with HTML, rux tags can be nested inside one another and can contain Ruby control structures, etc.
|
116
|
+
|
117
|
+
Components usually live in the app/components directory. Just as is possible with models, controllers, etc, it's possible to organize your view components into subdirectories as your application design warrants.
|
118
|
+
|
119
|
+
Let's take a look at two simple components. The first renders a first and last name, while the second uses the first to compose a greeting:
|
120
|
+
|
121
|
+
```ruby
|
122
|
+
# app/components/name_component.rux
|
123
|
+
class NameComponent < ViewComponent::Base
|
124
|
+
def initialize(first_name:, last_name:)
|
125
|
+
@first_name = first_name
|
126
|
+
@last_name = last_name
|
127
|
+
end
|
128
|
+
|
129
|
+
def call
|
130
|
+
<span class='name'>{@first_name} {@last_name}</span>
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# app/components/greeting_component.rux
|
135
|
+
class GreetingComponent < ViewComponent::Base
|
136
|
+
def call
|
137
|
+
<div class='greeting'>
|
138
|
+
Hey there <NameComponent first-name="Homer" last-name="Simpson" />!
|
139
|
+
</div>
|
140
|
+
end
|
141
|
+
end
|
142
|
+
```
|
143
|
+
|
144
|
+
Then, in one of your Rails views, render `GreetingComponent` like so:
|
145
|
+
|
146
|
+
```html+erb
|
147
|
+
<%# app/views/home/index.html.erb %>
|
148
|
+
<%= render(GreetingComponent.new) %>
|
149
|
+
```
|
150
|
+
|
151
|
+
When rendered, the view will contain the following HTML:
|
152
|
+
|
153
|
+
```html
|
154
|
+
<div class="greeting">
|
155
|
+
Hey there <span class="name">Homer Simpson!</span>
|
156
|
+
</div>
|
157
|
+
```
|
158
|
+
|
159
|
+
### Component Contents
|
160
|
+
|
161
|
+
View components can also have content bodies, which can be other view components. Use the `content` method where you want the nested content to be rendered. As an example, let's modify our `GreetingComponent`:
|
162
|
+
|
163
|
+
|
164
|
+
```ruby
|
165
|
+
# app/components/greeting_component.rux
|
166
|
+
class GreetingComponent < ViewComponent::Base
|
167
|
+
def call
|
168
|
+
<div class='greeting'>
|
169
|
+
<SalutationComponent>
|
170
|
+
<NameComponent first-name="Homer" last-name="Simpson" />
|
171
|
+
</SalutationComponent>
|
172
|
+
</div>
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
# app/components/salutation_component.rux
|
177
|
+
class SalutationComponent < ViewComponent::Base
|
178
|
+
SALUTATIONS = ['Hey there', 'Greetings', 'Great to see you'].freeze
|
179
|
+
|
180
|
+
def call
|
181
|
+
<span class='salutation'>
|
182
|
+
{SALUTATIONS.sample} {content}!
|
183
|
+
</span>
|
184
|
+
end
|
185
|
+
end
|
186
|
+
```
|
187
|
+
|
188
|
+
When rendered in a view, we get the following HTML:
|
189
|
+
|
190
|
+
```html
|
191
|
+
<div class="greeting">
|
192
|
+
<span class="salutation">
|
193
|
+
Greetings <span class="name">Homer Simpson</span>!
|
194
|
+
</span>
|
195
|
+
</div>
|
196
|
+
```
|
197
|
+
|
198
|
+
### Embedding Ruby
|
199
|
+
|
200
|
+
As we've already seen, you can embed Ruby code between curly braces. It's important to know however that Ruby code is only allowed for attribute values and content bodies.
|
201
|
+
|
202
|
+
Because the wide world of Ruby is available to you, anything goes. For example, let's modify our `GreetingComponent` to say hi to a variable number of people:
|
203
|
+
|
204
|
+
```ruby
|
205
|
+
# app/components/greeting_component.rux
|
206
|
+
class GreetingComponent < ViewComponent::Base
|
207
|
+
def initialize(people:)
|
208
|
+
@people = people
|
209
|
+
end
|
210
|
+
|
211
|
+
def call
|
212
|
+
<div class='greeting'>
|
213
|
+
{@people.map do |person|
|
214
|
+
<SalutationComponent>
|
215
|
+
Hey there <NameComponent
|
216
|
+
first-name={person[:first_name]}
|
217
|
+
last-name={person[:last_name]}
|
218
|
+
/>!
|
219
|
+
</SalutationComponent>
|
220
|
+
end}
|
221
|
+
</div>
|
222
|
+
end
|
223
|
+
end
|
224
|
+
```
|
225
|
+
|
226
|
+
Notice I used `map` to render multiple `NameComponents`. I've got rux in my Ruby in my rux in my Ruby!
|
227
|
+
|
228
|
+
Next, let's modify our view to pass in an array of person hashes:
|
229
|
+
|
230
|
+
```html+erb
|
231
|
+
<%# app/views/home/index.html.erb %>
|
232
|
+
<%= render(
|
233
|
+
GreetingComponent.new([
|
234
|
+
{ first_name: 'Homer', last_name: 'Simpson' },
|
235
|
+
{ first_name: 'Barney', last_name: 'Gumble' },
|
236
|
+
{ first_name: 'Monty', last_name: 'Burns' }
|
237
|
+
])
|
238
|
+
) %>
|
239
|
+
```
|
240
|
+
|
241
|
+
This results in the following HTML:
|
242
|
+
|
243
|
+
```html
|
244
|
+
<div class="greeting">
|
245
|
+
<span class="salutation">
|
246
|
+
Greetings <span class="name">Homer Simpson</span>!
|
247
|
+
</span>
|
248
|
+
<span class="salutation">
|
249
|
+
Great to see you <span class="name">Barney Gumble</span>!
|
250
|
+
</span>
|
251
|
+
<span class="salutation">
|
252
|
+
Hey there <span class="name">Monty Burns</span>!
|
253
|
+
</span>
|
254
|
+
</div>
|
255
|
+
```
|
256
|
+
|
257
|
+
## Rux Templates
|
258
|
+
|
259
|
+
In addition to supporting view components, rux also supports rendering rux directly in your Rails views. Just give your view a .ruxt file extension and voila! Rux in your views! As an example, let's rewrite our view from the previous section as a rux template:
|
260
|
+
|
261
|
+
```ruby
|
262
|
+
# app/views/home/index.html.ruxt
|
263
|
+
<GreetingComponent
|
264
|
+
people={[
|
265
|
+
{ first_name: 'Homer', last_name: 'Simpson' },
|
266
|
+
{ first_name: 'Barney', last_name: 'Gumble' },
|
267
|
+
{ first_name: 'Monty', last_name: 'Burns' }
|
268
|
+
]}
|
269
|
+
/>
|
270
|
+
```
|
271
|
+
|
272
|
+
## How it Works
|
273
|
+
|
274
|
+
Rux-rails monkeypatches the `Kernel` module in order to automatically transpile .rux files on `require`. That might be a controversial idea, but it seems to work really well. Here's how it works step-by-step:
|
275
|
+
|
276
|
+
1. First, rux-rails attempts to call Ruby's original `require` method.
|
277
|
+
1. If original `require` raises a `LoadError`, rux-rails searches the Ruby load path for a file with a .rux extension.
|
278
|
+
1. If a corresponding .rux file exists on disk, rux-rails compiles it loads it using `Kernel.load`.
|
279
|
+
1. If a corresponding .rux file cannot be found, rux-rails raises the original `LoadError`.
|
280
|
+
|
281
|
+
There are also monkeypatches in place for Zeitwerk and `ActiveSupport::Dependencies` to get auto-transpiling working with Rails autoloading (which is absurdly obtuse and complicated). The monkeypatches are necessary mostly because Ruby and Rails assume Ruby files will always have .rb file extensions.
|
282
|
+
|
283
|
+
Hit me up if you know of a less invasive way of enabling auto-transpilation.
|
284
|
+
|
285
|
+
## Editor Support
|
286
|
+
|
287
|
+
Sublime Text: [https://github.com/camertron/rux-SublimeText](https://github.com/camertron/rux-SublimeText)
|
288
|
+
|
289
|
+
Atom: [https://github.com/camertron/rux-atom](https://github.com/camertron/rux-atom)
|
290
|
+
|
291
|
+
VSCode: [https://github.com/camertron/rux-vscode](https://github.com/camertron/rux-vscode)
|
292
|
+
|
293
|
+
## Running Tests
|
294
|
+
|
295
|
+
`bundle exec appraisal rake` should do the trick.
|
296
|
+
|
297
|
+
## License
|
298
|
+
|
299
|
+
Licensed under the MIT license. See LICENSE for details.
|
300
|
+
|
301
|
+
## Authors
|
302
|
+
|
303
|
+
* Cameron C. Dutro: http://github.com/camertron
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
require 'rspec/core/rake_task'
|
3
|
+
require 'rubygems/package_task'
|
4
|
+
|
5
|
+
require 'rux-rails'
|
6
|
+
|
7
|
+
Bundler::GemHelper.install_tasks
|
8
|
+
|
9
|
+
task default: :spec
|
10
|
+
|
11
|
+
desc 'Run specs'
|
12
|
+
RSpec::Core::RakeTask.new do |t|
|
13
|
+
t.pattern = './spec/**/*_spec.rb'
|
14
|
+
end
|
data/lib/rux-rails.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
module RuxRails
|
2
|
+
autoload :Components, 'rux-rails/components'
|
3
|
+
autoload :TagBuilder, 'rux-rails/tag_builder'
|
4
|
+
autoload :TemplateHandler, 'rux-rails/template_handler'
|
5
|
+
autoload :Visitor, 'rux-rails/visitor'
|
6
|
+
|
7
|
+
class << self
|
8
|
+
attr_accessor :zeitwerk_mode, :transpile_on_load
|
9
|
+
alias_method :zeitwerk_mode?, :zeitwerk_mode
|
10
|
+
|
11
|
+
def visitor
|
12
|
+
@visitor ||= Visitor.new
|
13
|
+
end
|
14
|
+
|
15
|
+
def tag_builder
|
16
|
+
@tag_builder ||= TagBuilder.new
|
17
|
+
end
|
18
|
+
|
19
|
+
def transpile_on_load?
|
20
|
+
transpile_on_load.call
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
self.transpile_on_load = -> () { true }
|
25
|
+
end
|
26
|
+
|
27
|
+
begin
|
28
|
+
require 'zeitwerk'
|
29
|
+
rescue LoadError
|
30
|
+
require 'rux-rails/core_ext/kernel'
|
31
|
+
require 'rux-rails/ext/activesupport/dependencies'
|
32
|
+
|
33
|
+
RuxRails.zeitwerk_mode = false
|
34
|
+
else
|
35
|
+
require 'rux-rails/core_ext/kernel_zeitwerk'
|
36
|
+
require 'rux-rails/ext/zeitwerk/loader'
|
37
|
+
|
38
|
+
RuxRails.zeitwerk_mode = true
|
39
|
+
end
|
40
|
+
|
41
|
+
begin
|
42
|
+
require 'bootsnap'
|
43
|
+
rescue LoadError
|
44
|
+
else
|
45
|
+
require 'rux-rails/ext/bootsnap/autoload'
|
46
|
+
end
|
47
|
+
|
48
|
+
require 'rux'
|
49
|
+
require 'rux-rails/railtie'
|
50
|
+
require 'view_component/engine'
|
51
|
+
require 'active_support'
|
52
|
+
|
53
|
+
ViewComponent::Base.send(:include, RuxRails::Components)
|
54
|
+
Rux.tag_builder = RuxRails.tag_builder
|
55
|
+
Rux.buffer = ActiveSupport::SafeBuffer
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module RuxRails
|
2
|
+
module Components
|
3
|
+
class Audio < ViewComponent::Base
|
4
|
+
include ActionView::Helpers::AssetUrlHelper
|
5
|
+
|
6
|
+
attr_reader :src, :params
|
7
|
+
|
8
|
+
def initialize(src:, **params)
|
9
|
+
@src = src
|
10
|
+
@params = params
|
11
|
+
end
|
12
|
+
|
13
|
+
def call
|
14
|
+
audio_tag(src, **params)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|