react_to_rails 0.1.0 → 0.2.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/README.md +7 -5
- data/approvals/pricing_component_test.rb +42 -0
- data/lib/react_to_rails/cli.rb +6 -2
- data/lib/react_to_rails/convert.rb +77 -24
- data/lib/react_to_rails/version.rb +1 -1
- metadata +4 -3
- /data/{examples → approvals}/pricing_component.html.erb +0 -0
- /data/{examples → approvals}/pricing_component.rb +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2fbb427b028dbd40b73febc052ca5305da89181f686e7d247f297bf27dae42d7
|
4
|
+
data.tar.gz: 83c00c172263ac594204cab088dd0ffc61d0700bbc157cd7667e07d859fcb2f5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c48d2b54304140d1b711f57f4690b80e5e53c3a1c5d1c5456d6546a2e2f719832b8f4768061e561aa59131dd73536590459e95ca9052937e051216d90ba9a994
|
7
|
+
data.tar.gz: 9851cae0047b631dd49e78146f777d7df5b5fa8b1c8912a934b7f222b4c3d7358f285a28d821430c7cfaec0dfb1ee71621faec8e6b66ddf639809abe9aed6231
|
data/README.md
CHANGED
@@ -4,17 +4,19 @@ This gem converts React components to Rails [View Components](https://viewcompon
|
|
4
4
|
|
5
5
|
## Background
|
6
6
|
|
7
|
-
I created this tool use
|
7
|
+
I created this tool for use with [Tailwind Plus](https://tailwindcss.com/plus/) (formerly Tailwind UI).
|
8
8
|
|
9
9
|
Tailwind Plus is a commercial product which provides high quality components for React and Vue.
|
10
10
|
|
11
|
-
There are also plain HTML versions but they are awkward to work with, e.g.:
|
11
|
+
There are also plain HTML versions but they are awkward to work with for Rails, e.g.:
|
12
12
|
|
13
13
|
- You need to add ERB `<%= %>` placeholders for any content you're passing in.
|
14
14
|
- The HTML is static so is missing any loops, conditionals, etc
|
15
15
|
- Icons are converted to verbose SVG markup
|
16
16
|
|
17
|
-
This tool generates a View Component based on the supplied React component which should be much more convenient for Rails.
|
17
|
+
This tool generates a View Component based on the supplied React component which should be much more convenient for Rails. It also generates a corresponding test.
|
18
|
+
|
19
|
+
It's likely that you'll need to still do _some_ work, but it should save a lot of time.
|
18
20
|
|
19
21
|
For more details of the conversion process, you can look at the [prompt](./lib/react_to_rails/convert.rb).
|
20
22
|
|
@@ -88,8 +90,8 @@ The tool will output a report, and generate the view component files:
|
|
88
90
|
|
89
91
|
And here is the generated code:
|
90
92
|
|
91
|
-
* [app/component/pricing_component.html.erb](./
|
92
|
-
* [app/components/pricing_component.rb](./
|
93
|
+
* [app/component/pricing_component.html.erb](./test/approvals/pricing_component.html.erb).
|
94
|
+
* [app/components/pricing_component.rb](./test/approvals/pricing_component.rb)
|
93
95
|
|
94
96
|
## Limitations
|
95
97
|
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class PricingComponentTest < ActiveSupport::TestCase
|
4
|
+
include ViewComponent::TestHelpers
|
5
|
+
|
6
|
+
test "it renders pricing tiers correctly" do
|
7
|
+
tiers = [
|
8
|
+
{
|
9
|
+
name: 'Hobby',
|
10
|
+
id: 'tier-hobby',
|
11
|
+
href: '#',
|
12
|
+
price_monthly: '$29',
|
13
|
+
description: "The perfect plan if you're just getting started with our product.",
|
14
|
+
features: ['25 products', 'Up to 10,000 subscribers', 'Advanced analytics', '24-hour support response time'],
|
15
|
+
featured: false
|
16
|
+
},
|
17
|
+
{
|
18
|
+
name: 'Enterprise',
|
19
|
+
id: 'tier-enterprise',
|
20
|
+
href: '#',
|
21
|
+
price_monthly: '$99',
|
22
|
+
description: 'Dedicated support and infrastructure for your company.',
|
23
|
+
features: [
|
24
|
+
'Unlimited products',
|
25
|
+
'Unlimited subscribers',
|
26
|
+
'Advanced analytics',
|
27
|
+
'Dedicated support representative',
|
28
|
+
'Marketing automations',
|
29
|
+
'Custom integrations'
|
30
|
+
],
|
31
|
+
featured: true
|
32
|
+
}
|
33
|
+
]
|
34
|
+
render_inline(PricingComponent.new(tiers: tiers))
|
35
|
+
assert_selector "h2", text: "Pricing"
|
36
|
+
assert_selector "h3", text: "Hobby"
|
37
|
+
assert_selector "h3", text: "Enterprise"
|
38
|
+
assert_selector "span", text: "$29"
|
39
|
+
assert_selector "span", text: "$99"
|
40
|
+
assert_selector "a", text: "Get started today"
|
41
|
+
end
|
42
|
+
end
|
data/lib/react_to_rails/cli.rb
CHANGED
@@ -22,8 +22,12 @@ module ReactToRails
|
|
22
22
|
exit 1
|
23
23
|
end
|
24
24
|
|
25
|
-
|
26
|
-
|
25
|
+
convert = ReactToRails::Convert.for_path(path, name: name)
|
26
|
+
convert.call
|
27
|
+
|
28
|
+
File.write("app/components/#{convert.component_file_name}.rb", convert.view_component_ruby_code)
|
29
|
+
File.write("app/components/#{convert.component_file_name}.html.erb", convert.erb_template)
|
30
|
+
File.write("test/components/#{convert.component_file_name}_test.rb", convert.view_component_test_ruby_code)
|
27
31
|
rescue OptionParser::InvalidOption => e
|
28
32
|
puts "Error: #{e.message}"
|
29
33
|
puts
|
@@ -8,19 +8,52 @@ module ReactToRails
|
|
8
8
|
required :summary, String, doc: "A summary of what was done"
|
9
9
|
required :erb_template, String
|
10
10
|
required :view_component_ruby_code, String
|
11
|
+
required :view_component_test_ruby_code, String
|
11
12
|
required :demo_erb_code, String
|
12
13
|
end
|
13
14
|
|
15
|
+
class Client
|
16
|
+
def initialize(openai_client: nil)
|
17
|
+
@openai_client = openai_client || OpenAI::Client.new
|
18
|
+
end
|
19
|
+
|
20
|
+
def call(prompt)
|
21
|
+
response = openai_client.responses.create(
|
22
|
+
model: :"gpt-4.1",
|
23
|
+
temperature: 0,
|
24
|
+
input: [
|
25
|
+
{role: "user", content: prompt}
|
26
|
+
],
|
27
|
+
text: StructuredResponse
|
28
|
+
)
|
29
|
+
|
30
|
+
JSON.parse(response.output.first.content.first.to_json).fetch("parsed")
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
attr_reader :openai_client
|
36
|
+
end
|
37
|
+
|
14
38
|
class Convert
|
15
|
-
|
39
|
+
attr_reader :react_content,
|
40
|
+
:client,
|
41
|
+
:name,
|
42
|
+
:summary,
|
43
|
+
:view_component_ruby_code,
|
44
|
+
:view_component_test_ruby_code,
|
45
|
+
:demo_erb_code,
|
46
|
+
:erb_template
|
47
|
+
|
48
|
+
def initialize(react_content, name:, client: nil)
|
16
49
|
@react_content = react_content
|
17
|
-
@client = client
|
18
50
|
@name = name
|
51
|
+
@client = client || Client.new
|
19
52
|
end
|
20
53
|
|
21
|
-
def self.for_path(path,
|
54
|
+
def self.for_path(path, name:)
|
22
55
|
react_content = File.read(path)
|
23
|
-
new(react_content,
|
56
|
+
new(react_content, name: name)
|
24
57
|
end
|
25
58
|
|
26
59
|
def call
|
@@ -30,34 +63,28 @@ module ReactToRails
|
|
30
63
|
puts
|
31
64
|
puts "Waiting for OpenAI to respond..."
|
32
65
|
|
33
|
-
response =
|
34
|
-
model: :"gpt-4.1",
|
35
|
-
temperature: 0,
|
36
|
-
input: [
|
37
|
-
{role: "user", content: prompt}
|
38
|
-
],
|
39
|
-
text: StructuredResponse
|
40
|
-
)
|
41
|
-
|
42
|
-
puts "### RESPONSE ###"
|
43
|
-
|
44
|
-
response = JSON.parse(response.output.first.content.first.to_json).fetch("parsed")
|
66
|
+
response = Client.new.call(prompt)
|
45
67
|
|
46
68
|
@summary = response.fetch("summary")
|
47
69
|
@view_component_ruby_code = response.fetch("view_component_ruby_code")
|
48
70
|
@erb_template = response.fetch("erb_template")
|
49
71
|
@demo_erb_code = response.fetch("demo_erb_code")
|
72
|
+
@view_component_test_ruby_code = response.fetch("view_component_test_ruby_code")
|
50
73
|
|
51
|
-
component_file_name = @name.gsub(/([a-z\d])([A-Z])/, '\1_\2').downcase
|
52
|
-
File.write("app/components/#{component_file_name}.rb", @view_component_ruby_code)
|
53
|
-
File.write("app/components/#{component_file_name}.html.erb", @erb_template)
|
54
74
|
puts @summary
|
75
|
+
|
76
|
+
return if @erb_template == "" || @view_component_ruby_code == ""
|
77
|
+
|
55
78
|
puts
|
56
79
|
puts "### EXAMPLE USAGE ###"
|
57
80
|
puts
|
58
81
|
puts @demo_erb_code
|
59
82
|
end
|
60
83
|
|
84
|
+
def component_file_name
|
85
|
+
@name.gsub(/([a-z\d])([A-Z])/, '\1_\2').downcase
|
86
|
+
end
|
87
|
+
|
61
88
|
private
|
62
89
|
|
63
90
|
def prompt
|
@@ -98,6 +125,32 @@ module ReactToRails
|
|
98
125
|
<%= render(MessageComponent.new(name: name)) %>
|
99
126
|
```
|
100
127
|
|
128
|
+
Also generate a basic component test, for example:
|
129
|
+
|
130
|
+
```
|
131
|
+
require "test_helper"
|
132
|
+
|
133
|
+
class ExampleComponent < ActiveSupport::TestCase
|
134
|
+
include ViewComponent::TestHelpers
|
135
|
+
|
136
|
+
test "it renders correctly" do
|
137
|
+
title = "Component title"
|
138
|
+
description = "Component description"
|
139
|
+
people = [
|
140
|
+
{ name: 'Andy', title: 'Developer' }
|
141
|
+
]
|
142
|
+
example_component = ExampleComponent.new(title: title, description: description, people: people)
|
143
|
+
|
144
|
+
render_inline(example_component)
|
145
|
+
|
146
|
+
assert_selector "h1", text: title
|
147
|
+
assert_selector "p", text: description
|
148
|
+
assert_selector "td", text: people[0][:name]
|
149
|
+
assert_selector "td", text: people[0][:title]
|
150
|
+
end
|
151
|
+
end
|
152
|
+
```
|
153
|
+
|
101
154
|
Notes:
|
102
155
|
|
103
156
|
* If the code contains components from `@headlessui/react' then explain that is not supported and don't attempt to convert.
|
@@ -110,17 +163,17 @@ module ReactToRails
|
|
110
163
|
* Replace `<button>` tags with Rails `buton_tag` helper calls.
|
111
164
|
* Replace `onClick` events with theoretical Stimulus calls.
|
112
165
|
* Assume a heroicons helper is available, e.g.: <%= heroicon "magnifying-glass", options: { class: "text-primary-500" } %>
|
113
|
-
*
|
114
|
-
* If the component relies on
|
166
|
+
* Assume a `class_names` helper is available which works just like React's `classNames` helper.
|
167
|
+
* If the component relies on client-side behaviour, suggest to use Stimulus or Turbo instead. Also provide https://github.com/excid3/tailwindcss-stimulus-components as a reference.
|
115
168
|
* If the code constains instructions such as "This example requires updating your template" then report those details.
|
116
|
-
*
|
169
|
+
* Don't add HTML comment that weren't in the original.
|
170
|
+
* Let the user know if any parts were not possible to accurately convert.
|
171
|
+
* If `html_safe` is used then warn the user to check for potential security issues.
|
117
172
|
|
118
173
|
```
|
119
174
|
#{react_content}
|
120
175
|
```
|
121
176
|
EOS
|
122
177
|
end
|
123
|
-
|
124
|
-
attr_reader :react_content, :client, :name, :summary, :view_component_ruby_code, :demo_erb_code, :erb_template
|
125
178
|
end
|
126
179
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: react_to_rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andy Waite
|
@@ -48,9 +48,10 @@ files:
|
|
48
48
|
- LICENSE.txt
|
49
49
|
- README.md
|
50
50
|
- Rakefile
|
51
|
+
- approvals/pricing_component.html.erb
|
52
|
+
- approvals/pricing_component.rb
|
53
|
+
- approvals/pricing_component_test.rb
|
51
54
|
- examples/pricing.jsx
|
52
|
-
- examples/pricing_component.html.erb
|
53
|
-
- examples/pricing_component.rb
|
54
55
|
- exe/react_to_rails
|
55
56
|
- lib/react_to_rails.rb
|
56
57
|
- lib/react_to_rails/cli.rb
|
File without changes
|
File without changes
|