rux-rails 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (89) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +18 -0
  3. data/LICENSE +21 -0
  4. data/README.md +303 -0
  5. data/Rakefile +14 -0
  6. data/lib/rux-rails.rb +55 -0
  7. data/lib/rux-rails/components.rb +5 -0
  8. data/lib/rux-rails/components/audio.rb +18 -0
  9. data/lib/rux-rails/components/image.rb +18 -0
  10. data/lib/rux-rails/components/video.rb +18 -0
  11. data/lib/rux-rails/core_ext/kernel.rb +67 -0
  12. data/lib/rux-rails/core_ext/kernel_zeitwerk.rb +63 -0
  13. data/lib/rux-rails/ext/activesupport/dependencies.rb +47 -0
  14. data/lib/rux-rails/ext/bootsnap/autoload.rb +27 -0
  15. data/lib/rux-rails/ext/zeitwerk/loader.rb +34 -0
  16. data/lib/rux-rails/railtie.rb +40 -0
  17. data/lib/rux-rails/tag_builder.rb +9 -0
  18. data/lib/rux-rails/tasks/transpile.rake +20 -0
  19. data/lib/rux-rails/template_handler.rb +10 -0
  20. data/lib/rux-rails/version.rb +3 -0
  21. data/lib/rux-rails/visitor.rb +9 -0
  22. data/rux-rails.gemspec +20 -0
  23. data/spec/controllers/home_controller_spec.rb +10 -0
  24. data/spec/dummy/app/assets/config/manifest.js +1 -0
  25. data/spec/dummy/app/assets/images/cat.png +0 -0
  26. data/spec/dummy/app/components/button.rb +15 -0
  27. data/spec/dummy/app/components/button.rux +13 -0
  28. data/spec/dummy/app/components/home_component.rb +12 -0
  29. data/spec/dummy/app/components/home_component.rux +10 -0
  30. data/spec/dummy/app/controllers/application_controller.rb +2 -0
  31. data/spec/dummy/app/controllers/home_controller.rb +4 -0
  32. data/spec/dummy/app/views/home/index.html.ruxt +1 -0
  33. data/spec/dummy/app/views/layouts/application.html.erb +1 -0
  34. data/spec/dummy/config/application.rb +11 -0
  35. data/spec/dummy/config/routes.rb +3 -0
  36. data/spec/dummy/config/secrets.yml +2 -0
  37. data/spec/dummy/log/test.log +2118 -0
  38. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/32/32pb_0TYQBmjctU3QYUyA67SPPU4r3O3GRrrvvGPmiE.cache +0 -0
  39. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/3K/3KjU_rFDGewKaqHawzGeJe2gEICBn5tcZo3sYuMFzIc.cache +1 -0
  40. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/5M/5Mgf_F6qKQefTdtCoiuuLF0AR8nP_GPs-u-PcTufYzs.cache +0 -0
  41. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/5x/5xP7khlwunCGtQQ9t_SuYBsA61JaFMr__1jYkIy98XY.cache +1 -0
  42. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/6m/6mYj-71y_0hKIq32e38vFCEt9LfF87vohAsWW2Wbg0M.cache +0 -0
  43. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/A1/A1cFLBRsUT1Kvkt38lHj0w-Z2vLB9AsYtkDX2XJjz0Q.cache +1 -0
  44. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/Cn/CnDEkVSwPDYPIPiJFYRSFLGqSCY_pkS_V7Dz-GfmlwY.cache +0 -0
  45. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/DK/DKD3m4yzw3TjiKBihS0UPyPMDs9kI8cp-SU3livdbLs.cache +0 -0
  46. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/IW/IWWGQkVdUgcmpscwHv4rrweteApumkA_mlohZc7x5MQ.cache +0 -0
  47. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/Ir/IrSasvUim90oUR7YrMBuLayhD0MPwEV58KF_CjMUn3Q.cache +0 -0
  48. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/LE/LEJsgTyA6opQbKQ_CTYaKZFGvfcrLz2HvBFYDOCJ1X8.cache +1 -0
  49. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/MH/MHOHDnE27pZhoY720dRuX3Aci9c3wdtzVFMck4l5I7k.cache +0 -0
  50. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/MI/MImCtRvR-FLuuVSxhJuPGQMGqiRd8YAbtuCF9_7hpYo.cache +1 -0
  51. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/Nf/NfmBq6JQlKu-xOZEvol2JVtefL8z2CovBlU7pB_62Cs.cache +1 -0
  52. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/Ng/NgcuRXtzOH2ayPjP4-mEy0TczGm-QQX8cJgFa9Q9V0g.cache +1 -0
  53. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/Ni/NiNyWknHu11HPZMGF6uzXEGm3uWR8IDRXxbdHaDb-B8.cache +0 -0
  54. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/O7/O7hfUP8Ab39Pr3RFJ-hjEQwzL5g5bV34jqrgfAHttPk.cache +1 -0
  55. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/Od/OdDm67gkecLUhzycAeoA1oGrrYEOHA7Pjh8yC008ZSE.cache +1 -0
  56. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/PN/PN84ONAmRFDLuRDczB_bZ_J5bOGZ_wnS3wbUHCwQL6I.cache +1 -0
  57. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/Qi/QihD5ZjyFdxwcO4hS2LO_CKB5sER91fZwGdG3l90ibY.cache +1 -0
  58. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/Qp/Qp3zeUqqFCCB7ftjQceTBcEblRXGptQj1Il_RXc0LGo.cache +0 -0
  59. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/RQ/RQEUV9iD5r_MyVa8CQDjmCLLYJxEs2vq6gy3MhdpRc4.cache +1 -0
  60. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/Sm/Sm1vmN88CL-bAk2LKuGzKuO05wEDo-UGe7OzGjGSVhM.cache +0 -0
  61. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/Uw/UwbxWHbEmhghk4TR5gC2s8Lv4JHDh2JG9xiqVYhXXsU.cache +1 -0
  62. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/X0/X0Olhu8L-YHuCt7-6IDs85GOR1Qjw2uQenbBT_JvdI4.cache +1 -0
  63. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/Yd/YdUPIlAOT8anTOlcrIkX7LCq7pVmacbmzb71PRmiyuo.cache +1 -0
  64. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/Z-/Z-GpjYZu9P-kHgu4EP2mWfIJpBbLQXo2K84rYUvfbzE.cache +0 -0
  65. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/ZI/ZId3kgSFsO8e39IJkB-1aDJqOaX1MqIHuwW0iCkSLGI.cache +1 -0
  66. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/Zr/Zr0O2TWCAVE8nF3AgLKuhZMhTIPg1D3Z5xwULyBUZKU.cache +0 -0
  67. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/aA/aAjjvDUDBEAs-2nAL4LDASNpmJJ8uGxUB56Q-f3IC8c.cache +0 -0
  68. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/fH/fHqdxZg60xcEnxUyaQYIA6mkvBUILR025tWJoSwfVsY.cache +0 -0
  69. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/fK/fKmXQml02NUF4j7kJ6e4Zcp6TcIHj7NQxrHgy8BgbYQ.cache +1 -0
  70. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/fu/fuD9esQDi8ZNKnICDWYSwr4hudCvzlZdWzsSMlvmCwM.cache +1 -0
  71. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/h0/h08cse92uOenHyfLkUVYhzV1fnofHOSIjdOgbP5PJng.cache +1 -0
  72. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/hq/hqCrs7MASvB0XlwcwyBYRZsMlyUvV3-kEJo0fqWOUHQ.cache +1 -0
  73. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/kA/kA49hHnh_KnK-HMeoHgMxO1nN02-CP1noUR5_xXvRBQ.cache +0 -0
  74. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/kw/kwNFhW24nxXy_HfVKYdXJyRqhu7ljgl9wR2DcJUo-nA.cache +0 -0
  75. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/mz/mzI4PfMw958wA6D3gAWAUKuRNTqotuxUF_M7z5qvlDc.cache +0 -0
  76. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/n7/n7EZC0292NrKBtK_fDPAZy7iSQcAYYsu8SgtfGw42ck.cache +0 -0
  77. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/oW/oWyMglNdSdtQBhKBrh-uX5gKeT09QXsejojnLyDyF0w.cache +0 -0
  78. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/pb/pbPD_X8-SCVPyisI4GhmmMbvioVDWP0u9Jl-HoRfHrs.cache +0 -0
  79. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/rj/rjai7nOMMVVC5ReIlRqsGAMUHmOxWjpJmJ8kQQnuO7w.cache +1 -0
  80. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/tO/tOXcpquR94HvQx0bF2pphwzXcUxgaFx3e27nZqsa7pc.cache +0 -0
  81. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/vE/vEsbMAeGNdEnGc_YJFoitBHn5TOLarMYxpidS0J_Hy8.cache +1 -0
  82. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/wN/wNw4kAG0E_RrvLeFAizMmTp_0jh-HGw1ONLtRyw92z0.cache +0 -0
  83. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/wj/wj3J7_iMl77w2K5Vuffalm1YjAaVImB5RZqgZ5_3jjE.cache +0 -0
  84. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/x-/x-NRUZ7BdXDbL3MOxJkPGMIoA3vxIUBueucxvozhT1A.cache +0 -0
  85. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/xZ/xZ_GDDoVsf4lq9-Fjl6wT6g5hwkQw9NyamPPMGHeybY.cache +0 -0
  86. data/spec/dummy/tmp/cache/assets/sprockets/v4.0.0/z5/z5WcmxKsYsoeqz5mkpx6aF6m3Z9k3TBPIanyLqX9bos.cache +1 -0
  87. data/spec/dummy/tmp/development_secret.txt +1 -0
  88. data/spec/spec_helper.rb +25 -0
  89. 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,5 @@
1
+ module RuxRails
2
+ module Components
3
+ autoload :Image, 'rux-rails/components/image'
4
+ end
5
+ end
@@ -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