react_on_rails 17.0.0.rc.2 → 17.0.0.rc.3
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/Gemfile.lock +1 -1
- data/lib/generators/USAGE +6 -0
- data/lib/generators/react_on_rails/base_generator.rb +20 -2
- data/lib/generators/react_on_rails/generator_helper.rb +7 -0
- data/lib/generators/react_on_rails/install_generator.rb +58 -1
- data/lib/generators/react_on_rails/js_dependency_manager.rb +36 -0
- data/lib/generators/react_on_rails/react_no_redux_generator.rb +20 -8
- data/lib/generators/react_on_rails/react_with_redux_generator.rb +28 -4
- data/lib/generators/react_on_rails/rsc_generator.rb +5 -0
- data/lib/generators/react_on_rails/rsc_setup.rb +35 -1
- data/lib/generators/react_on_rails/templates/agent_files/.cursor/rules/react-on-rails.mdc +11 -0
- data/lib/generators/react_on_rails/templates/agent_files/.github/copilot-instructions.md +7 -0
- data/lib/generators/react_on_rails/templates/agent_files/AGENTS.md +164 -0
- data/lib/generators/react_on_rails/templates/agent_files/CLAUDE.md +8 -0
- data/lib/generators/react_on_rails/templates/base/base/config/webpack/commonWebpackConfig.js.tt +81 -1
- data/lib/generators/react_on_rails/templates/base/tailwind/app/javascript/src/HelloWorld/ror_components/HelloWorld.client.jsx +30 -0
- data/lib/generators/react_on_rails/templates/base/tailwind/app/javascript/src/HelloWorld/ror_components/HelloWorld.client.tsx +34 -0
- data/lib/generators/react_on_rails/templates/base/tailwind/app/javascript/stylesheets/application.css +1 -0
- data/lib/generators/react_on_rails/templates/redux/tailwind/app/javascript/bundles/HelloWorld/components/HelloWorld.jsx +25 -0
- data/lib/generators/react_on_rails/templates/redux/tailwind/app/javascript/bundles/HelloWorld/components/HelloWorld.tsx +29 -0
- data/lib/react_on_rails/doctor.rb +187 -24
- data/lib/react_on_rails/font_helper.rb +162 -0
- data/lib/react_on_rails/helper.rb +150 -0
- data/lib/react_on_rails/smart_error.rb +135 -5
- data/lib/react_on_rails/version.rb +1 -1
- data/lib/tasks/doctor.rake +5 -2
- data/sig/react_on_rails/helper.rbs +3 -0
- data/sig/react_on_rails/smart_error.rbs +25 -2
- metadata +11 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0630c2730fc7389d31e1f99d88e8df8dc6eb18b670e2909afd25e1e71b0c50ab
|
|
4
|
+
data.tar.gz: 6db462a802556e23085ae038585dd7c310c7710fd41a0a336d350383e85b9333
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 395e61f25e352b69df2788554dd0fcba85834df3e2f42bd768b6c2a81c8579e2d2504da1a47417abcc8ee5369181260c54f828e206f36dba409cbeef5a5818a9
|
|
7
|
+
data.tar.gz: 5ee8406c0ce296a4dca6e38299c1274fd2e5b6dde7f62f83f9790122dfc44cf88c0ca2cc093a9da7089265e53ed08ded28a018efc1929828c3a3ced000ba41c4
|
data/Gemfile.lock
CHANGED
data/lib/generators/USAGE
CHANGED
|
@@ -8,6 +8,12 @@ The react_on_rails:install generator integrates a React frontend, including SSR,
|
|
|
8
8
|
to integrate the Redux state container framework. The necessary node modules
|
|
9
9
|
will be automatically included for you.
|
|
10
10
|
|
|
11
|
+
* Tailwind CSS v4 (Optional)
|
|
12
|
+
|
|
13
|
+
Passing the --tailwind generator option installs Tailwind CSS v4, configures
|
|
14
|
+
the PostCSS plugin for Webpack or Rspack, and styles the generated
|
|
15
|
+
server-rendered Hello World example.
|
|
16
|
+
|
|
11
17
|
*******************************************************************************
|
|
12
18
|
|
|
13
19
|
After running the generator, you will want to:
|
|
@@ -41,6 +41,12 @@ module ReactOnRails
|
|
|
41
41
|
type: :boolean,
|
|
42
42
|
desc: "Use Webpack as the bundler (alias for --no-rspack; --no-webpack is equivalent to --rspack)"
|
|
43
43
|
|
|
44
|
+
# --tailwind
|
|
45
|
+
class_option :tailwind,
|
|
46
|
+
type: :boolean,
|
|
47
|
+
default: false,
|
|
48
|
+
desc: "Install Tailwind CSS v4 and style the generated SSR example"
|
|
49
|
+
|
|
44
50
|
# --pro
|
|
45
51
|
class_option :pro,
|
|
46
52
|
type: :boolean,
|
|
@@ -106,6 +112,10 @@ module ReactOnRails
|
|
|
106
112
|
generator.__send__(:use_rsc?)
|
|
107
113
|
end
|
|
108
114
|
|
|
115
|
+
def use_tailwind?
|
|
116
|
+
generator.__send__(:use_tailwind?)
|
|
117
|
+
end
|
|
118
|
+
|
|
109
119
|
def shakapacker_version_9_or_higher?
|
|
110
120
|
generator.__send__(:shakapacker_version_9_or_higher?)
|
|
111
121
|
end
|
|
@@ -228,7 +238,7 @@ module ReactOnRails
|
|
|
228
238
|
base_files = %w[app/javascript/packs/server-bundle.js]
|
|
229
239
|
|
|
230
240
|
# Skip HelloWorld CSS for Redux (uses HelloWorldApp) or RSC (uses HelloServer)
|
|
231
|
-
unless options.redux? || use_rsc?
|
|
241
|
+
unless options.redux? || use_rsc? || use_tailwind?
|
|
232
242
|
base_files << "app/javascript/src/HelloWorld/ror_components/HelloWorld.module.css"
|
|
233
243
|
end
|
|
234
244
|
|
|
@@ -259,6 +269,14 @@ module ReactOnRails
|
|
|
259
269
|
copy_webpack_main_config(base_path, config)
|
|
260
270
|
end
|
|
261
271
|
|
|
272
|
+
def copy_tailwind_files
|
|
273
|
+
return unless use_tailwind?
|
|
274
|
+
|
|
275
|
+
base_path = "base/tailwind/"
|
|
276
|
+
copy_file("#{base_path}app/javascript/stylesheets/application.css",
|
|
277
|
+
"app/javascript/stylesheets/application.css")
|
|
278
|
+
end
|
|
279
|
+
|
|
262
280
|
def copy_packer_config
|
|
263
281
|
base_path = "base/base/"
|
|
264
282
|
config = "config/shakapacker.yml"
|
|
@@ -970,7 +988,7 @@ module ReactOnRails
|
|
|
970
988
|
# Note: files originally generated with --pro or --rsc will not match when the
|
|
971
989
|
# current run omits those options; in that case, we preserve the directory.
|
|
972
990
|
# Templates rely on config[:message] plus a small helper subset exposed by
|
|
973
|
-
# TemplateRenderContext (add_documentation_reference, use_pro?, use_rsc?,
|
|
991
|
+
# TemplateRenderContext (add_documentation_reference, use_pro?, use_rsc?, use_tailwind?,
|
|
974
992
|
# shakapacker_version_9_or_higher?, rsc_plugin_class_name, rsc_plugin_import_path).
|
|
975
993
|
# Missing method delegates raise NoMethodError and are caught below, treating the
|
|
976
994
|
# file as non-removable.
|
|
@@ -121,6 +121,13 @@ module GeneratorHelper
|
|
|
121
121
|
options[:rsc]
|
|
122
122
|
end
|
|
123
123
|
|
|
124
|
+
# Check if Tailwind CSS should be installed and wired into the generated example.
|
|
125
|
+
#
|
|
126
|
+
# @return [Boolean] true if --tailwind is set
|
|
127
|
+
def use_tailwind?
|
|
128
|
+
options[:tailwind]
|
|
129
|
+
end
|
|
130
|
+
|
|
124
131
|
# Determine if the project is using rspack as the bundler.
|
|
125
132
|
#
|
|
126
133
|
# Detection priority:
|
|
@@ -43,6 +43,12 @@ module ReactOnRails
|
|
|
43
43
|
desc: "Generate TypeScript files and install TypeScript dependencies. Default: false",
|
|
44
44
|
aliases: "-T"
|
|
45
45
|
|
|
46
|
+
# --tailwind
|
|
47
|
+
class_option :tailwind,
|
|
48
|
+
type: :boolean,
|
|
49
|
+
default: false,
|
|
50
|
+
desc: "Install Tailwind CSS v4 and style the generated SSR example. Default: false"
|
|
51
|
+
|
|
46
52
|
# --rspack / --no-rspack (Rspack is the default on fresh installs; --no-rspack selects Webpack)
|
|
47
53
|
# IMPORTANT: do NOT add a `default:` here. The absence of a default is load-bearing — Thor
|
|
48
54
|
# only includes :rspack in the options hash when the flag is explicitly passed, which is how
|
|
@@ -66,6 +72,15 @@ module ReactOnRails
|
|
|
66
72
|
default: false,
|
|
67
73
|
desc: "Skip warnings. Default: false"
|
|
68
74
|
|
|
75
|
+
# --agent-files / --no-agent-files
|
|
76
|
+
# Emits consumer-scoped AI-agent guidance (AGENTS.md) plus thin editor pointer
|
|
77
|
+
# files (CLAUDE.md, .cursor/rules/react-on-rails.mdc, .github/copilot-instructions.md).
|
|
78
|
+
# Default ON; pass --no-agent-files to skip. Existing files are never overwritten.
|
|
79
|
+
class_option :agent_files,
|
|
80
|
+
type: :boolean,
|
|
81
|
+
default: true,
|
|
82
|
+
desc: "Write AI-agent guidance files (AGENTS.md + editor pointers). Default: true"
|
|
83
|
+
|
|
69
84
|
# --pro
|
|
70
85
|
class_option :pro,
|
|
71
86
|
type: :boolean,
|
|
@@ -183,6 +198,7 @@ module ReactOnRails
|
|
|
183
198
|
add_package_json_scripts
|
|
184
199
|
add_ci_workflow
|
|
185
200
|
add_bin_scripts
|
|
201
|
+
add_agent_files
|
|
186
202
|
add_post_install_message
|
|
187
203
|
else
|
|
188
204
|
error = <<~MSG.strip
|
|
@@ -227,7 +243,7 @@ module ReactOnRails
|
|
|
227
243
|
# --pretend/--force/--skip must be forwarded explicitly at each boundary.
|
|
228
244
|
invoke "react_on_rails:base", [],
|
|
229
245
|
{ typescript: options.typescript?, redux: options.redux?, rspack: using_rspack?,
|
|
230
|
-
pro: use_pro?, rsc: use_rsc?, new_app: options.new_app?,
|
|
246
|
+
pro: use_pro?, rsc: use_rsc?, tailwind: use_tailwind?, new_app: options.new_app?,
|
|
231
247
|
shakapacker_just_installed: shakapacker_just_installed?,
|
|
232
248
|
force: options[:force], skip: options[:skip], pretend: options[:pretend] }
|
|
233
249
|
|
|
@@ -237,6 +253,7 @@ module ReactOnRails
|
|
|
237
253
|
# - Without --rsc: Normal behavior (HelloWorld or HelloWorldApp based on --redux)
|
|
238
254
|
if options.redux?
|
|
239
255
|
invoke "react_on_rails:react_with_redux", [], { typescript: options.typescript?,
|
|
256
|
+
tailwind: use_tailwind?,
|
|
240
257
|
invoked_by_install: true,
|
|
241
258
|
new_app: options.new_app?,
|
|
242
259
|
rsc: use_rsc?,
|
|
@@ -246,6 +263,7 @@ module ReactOnRails
|
|
|
246
263
|
# Only generate HelloWorld if RSC is not enabled
|
|
247
264
|
# For RSC, HelloServer replaces HelloWorld as the example component
|
|
248
265
|
invoke "react_on_rails:react_no_redux", [], { typescript: options.typescript?,
|
|
266
|
+
tailwind: use_tailwind?,
|
|
249
267
|
new_app: options.new_app?,
|
|
250
268
|
force: options[:force], skip: options[:skip],
|
|
251
269
|
pretend: options[:pretend] }
|
|
@@ -265,6 +283,7 @@ module ReactOnRails
|
|
|
265
283
|
|
|
266
284
|
invoke "react_on_rails:rsc", [], { typescript: options.typescript?, invoked_by_install: true,
|
|
267
285
|
new_app: options.new_app?, redux: options.redux?,
|
|
286
|
+
tailwind: use_tailwind?,
|
|
268
287
|
force: options[:force], skip: options[:skip],
|
|
269
288
|
pretend: options[:pretend] }
|
|
270
289
|
end
|
|
@@ -622,6 +641,40 @@ module ReactOnRails
|
|
|
622
641
|
File.chmod(0o755, *files_to_become_executable)
|
|
623
642
|
end
|
|
624
643
|
|
|
644
|
+
# Consumer-scoped AI-agent guidance written into the generated app. The canonical
|
|
645
|
+
# AGENTS.md content lives in templates/agent_files/ and is the single source of truth;
|
|
646
|
+
# create-react-on-rails-app gets it for free because it delegates to this generator.
|
|
647
|
+
# Each file is copied only when absent so we never clobber an app's existing agent files.
|
|
648
|
+
AGENT_FILES = %w[
|
|
649
|
+
AGENTS.md
|
|
650
|
+
CLAUDE.md
|
|
651
|
+
.cursor/rules/react-on-rails.mdc
|
|
652
|
+
.github/copilot-instructions.md
|
|
653
|
+
].freeze
|
|
654
|
+
private_constant :AGENT_FILES
|
|
655
|
+
|
|
656
|
+
def add_agent_files
|
|
657
|
+
return unless options.agent_files?
|
|
658
|
+
|
|
659
|
+
# AGENTS.md is the canonical file the editor pointers (CLAUDE.md, Cursor, Copilot) all
|
|
660
|
+
# reference. If the app already has its own AGENTS.md, it may document unrelated
|
|
661
|
+
# conventions, so leave it untouched AND skip the pointer files rather than emit editor
|
|
662
|
+
# guidance pointing at an AGENTS.md we did not write.
|
|
663
|
+
if File.exist?(File.join(destination_root, "AGENTS.md"))
|
|
664
|
+
say_status :skip, "AGENTS.md already exists; leaving it and the editor pointer files untouched", :yellow
|
|
665
|
+
return
|
|
666
|
+
end
|
|
667
|
+
|
|
668
|
+
AGENT_FILES.each do |relative_path|
|
|
669
|
+
if File.exist?(File.join(destination_root, relative_path))
|
|
670
|
+
say_status :skip, "#{relative_path} already exists; leaving it untouched", :yellow
|
|
671
|
+
next
|
|
672
|
+
end
|
|
673
|
+
|
|
674
|
+
copy_file("templates/agent_files/#{relative_path}", relative_path)
|
|
675
|
+
end
|
|
676
|
+
end
|
|
677
|
+
|
|
625
678
|
def replace_stock_rails_bin_dev!
|
|
626
679
|
@preserve_existing_bin_dev = false
|
|
627
680
|
|
|
@@ -722,6 +775,10 @@ module ReactOnRails
|
|
|
722
775
|
flags << "--pro"
|
|
723
776
|
end
|
|
724
777
|
|
|
778
|
+
# Preserve an explicit agent-files opt-out so the suggested re-run doesn't emit
|
|
779
|
+
# AGENTS.md/editor files a user deliberately skipped (--agent-files defaults to on).
|
|
780
|
+
flags << "--no-agent-files" unless options.agent_files?
|
|
781
|
+
|
|
725
782
|
["rails generate react_on_rails:install", *flags].join(" ")
|
|
726
783
|
end
|
|
727
784
|
|
|
@@ -83,6 +83,15 @@ module ReactOnRails
|
|
|
83
83
|
style-loader@^4.0.0
|
|
84
84
|
].freeze
|
|
85
85
|
|
|
86
|
+
# Tailwind v4 CSS-first setup. The patch-level floors match published
|
|
87
|
+
# releases verified with the generator's SSR smoke app.
|
|
88
|
+
TAILWIND_DEPENDENCIES = %w[
|
|
89
|
+
tailwindcss@^4.3.0
|
|
90
|
+
@tailwindcss/postcss@^4.3.0
|
|
91
|
+
postcss@^8.5.15
|
|
92
|
+
postcss-loader@^8.2.1
|
|
93
|
+
].freeze
|
|
94
|
+
|
|
86
95
|
# Development-only dependencies for hot reloading (Webpack)
|
|
87
96
|
# Both packages are pre-1.0, so left bare (see pinning note above).
|
|
88
97
|
DEV_DEPENDENCIES = %w[
|
|
@@ -175,6 +184,7 @@ module ReactOnRails
|
|
|
175
184
|
add_react_on_rails_package unless using_pro
|
|
176
185
|
add_react_dependencies
|
|
177
186
|
add_css_dependencies
|
|
187
|
+
add_tailwind_dependencies_if_requested
|
|
178
188
|
add_rspack_dependencies if using_rspack?
|
|
179
189
|
add_transpiler_dependencies
|
|
180
190
|
add_pro_dependencies if using_pro
|
|
@@ -290,6 +300,32 @@ module ReactOnRails
|
|
|
290
300
|
MSG
|
|
291
301
|
end
|
|
292
302
|
|
|
303
|
+
def add_tailwind_dependencies
|
|
304
|
+
say "Installing Tailwind CSS v4 dependencies..."
|
|
305
|
+
return if add_packages(TAILWIND_DEPENDENCIES)
|
|
306
|
+
|
|
307
|
+
GeneratorMessages.add_warning(<<~MSG.strip)
|
|
308
|
+
⚠️ Failed to add Tailwind CSS dependencies.
|
|
309
|
+
|
|
310
|
+
You can install them manually by running:
|
|
311
|
+
#{manual_add_packages_command(TAILWIND_DEPENDENCIES)}
|
|
312
|
+
MSG
|
|
313
|
+
rescue StandardError => e
|
|
314
|
+
GeneratorMessages.add_warning(<<~MSG.strip)
|
|
315
|
+
⚠️ Error adding Tailwind CSS dependencies: #{e.message}
|
|
316
|
+
|
|
317
|
+
You can install them manually by running:
|
|
318
|
+
#{manual_add_packages_command(TAILWIND_DEPENDENCIES)}
|
|
319
|
+
MSG
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
def add_tailwind_dependencies_if_requested
|
|
323
|
+
# use_tailwind? is provided by GeneratorHelper, included alongside this module.
|
|
324
|
+
return unless use_tailwind?
|
|
325
|
+
|
|
326
|
+
add_tailwind_dependencies
|
|
327
|
+
end
|
|
328
|
+
|
|
293
329
|
def add_rspack_dependencies
|
|
294
330
|
say "Installing Rspack core dependencies..."
|
|
295
331
|
return if add_packages(RSPACK_DEPENDENCIES)
|
|
@@ -18,6 +18,11 @@ module ReactOnRails
|
|
|
18
18
|
default: false,
|
|
19
19
|
desc: "Generate TypeScript files"
|
|
20
20
|
|
|
21
|
+
class_option :tailwind,
|
|
22
|
+
type: :boolean,
|
|
23
|
+
default: false,
|
|
24
|
+
desc: "Style the generated HelloWorld example with Tailwind CSS v4"
|
|
25
|
+
|
|
21
26
|
class_option :new_app,
|
|
22
27
|
type: :boolean,
|
|
23
28
|
default: false,
|
|
@@ -25,17 +30,24 @@ module ReactOnRails
|
|
|
25
30
|
|
|
26
31
|
def copy_base_files
|
|
27
32
|
base_js_path = "base/base"
|
|
33
|
+
tailwind_js_path = "base/tailwind"
|
|
34
|
+
ext = component_extension(options)
|
|
28
35
|
|
|
29
36
|
# Determine which component files to copy based on TypeScript option
|
|
30
|
-
|
|
31
|
-
"app/javascript/src/HelloWorld/ror_components/HelloWorld.client.#{
|
|
32
|
-
|
|
33
|
-
"app/javascript/src/HelloWorld/ror_components/HelloWorld.
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
client_component =
|
|
38
|
+
"app/javascript/src/HelloWorld/ror_components/HelloWorld.client.#{ext}"
|
|
39
|
+
server_component =
|
|
40
|
+
"app/javascript/src/HelloWorld/ror_components/HelloWorld.server.#{ext}"
|
|
41
|
+
|
|
42
|
+
if use_tailwind?
|
|
43
|
+
copy_file("#{tailwind_js_path}/#{client_component}", client_component)
|
|
44
|
+
else
|
|
45
|
+
copy_file("#{base_js_path}/#{client_component}", client_component)
|
|
46
|
+
copy_file("#{base_js_path}/app/javascript/src/HelloWorld/ror_components/HelloWorld.module.css",
|
|
47
|
+
"app/javascript/src/HelloWorld/ror_components/HelloWorld.module.css")
|
|
38
48
|
end
|
|
49
|
+
|
|
50
|
+
copy_file("#{base_js_path}/#{server_component}", server_component)
|
|
39
51
|
end
|
|
40
52
|
|
|
41
53
|
def create_appropriate_templates
|
|
@@ -35,6 +35,11 @@ module ReactOnRails
|
|
|
35
35
|
default: false,
|
|
36
36
|
hide: true
|
|
37
37
|
|
|
38
|
+
class_option :tailwind,
|
|
39
|
+
type: :boolean,
|
|
40
|
+
default: false,
|
|
41
|
+
hide: true
|
|
42
|
+
|
|
38
43
|
def create_redux_directories
|
|
39
44
|
# Create auto-bundling directory structure for Redux
|
|
40
45
|
empty_directory("app/javascript/src/HelloWorldApp/ror_components")
|
|
@@ -53,18 +58,33 @@ module ReactOnRails
|
|
|
53
58
|
"app/javascript/src/HelloWorldApp/ror_components/HelloWorldApp.client.#{ext}")
|
|
54
59
|
copy_file("#{base_js_path}/app/javascript/bundles/HelloWorld/startup/HelloWorldApp.server.#{ext}",
|
|
55
60
|
"app/javascript/src/HelloWorldApp/ror_components/HelloWorldApp.server.#{ext}")
|
|
56
|
-
|
|
57
|
-
|
|
61
|
+
|
|
62
|
+
unless use_tailwind?
|
|
63
|
+
copy_file("#{base_js_path}/app/javascript/bundles/HelloWorld/components/HelloWorld.module.css",
|
|
64
|
+
"app/javascript/src/HelloWorldApp/components/HelloWorld.module.css")
|
|
65
|
+
end
|
|
58
66
|
|
|
59
67
|
# Update import paths in client component
|
|
60
68
|
ror_client_file = "app/javascript/src/HelloWorldApp/ror_components/HelloWorldApp.client.#{ext}"
|
|
61
69
|
gsub_file(ror_client_file, "../store/helloWorldStore", "../store/helloWorldStore")
|
|
62
70
|
gsub_file(ror_client_file, "../containers/HelloWorldContainer",
|
|
63
71
|
"../containers/HelloWorldContainer")
|
|
72
|
+
return unless use_tailwind?
|
|
73
|
+
|
|
74
|
+
stylesheet_import = "import '../../../stylesheets/application.css';\n"
|
|
75
|
+
ror_client_file_path = File.join(destination_root, ror_client_file)
|
|
76
|
+
if options[:pretend]
|
|
77
|
+
say_status :pretend, "Would add Tailwind stylesheet import to #{ror_client_file}", :yellow
|
|
78
|
+
return
|
|
79
|
+
end
|
|
80
|
+
return if File.read(ror_client_file_path).include?(stylesheet_import)
|
|
81
|
+
|
|
82
|
+
prepend_to_file(ror_client_file, stylesheet_import)
|
|
64
83
|
end
|
|
65
84
|
|
|
66
85
|
def copy_base_redux_files
|
|
67
86
|
base_hello_world_path = "redux/base/app/javascript/bundles/HelloWorld"
|
|
87
|
+
tailwind_hello_world_path = "redux/tailwind/app/javascript/bundles/HelloWorld"
|
|
68
88
|
redux_extension = options.typescript? ? "ts" : "js"
|
|
69
89
|
|
|
70
90
|
# Copy Redux infrastructure files with appropriate extension
|
|
@@ -72,11 +92,15 @@ module ReactOnRails
|
|
|
72
92
|
containers/HelloWorldContainer.#{redux_extension}
|
|
73
93
|
constants/helloWorldConstants.#{redux_extension}
|
|
74
94
|
reducers/helloWorldReducer.#{redux_extension}
|
|
75
|
-
store/helloWorldStore.#{redux_extension}
|
|
76
|
-
components/HelloWorld.#{component_extension(options)}].each do |file|
|
|
95
|
+
store/helloWorldStore.#{redux_extension}].each do |file|
|
|
77
96
|
copy_file("#{base_hello_world_path}/#{file}",
|
|
78
97
|
"app/javascript/src/HelloWorldApp/#{file}")
|
|
79
98
|
end
|
|
99
|
+
|
|
100
|
+
component_file = "components/HelloWorld.#{component_extension(options)}"
|
|
101
|
+
component_source_path = use_tailwind? ? tailwind_hello_world_path : base_hello_world_path
|
|
102
|
+
copy_file("#{component_source_path}/#{component_file}",
|
|
103
|
+
"app/javascript/src/HelloWorldApp/#{component_file}")
|
|
80
104
|
end
|
|
81
105
|
|
|
82
106
|
def create_appropriate_templates
|
|
@@ -42,6 +42,11 @@ module ReactOnRails
|
|
|
42
42
|
default: false,
|
|
43
43
|
hide: true
|
|
44
44
|
|
|
45
|
+
class_option :tailwind,
|
|
46
|
+
type: :boolean,
|
|
47
|
+
default: false,
|
|
48
|
+
hide: true
|
|
49
|
+
|
|
45
50
|
def run_generator
|
|
46
51
|
# When invoked by install_generator, skip prerequisites (parent already validated)
|
|
47
52
|
if options[:invoked_by_install] || prerequisites_met?
|
|
@@ -206,7 +206,13 @@ module ReactOnRails
|
|
|
206
206
|
# Check if HelloServer already exists (check both jsx and tsx)
|
|
207
207
|
if File.exist?(File.join(destination_root, "#{ror_components_dir}/HelloServer.jsx")) ||
|
|
208
208
|
File.exist?(File.join(destination_root, "#{ror_components_dir}/HelloServer.tsx"))
|
|
209
|
-
|
|
209
|
+
tailwind_import_added = add_tailwind_import_to_rsc_client_component(components_dir)
|
|
210
|
+
message = if tailwind_import_added
|
|
211
|
+
"ℹ️ HelloServer component already exists; added Tailwind stylesheet import to LikeButton"
|
|
212
|
+
else
|
|
213
|
+
"ℹ️ HelloServer component already exists, skipping"
|
|
214
|
+
end
|
|
215
|
+
say message, :yellow
|
|
210
216
|
return
|
|
211
217
|
end
|
|
212
218
|
|
|
@@ -223,10 +229,38 @@ module ReactOnRails
|
|
|
223
229
|
"#{components_dir}/HelloServer.#{ext}")
|
|
224
230
|
copy_file("templates/rsc/base/app/javascript/src/HelloServer/components/LikeButton.#{ext}",
|
|
225
231
|
"#{components_dir}/LikeButton.#{ext}")
|
|
232
|
+
add_tailwind_import_to_rsc_client_component(components_dir)
|
|
226
233
|
|
|
227
234
|
say "✅ Created HelloServer component", :green
|
|
228
235
|
end
|
|
229
236
|
|
|
237
|
+
def add_tailwind_import_to_rsc_client_component(components_dir)
|
|
238
|
+
return false unless use_tailwind?
|
|
239
|
+
|
|
240
|
+
candidate_entry_paths = %w[jsx tsx].map do |extension|
|
|
241
|
+
"#{components_dir}/LikeButton.#{extension}"
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
relative_entry_path = candidate_entry_paths.find do |entry_path|
|
|
245
|
+
File.exist?(File.join(destination_root, entry_path))
|
|
246
|
+
end
|
|
247
|
+
return false unless relative_entry_path
|
|
248
|
+
|
|
249
|
+
# Path is relative to app/javascript/src/HelloServer/components/.
|
|
250
|
+
stylesheet_import = "import '../../../stylesheets/application.css';"
|
|
251
|
+
entry_path = File.join(destination_root, relative_entry_path)
|
|
252
|
+
entry_content = File.read(entry_path)
|
|
253
|
+
return false if entry_content.include?(stylesheet_import)
|
|
254
|
+
|
|
255
|
+
client_directive_pattern = /\A\s*['"]use client['"];?[^\S\r\n]*(?:\r?\n|$)/
|
|
256
|
+
if entry_content.match?(client_directive_pattern)
|
|
257
|
+
insert_into_file(relative_entry_path, "#{stylesheet_import}\n", after: client_directive_pattern)
|
|
258
|
+
else
|
|
259
|
+
prepend_to_file(relative_entry_path, "#{stylesheet_import}\n")
|
|
260
|
+
end
|
|
261
|
+
true
|
|
262
|
+
end
|
|
263
|
+
|
|
230
264
|
def create_hello_server_controller
|
|
231
265
|
controller_path = "app/controllers/hello_server_controller.rb"
|
|
232
266
|
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: React on Rails conventions for this app (components, react_component helper, SSR bundles, errors)
|
|
3
|
+
globs:
|
|
4
|
+
alwaysApply: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
This app uses **React on Rails**. For React on Rails conventions — adding and
|
|
8
|
+
registering components, the `react_component` view helper, `.client`/`.server`
|
|
9
|
+
bundle rules, the `ReactOnRails` JS API, common errors and fixes, and the
|
|
10
|
+
`bin/rails react_on_rails:doctor` diagnostic — read **AGENTS.md** in the project
|
|
11
|
+
root.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# GitHub Copilot instructions
|
|
2
|
+
|
|
3
|
+
This app uses **React on Rails**. For React on Rails conventions — adding and
|
|
4
|
+
registering components, the `react_component` view helper, `.client`/`.server`
|
|
5
|
+
bundle rules, the `ReactOnRails` JS API, common errors and fixes, and the
|
|
6
|
+
`bin/rails react_on_rails:doctor` diagnostic — read **AGENTS.md** in the project
|
|
7
|
+
root.
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# AGENTS.md — React on Rails (for AI coding agents working IN this app)
|
|
2
|
+
|
|
3
|
+
This app uses **React on Rails** to render React components from Rails views, with
|
|
4
|
+
optional server-side rendering (SSR). This file teaches an AI agent the conventions
|
|
5
|
+
it needs to add, register, and render a component correctly on the first try.
|
|
6
|
+
|
|
7
|
+
It is intentionally short. For the full reference, see the links at the bottom.
|
|
8
|
+
|
|
9
|
+
> This file describes an app that **uses** React on Rails. It is not the
|
|
10
|
+
> contributor guide for the React on Rails framework itself.
|
|
11
|
+
|
|
12
|
+
## 1. Adding a component (auto-bundling — the default)
|
|
13
|
+
|
|
14
|
+
Place a component under any directory named `ror_components`. React on Rails
|
|
15
|
+
auto-bundles it, so **no manual registration is needed**:
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
app/javascript/src/<Name>/ror_components/<Name>.tsx # or .jsx
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
The component file **must `export default`** the React component. Example:
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
// app/javascript/src/SimpleCounter/ror_components/SimpleCounter.tsx
|
|
25
|
+
import React, { useState } from 'react';
|
|
26
|
+
|
|
27
|
+
const SimpleCounter = (props: { initialCount?: number }) => {
|
|
28
|
+
const [count, setCount] = useState(props.initialCount ?? 0);
|
|
29
|
+
return <button onClick={() => setCount((c) => c + 1)}>Count: {count}</button>;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export default SimpleCounter; // default export is required
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Manual registration (alternative)
|
|
36
|
+
|
|
37
|
+
If you are not using the `ror_components/` auto-bundling convention, register the
|
|
38
|
+
component explicitly from a pack/entry file, importing from the `react-on-rails`
|
|
39
|
+
npm package:
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
import ReactOnRails from 'react-on-rails';
|
|
43
|
+
import SimpleCounter from './SimpleCounter';
|
|
44
|
+
|
|
45
|
+
ReactOnRails.register({ SimpleCounter });
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The key (`SimpleCounter`) is the name you pass to `react_component` in the Rails view.
|
|
49
|
+
|
|
50
|
+
## 2. Rendering it from a Rails view
|
|
51
|
+
|
|
52
|
+
Use the `react_component` view helper in any `.html.erb` view (e.g.
|
|
53
|
+
`app/views/<controller>/<action>.html.erb`). The first argument is the registered
|
|
54
|
+
component name; it must match the file/registration name exactly (case-sensitive).
|
|
55
|
+
|
|
56
|
+
```erb
|
|
57
|
+
<%= react_component("SimpleCounter", props: { initialCount: 5 }, prerender: true) %>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
- `props:` — a Ruby Hash serialized to JSON and passed to the component.
|
|
61
|
+
- `prerender:` — `true` renders the component on the server (SSR) then hydrates on
|
|
62
|
+
the client; `false` renders only on the client. Set `prerender: false` to isolate
|
|
63
|
+
SSR issues while debugging.
|
|
64
|
+
|
|
65
|
+
## 3. `.client` vs `.server` (and plain) bundles — what runs where
|
|
66
|
+
|
|
67
|
+
When a component name resolves to multiple files, React on Rails picks by suffix:
|
|
68
|
+
|
|
69
|
+
- `<Name>.client.tsx` — runs in the **browser** only. May use `window`, `document`,
|
|
70
|
+
browser-only APIs, and client-side hooks/effects.
|
|
71
|
+
- `<Name>.server.tsx` — runs during **server-side rendering** (Node, no DOM). It
|
|
72
|
+
**must not** use browser-only globals (`window`, `document`, `localStorage`).
|
|
73
|
+
Often it just re-exports the client component for SSR.
|
|
74
|
+
- `<Name>.tsx` (plain, no suffix) — used for **both** server and client. Keep it
|
|
75
|
+
isomorphic: guard any browser-only code with `if (typeof window !== 'undefined')`.
|
|
76
|
+
|
|
77
|
+
Rule of thumb: if `prerender: true`, the code path must run in Node without a DOM.
|
|
78
|
+
|
|
79
|
+
## 4. The `ReactOnRails` JS API you will actually touch
|
|
80
|
+
|
|
81
|
+
Import from the `react-on-rails` npm package:
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
import ReactOnRails from 'react-on-rails';
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
- `ReactOnRails.register({ Name })` — register one or more components by name.
|
|
88
|
+
- `ReactOnRails.registerStore({ name })` / `ReactOnRails.getStore(name)` — register
|
|
89
|
+
and retrieve a Redux store (only if this app uses Redux).
|
|
90
|
+
- `ReactOnRails.reactOnRailsPageLoaded()` — rarely needed; React on Rails wires
|
|
91
|
+
client hydration automatically.
|
|
92
|
+
|
|
93
|
+
You usually only need `register` (and only when not using `ror_components/`).
|
|
94
|
+
|
|
95
|
+
## 5. Top errors and fixes
|
|
96
|
+
|
|
97
|
+
These messages come straight from React on Rails' runtime errors. When you hit one,
|
|
98
|
+
apply the matching fix.
|
|
99
|
+
|
|
100
|
+
### `Component '<Name>' Not Registered`
|
|
101
|
+
|
|
102
|
+
The component name in the view does not match a registered/auto-bundled component.
|
|
103
|
+
|
|
104
|
+
- Recommended (auto-bundling): put the component file directly inside a
|
|
105
|
+
`ror_components/` directory, e.g.
|
|
106
|
+
`app/javascript/src/<Name>/ror_components/<Name>.client.tsx`, with a `default`
|
|
107
|
+
export, then regenerate packs: `bin/rails react_on_rails:generate_packs`.
|
|
108
|
+
- Or register manually: `ReactOnRails.register({ <Name>: <Name> });` and
|
|
109
|
+
`import <Name> from './components/<Name>';`.
|
|
110
|
+
- Check the name matches the `react_component("<Name>", ...)` call exactly (case-sensitive).
|
|
111
|
+
|
|
112
|
+
### `Auto-loaded Bundle Missing`
|
|
113
|
+
|
|
114
|
+
The component is set up for auto-loading but its bundle is missing.
|
|
115
|
+
|
|
116
|
+
1. Run the pack generation task: `bin/rails react_on_rails:generate_packs`.
|
|
117
|
+
2. Ensure the component is in the correct directory under `app/javascript`.
|
|
118
|
+
3. Check naming conventions: file is `<Name>.jsx` or `<Name>.tsx` and `export default`s.
|
|
119
|
+
4. Verify nested entries are enabled in your Shakapacker/webpack config.
|
|
120
|
+
|
|
121
|
+
### `Hydration Mismatch`
|
|
122
|
+
|
|
123
|
+
Server-rendered HTML doesn't match what React rendered on the client.
|
|
124
|
+
|
|
125
|
+
1. **Random IDs or timestamps**: use props or deterministic values, not
|
|
126
|
+
`Math.random()` / `Date.now()`.
|
|
127
|
+
2. **Browser-only APIs**: guard with `if (typeof window !== 'undefined') { ... }`.
|
|
128
|
+
3. **Different data**: ensure props/`railsContext`/store init are identical on
|
|
129
|
+
server and client.
|
|
130
|
+
4. Temporarily set `prerender: false` to isolate the issue.
|
|
131
|
+
|
|
132
|
+
### `Server Rendering Failed`
|
|
133
|
+
|
|
134
|
+
An error occurred while server-rendering a component.
|
|
135
|
+
|
|
136
|
+
1. Check JS console output in the Rails log: `tail -f log/development.log | grep 'React on Rails'`.
|
|
137
|
+
2. Common causes: missing Node dependencies, syntax errors, or browser-only APIs in
|
|
138
|
+
server code.
|
|
139
|
+
3. Set `config.trace = true` and check `config.server_bundle_js_file` points at the
|
|
140
|
+
correct file.
|
|
141
|
+
|
|
142
|
+
### `Redux Store Not Found`
|
|
143
|
+
|
|
144
|
+
A Redux store wasn't registered before a component that depends on it rendered.
|
|
145
|
+
|
|
146
|
+
1. Register the store: `ReactOnRails.registerStore({ <store> });`.
|
|
147
|
+
2. Initialize it in the view before the component: `<%= redux_store('<store>', props: {}) %>`.
|
|
148
|
+
3. Declare the dependency: `store_dependencies: ['<store>']`.
|
|
149
|
+
|
|
150
|
+
## 6. Diagnose your setup
|
|
151
|
+
|
|
152
|
+
Before guessing, run the doctor — it checks configuration, bundles, and dependencies:
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
bin/rails react_on_rails:doctor
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## 7. Where the full reference lives
|
|
159
|
+
|
|
160
|
+
- Hosted docs: https://reactonrails.com/docs/
|
|
161
|
+
- Getting-started tutorial: https://reactonrails.com/docs/getting-started/tutorial/
|
|
162
|
+
- Machine-readable route map / expanded reference (for agents):
|
|
163
|
+
https://github.com/shakacode/react_on_rails/blob/main/llms.txt and
|
|
164
|
+
https://github.com/shakacode/react_on_rails/blob/main/llms-full.txt
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This app uses **React on Rails**. For React on Rails conventions — adding and
|
|
4
|
+
registering components, the `react_component` view helper, `.client`/`.server`
|
|
5
|
+
bundle rules, the `ReactOnRails` JS API, common errors and fixes, and the
|
|
6
|
+
`bin/rails react_on_rails:doctor` diagnostic — read **[AGENTS.md](./AGENTS.md)**.
|
|
7
|
+
|
|
8
|
+
Follow any project-specific instructions elsewhere in this file or repository.
|