react_on_rails 17.0.0.rc.2 → 17.0.0.rc.4

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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +1 -1
  3. data/lib/generators/USAGE +6 -0
  4. data/lib/generators/react_on_rails/base_generator.rb +20 -2
  5. data/lib/generators/react_on_rails/dev_tests_generator.rb +20 -1
  6. data/lib/generators/react_on_rails/generator_helper.rb +7 -0
  7. data/lib/generators/react_on_rails/install_generator.rb +58 -1
  8. data/lib/generators/react_on_rails/js_dependency_manager.rb +37 -0
  9. data/lib/generators/react_on_rails/react_no_redux_generator.rb +20 -8
  10. data/lib/generators/react_on_rails/react_with_redux_generator.rb +28 -4
  11. data/lib/generators/react_on_rails/rsc_generator.rb +5 -0
  12. data/lib/generators/react_on_rails/rsc_setup.rb +35 -1
  13. data/lib/generators/react_on_rails/templates/agent_files/.cursor/rules/react-on-rails.mdc +11 -0
  14. data/lib/generators/react_on_rails/templates/agent_files/.github/copilot-instructions.md +7 -0
  15. data/lib/generators/react_on_rails/templates/agent_files/AGENTS.md +165 -0
  16. data/lib/generators/react_on_rails/templates/agent_files/CLAUDE.md +8 -0
  17. data/lib/generators/react_on_rails/templates/base/base/bin/switch-bundler +6 -1
  18. data/lib/generators/react_on_rails/templates/base/base/config/webpack/commonWebpackConfig.js.tt +81 -1
  19. data/lib/generators/react_on_rails/templates/base/base/config/webpack/development.js.tt +2 -2
  20. data/lib/generators/react_on_rails/templates/base/tailwind/app/javascript/src/HelloWorld/ror_components/HelloWorld.client.jsx +30 -0
  21. data/lib/generators/react_on_rails/templates/base/tailwind/app/javascript/src/HelloWorld/ror_components/HelloWorld.client.tsx +34 -0
  22. data/lib/generators/react_on_rails/templates/base/tailwind/app/javascript/stylesheets/application.css +1 -0
  23. data/lib/generators/react_on_rails/templates/dev_tests/eslint.config.mjs +50 -0
  24. data/lib/generators/react_on_rails/templates/redux/tailwind/app/javascript/bundles/HelloWorld/components/HelloWorld.jsx +25 -0
  25. data/lib/generators/react_on_rails/templates/redux/tailwind/app/javascript/bundles/HelloWorld/components/HelloWorld.tsx +29 -0
  26. data/lib/react_on_rails/controller/form_responders.rb +59 -0
  27. data/lib/react_on_rails/dev/server_manager.rb +1 -1
  28. data/lib/react_on_rails/doctor.rb +187 -24
  29. data/lib/react_on_rails/font_helper.rb +162 -0
  30. data/lib/react_on_rails/helper.rb +150 -0
  31. data/lib/react_on_rails/smart_error.rb +135 -5
  32. data/lib/react_on_rails/version.rb +1 -1
  33. data/lib/react_on_rails.rb +1 -0
  34. data/lib/tasks/doctor.rake +5 -2
  35. data/sig/react_on_rails/controller/form_responders.rbs +10 -0
  36. data/sig/react_on_rails/helper.rbs +3 -0
  37. data/sig/react_on_rails/smart_error.rbs +25 -2
  38. metadata +14 -2
  39. data/lib/generators/react_on_rails/templates/dev_tests/.eslintrc +0 -25
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5610a42464b29a2f8066e2a91d342838cdd1f3734c3965864e0a5bd0d18e4c8a
4
- data.tar.gz: 551fc429af6eea0798b929d0aece8b417b4646dfed702c1dcd39ae26d52084ad
3
+ metadata.gz: 1a437fef821bdc1e6bd3b289e1b627bafc9a5c5850dc1f830c15d683dc1593e2
4
+ data.tar.gz: 4a5679fea3c1f2b24dbbf7b6325690e0e95222b03452c9e6cca211fdf149e83d
5
5
  SHA512:
6
- metadata.gz: 4b1df35fc94bc088e6a9afe96ca54ec651951149ae82415e55d1db023ef582089fb58f9536db698d0971633678b3d2d70188c0ca355217fd9f6a854ead1f177c
7
- data.tar.gz: c1371c43729036f9f9dbb78d68b85be81919a98a16fb8d3d4185b94c0f48c92506404bedb6e1a7a804b52596ae784b4f60487cc4bad8333f0933e38aa005551a
6
+ metadata.gz: 5c905725d516131bbc94c8103953d0522a62611428c692d414b0f029299d29641f4239d4e88c054bb29aa3dfd6091133dc7c360912ba84ee5fc060540c5d26e3
7
+ data.tar.gz: 3b0af40fdd4fe98d75ec7fa198fd44bdafcc6f529364624e8bf42669e55d01833142698249c3d41b6587b4e65d2fbac69d96105c65667d36c58f117375529998
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- react_on_rails (17.0.0.rc.2)
4
+ react_on_rails (17.0.0.rc.4)
5
5
  addressable
6
6
  connection_pool
7
7
  execjs (~> 2.5)
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.
@@ -8,6 +8,16 @@ module ReactOnRails
8
8
  class DevTestsGenerator < Rails::Generators::Base
9
9
  include GeneratorHelper
10
10
 
11
+ ESLINT_DEV_DEPENDENCIES = {
12
+ "@eslint/js" => "^9.0.0",
13
+ "eslint" => "^9.0.0",
14
+ "eslint-config-prettier" => "^10.0.0",
15
+ "eslint-plugin-import" => "^2.29.0",
16
+ "eslint-plugin-react" => "^7.37.5",
17
+ "eslint-plugin-react-hooks" => "^6.1.1",
18
+ "globals" => "^16.0.0"
19
+ }.freeze
20
+
11
21
  Rails::Generators.hide_namespace(namespace)
12
22
  source_root(File.expand_path("templates/dev_tests", __dir__))
13
23
 
@@ -24,7 +34,7 @@ module ReactOnRails
24
34
  desc: "Include React Server Components test (hello_server_spec.rb)"
25
35
 
26
36
  def copy_rspec_files
27
- %w[.eslintrc
37
+ %w[eslint.config.mjs
28
38
  spec/spec_helper.rb
29
39
  spec/rails_helper.rb
30
40
  spec/simplecov_helper.rb
@@ -69,6 +79,15 @@ module ReactOnRails
69
79
 
70
80
  File.open(package_json, "w+") { |f| f.puts JSON.pretty_generate(contents) }
71
81
  end
82
+
83
+ def add_internal_eslint_dev_dependencies
84
+ package_json = File.join(destination_root, "package.json")
85
+ contents = JSON.parse(File.read(package_json))
86
+ contents["devDependencies"] ||= {}
87
+ contents["devDependencies"].merge!(ESLINT_DEV_DEPENDENCIES)
88
+
89
+ File.open(package_json, "w+") { |f| f.puts JSON.pretty_generate(contents) }
90
+ end
72
91
  end
73
92
  end
74
93
  end
@@ -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[
@@ -103,6 +112,7 @@ module ReactOnRails
103
112
  # @rspack/cli uses ^2.0.0-0 to match @rspack/core's prerelease range.
104
113
  RSPACK_DEV_DEPENDENCIES = %w[
105
114
  @rspack/cli@^2.0.0-0
115
+ @rspack/dev-server@^2.0.0
106
116
  @rspack/plugin-react-refresh@^2.0.0
107
117
  react-refresh
108
118
  ].freeze
@@ -175,6 +185,7 @@ module ReactOnRails
175
185
  add_react_on_rails_package unless using_pro
176
186
  add_react_dependencies
177
187
  add_css_dependencies
188
+ add_tailwind_dependencies_if_requested
178
189
  add_rspack_dependencies if using_rspack?
179
190
  add_transpiler_dependencies
180
191
  add_pro_dependencies if using_pro
@@ -290,6 +301,32 @@ module ReactOnRails
290
301
  MSG
291
302
  end
292
303
 
304
+ def add_tailwind_dependencies
305
+ say "Installing Tailwind CSS v4 dependencies..."
306
+ return if add_packages(TAILWIND_DEPENDENCIES)
307
+
308
+ GeneratorMessages.add_warning(<<~MSG.strip)
309
+ ⚠️ Failed to add Tailwind CSS dependencies.
310
+
311
+ You can install them manually by running:
312
+ #{manual_add_packages_command(TAILWIND_DEPENDENCIES)}
313
+ MSG
314
+ rescue StandardError => e
315
+ GeneratorMessages.add_warning(<<~MSG.strip)
316
+ ⚠️ Error adding Tailwind CSS dependencies: #{e.message}
317
+
318
+ You can install them manually by running:
319
+ #{manual_add_packages_command(TAILWIND_DEPENDENCIES)}
320
+ MSG
321
+ end
322
+
323
+ def add_tailwind_dependencies_if_requested
324
+ # use_tailwind? is provided by GeneratorHelper, included alongside this module.
325
+ return unless use_tailwind?
326
+
327
+ add_tailwind_dependencies
328
+ end
329
+
293
330
  def add_rspack_dependencies
294
331
  say "Installing Rspack core dependencies..."
295
332
  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
- component_files = [
31
- "app/javascript/src/HelloWorld/ror_components/HelloWorld.client.#{component_extension(options)}",
32
- "app/javascript/src/HelloWorld/ror_components/HelloWorld.server.#{component_extension(options)}",
33
- "app/javascript/src/HelloWorld/ror_components/HelloWorld.module.css"
34
- ]
35
-
36
- component_files.each do |file|
37
- copy_file("#{base_js_path}/#{file}", file)
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
- copy_file("#{base_js_path}/app/javascript/bundles/HelloWorld/components/HelloWorld.module.css",
57
- "app/javascript/src/HelloWorldApp/components/HelloWorld.module.css")
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
- say "ℹ️ HelloServer component already exists, skipping", :yellow
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.