react_on_rails 15.0.0.rc.2 → 16.0.1.rc.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.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +103 -34
  3. data/CLAUDE.md +102 -0
  4. data/CODING_AGENTS.md +312 -0
  5. data/CONTRIBUTING.md +378 -3
  6. data/Gemfile.lock +2 -1
  7. data/LICENSE.md +30 -4
  8. data/LICENSES/README.md +14 -0
  9. data/REACT-ON-RAILS-PRO-LICENSE.md +129 -0
  10. data/README.md +70 -20
  11. data/TODO.md +135 -0
  12. data/eslint.config.ts +5 -0
  13. data/knip.ts +20 -9
  14. data/lib/generators/USAGE +4 -5
  15. data/lib/generators/react_on_rails/USAGE +65 -0
  16. data/lib/generators/react_on_rails/base_generator.rb +263 -57
  17. data/lib/generators/react_on_rails/dev_tests_generator.rb +1 -0
  18. data/lib/generators/react_on_rails/generator_helper.rb +35 -1
  19. data/lib/generators/react_on_rails/generator_messages.rb +138 -17
  20. data/lib/generators/react_on_rails/install_generator.rb +336 -26
  21. data/lib/generators/react_on_rails/react_no_redux_generator.rb +19 -6
  22. data/lib/generators/react_on_rails/react_with_redux_generator.rb +111 -18
  23. data/lib/generators/react_on_rails/templates/base/base/Procfile.dev +5 -0
  24. data/lib/generators/react_on_rails/templates/base/base/Procfile.dev-prod-assets +8 -0
  25. data/lib/generators/react_on_rails/templates/base/base/Procfile.dev-static-assets +2 -0
  26. data/lib/generators/react_on_rails/templates/base/base/app/javascript/bundles/HelloWorld/components/HelloWorld.jsx +0 -5
  27. data/lib/generators/react_on_rails/templates/base/base/app/javascript/packs/server-bundle.js +1 -8
  28. data/lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.client.jsx +21 -0
  29. data/lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.client.tsx +25 -0
  30. data/lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.module.css +4 -0
  31. data/lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.server.jsx +5 -0
  32. data/lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.server.tsx +5 -0
  33. data/lib/generators/react_on_rails/templates/base/base/app/views/hello_world/index.html.erb.tt +1 -1
  34. data/lib/generators/react_on_rails/templates/base/base/app/views/layouts/hello_world.html.erb +4 -2
  35. data/lib/generators/react_on_rails/templates/base/base/babel.config.js.tt +5 -2
  36. data/lib/generators/react_on_rails/templates/base/base/bin/dev +34 -0
  37. data/lib/generators/react_on_rails/templates/base/base/config/initializers/react_on_rails.rb.tt +3 -3
  38. data/lib/generators/react_on_rails/templates/base/base/config/shakapacker.yml +76 -7
  39. data/lib/generators/react_on_rails/templates/base/base/config/webpack/commonWebpackConfig.js.tt +1 -1
  40. data/lib/generators/react_on_rails/templates/base/base/config/webpack/development.js.tt +8 -8
  41. data/lib/generators/react_on_rails/templates/base/base/config/webpack/production.js.tt +2 -2
  42. data/lib/generators/react_on_rails/templates/base/base/config/webpack/test.js.tt +2 -2
  43. data/lib/generators/react_on_rails/templates/dev_tests/spec/system/hello_world_spec.rb +0 -2
  44. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/actions/helloWorldActionCreators.ts +18 -0
  45. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/components/HelloWorld.jsx +0 -6
  46. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/components/HelloWorld.module.css +4 -0
  47. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/components/HelloWorld.tsx +24 -0
  48. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/constants/helloWorldConstants.ts +6 -0
  49. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/containers/HelloWorldContainer.ts +20 -0
  50. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/reducers/helloWorldReducer.ts +22 -0
  51. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/startup/HelloWorldApp.client.tsx +23 -0
  52. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/startup/HelloWorldApp.server.jsx +5 -0
  53. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/startup/HelloWorldApp.server.tsx +5 -0
  54. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/store/helloWorldStore.ts +18 -0
  55. data/lib/react_on_rails/configuration.rb +15 -11
  56. data/lib/react_on_rails/controller.rb +5 -3
  57. data/lib/react_on_rails/dev/file_manager.rb +78 -0
  58. data/lib/react_on_rails/dev/pack_generator.rb +27 -0
  59. data/lib/react_on_rails/dev/process_manager.rb +61 -0
  60. data/lib/react_on_rails/dev/server_manager.rb +487 -0
  61. data/lib/react_on_rails/dev.rb +20 -0
  62. data/lib/react_on_rails/doctor.rb +1149 -0
  63. data/lib/react_on_rails/engine.rb +6 -0
  64. data/lib/react_on_rails/git_utils.rb +12 -2
  65. data/lib/react_on_rails/helper.rb +19 -44
  66. data/lib/react_on_rails/packer_utils.rb +4 -18
  67. data/lib/react_on_rails/packs_generator.rb +134 -8
  68. data/lib/react_on_rails/pro/NOTICE +21 -0
  69. data/lib/react_on_rails/pro/helper.rb +122 -0
  70. data/lib/react_on_rails/pro/utils.rb +53 -0
  71. data/lib/react_on_rails/react_component/render_options.rb +8 -4
  72. data/lib/react_on_rails/server_rendering_js_code.rb +0 -1
  73. data/lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb +1 -0
  74. data/lib/react_on_rails/system_checker.rb +659 -0
  75. data/lib/react_on_rails/test_helper/webpack_assets_status_checker.rb +1 -0
  76. data/lib/react_on_rails/utils.rb +16 -1
  77. data/lib/react_on_rails/version.rb +1 -1
  78. data/lib/react_on_rails/version_syntax_converter.rb +1 -1
  79. data/lib/react_on_rails.rb +1 -0
  80. data/lib/tasks/doctor.rake +51 -0
  81. data/lib/tasks/generate_packs.rake +144 -1
  82. data/package-lock.json +11984 -0
  83. data/react_on_rails.gemspec +1 -0
  84. metadata +55 -11
  85. data/REACT-ON-RAILS-PRO-LICENSE +0 -95
  86. data/lib/generators/react_on_rails/adapt_for_older_shakapacker_generator.rb +0 -41
  87. data/lib/generators/react_on_rails/bin/dev +0 -30
  88. data/lib/generators/react_on_rails/bin/dev-static +0 -30
  89. data/lib/generators/react_on_rails/templates/base/base/Procfile.dev-static.tt +0 -9
  90. data/lib/generators/react_on_rails/templates/base/base/Procfile.dev.tt +0 -5
  91. data/lib/generators/react_on_rails/templates/base/base/app/javascript/packs/registration.js.tt +0 -8
  92. /data/lib/generators/react_on_rails/templates/base/base/config/webpack/{webpackConfig.js.tt → generateWebpackConfigs.js.tt} +0 -0
  93. /data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/startup/{HelloWorldApp.jsx → HelloWorldApp.client.jsx} +0 -0
@@ -1,12 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "rails/generators"
4
+ require "fileutils"
4
5
  require_relative "generator_messages"
5
6
  require_relative "generator_helper"
6
7
  module ReactOnRails
7
8
  module Generators
8
9
  class BaseGenerator < Rails::Generators::Base
9
10
  include GeneratorHelper
11
+
10
12
  Rails::Generators.hide_namespace(namespace)
11
13
  source_root(File.expand_path("templates", __dir__))
12
14
 
@@ -14,7 +16,7 @@ module ReactOnRails
14
16
  class_option :redux,
15
17
  type: :boolean,
16
18
  default: false,
17
- desc: "Install Redux gems and Redux version of Hello World Example",
19
+ desc: "Install Redux package and Redux version of Hello World Example",
18
20
  aliases: "-R"
19
21
 
20
22
  def add_hello_world_route
@@ -22,17 +24,21 @@ module ReactOnRails
22
24
  end
23
25
 
24
26
  def create_react_directories
25
- dirs = %w[components]
26
- dirs.each { |name| empty_directory("app/javascript/bundles/HelloWorld/#{name}") }
27
+ # Create auto-registration directory structure for non-Redux components only
28
+ # Redux components handle their own directory structure
29
+ return if options.redux?
30
+
31
+ empty_directory("app/javascript/src/HelloWorld/ror_components")
27
32
  end
28
33
 
29
34
  def copy_base_files
30
35
  base_path = "base/base/"
31
36
  base_files = %w[app/controllers/hello_world_controller.rb
32
- app/views/layouts/hello_world.html.erb]
33
- base_templates = %w[config/initializers/react_on_rails.rb
34
- Procfile.dev
35
- Procfile.dev-static]
37
+ app/views/layouts/hello_world.html.erb
38
+ Procfile.dev
39
+ Procfile.dev-static-assets
40
+ Procfile.dev-prod-assets]
41
+ base_templates = %w[config/initializers/react_on_rails.rb]
36
42
  base_files.each { |file| copy_file("#{base_path}#{file}", file) }
37
43
  base_templates.each do |file|
38
44
  template("#{base_path}/#{file}.tt", file, { packer_type: ReactOnRails::PackerUtils.packer_type })
@@ -41,9 +47,12 @@ module ReactOnRails
41
47
 
42
48
  def copy_js_bundle_files
43
49
  base_path = "base/base/"
44
- base_files = %w[app/javascript/packs/server-bundle.js
45
- app/javascript/bundles/HelloWorld/components/HelloWorldServer.js
46
- app/javascript/bundles/HelloWorld/components/HelloWorld.module.css]
50
+ base_files = %w[app/javascript/packs/server-bundle.js]
51
+
52
+ # Only copy HelloWorld.module.css for non-Redux components
53
+ # Redux components handle their own CSS files
54
+ base_files << "app/javascript/src/HelloWorld/ror_components/HelloWorld.module.css" unless options.redux?
55
+
47
56
  base_files.each { |file| copy_file("#{base_path}#{file}", file) }
48
57
  end
49
58
 
@@ -57,15 +66,25 @@ module ReactOnRails
57
66
  config/webpack/development.js
58
67
  config/webpack/production.js
59
68
  config/webpack/serverWebpackConfig.js
60
- config/webpack/webpack.config.js
61
- config/webpack/webpackConfig.js]
69
+ config/webpack/generateWebpackConfigs.js]
62
70
  config = {
63
71
  message: "// The source code including full typescript support is available at:"
64
72
  }
65
73
  base_files.each { |file| template("#{base_path}/#{file}.tt", file, config) }
74
+
75
+ # Handle webpack.config.js separately with smart replacement
76
+ copy_webpack_main_config(base_path, config)
66
77
  end
67
78
 
68
79
  def copy_packer_config
80
+ # Skip copying if Shakapacker was just installed (to avoid conflicts)
81
+ # Check for a temporary marker file that indicates fresh Shakapacker install
82
+ if File.exist?(".shakapacker_just_installed")
83
+ puts "Skipping Shakapacker config copy (already installed by Shakapacker installer)"
84
+ File.delete(".shakapacker_just_installed") # Clean up marker
85
+ return
86
+ end
87
+
69
88
  puts "Adding Shakapacker #{ReactOnRails::PackerUtils.shakapacker_version} config"
70
89
  base_path = "base/base/"
71
90
  config = "config/shakapacker.yml"
@@ -77,39 +96,55 @@ module ReactOnRails
77
96
  end
78
97
 
79
98
  def add_js_dependencies
80
- major_minor_patch_only = /\A\d+\.\d+\.\d+\z/
81
- if ReactOnRails::VERSION.match?(major_minor_patch_only)
82
- package_json.manager.add(["react-on-rails@#{ReactOnRails::VERSION}"])
83
- else
84
- # otherwise add latest
85
- puts "Adding the latest react-on-rails NPM module. Double check this is correct in package.json"
86
- package_json.manager.add(["react-on-rails"])
99
+ add_react_on_rails_package
100
+ add_react_dependencies
101
+ add_css_dependencies
102
+ add_dev_dependencies
103
+ end
104
+
105
+ def install_js_dependencies
106
+ # Detect which package manager to use
107
+ success = if File.exist?(File.join(destination_root, "yarn.lock"))
108
+ system("yarn", "install")
109
+ elsif File.exist?(File.join(destination_root, "pnpm-lock.yaml"))
110
+ system("pnpm", "install")
111
+ elsif File.exist?(File.join(destination_root, "package-lock.json")) ||
112
+ File.exist?(File.join(destination_root, "package.json"))
113
+ # Use npm for package-lock.json or as default fallback
114
+ system("npm", "install")
115
+ else
116
+ true # No package manager detected, skip
117
+ end
118
+
119
+ unless success
120
+ GeneratorMessages.add_warning(<<~MSG.strip)
121
+ ⚠️ JavaScript dependencies installation failed.
122
+
123
+ This could be due to network issues or missing package manager.
124
+ You can install dependencies manually later by running:
125
+ • npm install (if using npm)
126
+ • yarn install (if using yarn)
127
+ • pnpm install (if using pnpm)
128
+ MSG
87
129
  end
88
130
 
89
- puts "Adding React dependencies"
90
- package_json.manager.add([
91
- "react",
92
- "react-dom",
93
- "@babel/preset-react",
94
- "prop-types",
95
- "babel-plugin-transform-react-remove-prop-types",
96
- "babel-plugin-macros"
97
- ])
131
+ success
132
+ end
98
133
 
99
- puts "Adding CSS handlers"
134
+ def update_gitignore_for_auto_registration
135
+ gitignore_path = File.join(destination_root, ".gitignore")
136
+ return unless File.exist?(gitignore_path)
100
137
 
101
- package_json.manager.add(%w[
102
- css-loader
103
- css-minimizer-webpack-plugin
104
- mini-css-extract-plugin
105
- style-loader
106
- ])
138
+ gitignore_content = File.read(gitignore_path)
139
+ return if gitignore_content.include?("**/generated/**")
107
140
 
108
- puts "Adding dev dependencies"
109
- package_json.manager.add([
110
- "@pmmmwh/react-refresh-webpack-plugin",
111
- "react-refresh"
112
- ], type: :dev)
141
+ append_to_file ".gitignore" do
142
+ <<~GITIGNORE
143
+
144
+ # Generated React on Rails packs
145
+ **/generated/**
146
+ GITIGNORE
147
+ end
113
148
  end
114
149
 
115
150
  def append_to_spec_rails_helper
@@ -118,26 +153,70 @@ module ReactOnRails
118
153
  add_configure_rspec_to_compile_assets(rails_helper)
119
154
  else
120
155
  spec_helper = File.join(destination_root, "spec/spec_helper.rb")
121
- if File.exist?(spec_helper)
122
- add_configure_rspec_to_compile_assets(spec_helper)
123
- else
124
- # rubocop:disable Layout/EmptyLinesAroundArguments
125
- GeneratorMessages.add_info(
126
- <<-MSG.strip_heredoc
156
+ add_configure_rspec_to_compile_assets(spec_helper) if File.exist?(spec_helper)
157
+ end
158
+ end
127
159
 
128
- We did not find a spec/rails_helper.rb or spec/spec_helper.rb to add
129
- the React on Rails Test helper, which ensures that if we are running
130
- js tests, then we are using latest webpack assets. You can later add
131
- this to your rspec config:
160
+ def add_react_on_rails_package
161
+ major_minor_patch_only = /\A\d+\.\d+\.\d+\z/
132
162
 
133
- # This will use the defaults of :js and :server_rendering meta tags
134
- ReactOnRails::TestHelper.configure_rspec_to_compile_assets(config)
135
- MSG
136
- )
137
- # rubocop:enable Layout/EmptyLinesAroundArguments
163
+ # Try to use package_json gem first, fall back to direct npm commands
164
+ react_on_rails_pkg = if ReactOnRails::VERSION.match?(major_minor_patch_only)
165
+ ["react-on-rails@#{ReactOnRails::VERSION}"]
166
+ else
167
+ puts "Adding the latest react-on-rails NPM module. " \
168
+ "Double check this is correct in package.json"
169
+ ["react-on-rails"]
170
+ end
138
171
 
139
- end
140
- end
172
+ puts "Installing React on Rails package..."
173
+ return if add_npm_dependencies(react_on_rails_pkg)
174
+
175
+ puts "Using direct npm commands as fallback"
176
+ success = system("npm", "install", *react_on_rails_pkg)
177
+ handle_npm_failure("react-on-rails package", react_on_rails_pkg) unless success
178
+ end
179
+
180
+ def add_react_dependencies
181
+ puts "Installing React dependencies..."
182
+ react_deps = %w[
183
+ react
184
+ react-dom
185
+ @babel/preset-react
186
+ prop-types
187
+ babel-plugin-transform-react-remove-prop-types
188
+ babel-plugin-macros
189
+ ]
190
+ return if add_npm_dependencies(react_deps)
191
+
192
+ success = system("npm", "install", *react_deps)
193
+ handle_npm_failure("React dependencies", react_deps) unless success
194
+ end
195
+
196
+ def add_css_dependencies
197
+ puts "Installing CSS handling dependencies..."
198
+ css_deps = %w[
199
+ css-loader
200
+ css-minimizer-webpack-plugin
201
+ mini-css-extract-plugin
202
+ style-loader
203
+ ]
204
+ return if add_npm_dependencies(css_deps)
205
+
206
+ success = system("npm", "install", *css_deps)
207
+ handle_npm_failure("CSS dependencies", css_deps) unless success
208
+ end
209
+
210
+ def add_dev_dependencies
211
+ puts "Installing development dependencies..."
212
+ dev_deps = %w[
213
+ @pmmmwh/react-refresh-webpack-plugin
214
+ react-refresh
215
+ ]
216
+ return if add_npm_dependencies(dev_deps, dev: true)
217
+
218
+ success = system("npm", "install", "--save-dev", *dev_deps)
219
+ handle_npm_failure("development dependencies", dev_deps, dev: true) unless success
141
220
  end
142
221
 
143
222
  CONFIGURE_RSPEC_TO_COMPILE_ASSETS = <<-STR.strip_heredoc
@@ -145,10 +224,137 @@ module ReactOnRails
145
224
  # Ensure that if we are running js tests, we are using latest webpack assets
146
225
  # This will use the defaults of :js and :server_rendering meta tags
147
226
  ReactOnRails::TestHelper.configure_rspec_to_compile_assets(config)
227
+ end
148
228
  STR
149
229
 
150
230
  private
151
231
 
232
+ def handle_npm_failure(dependency_type, packages, dev: false)
233
+ install_command = dev ? "npm install --save-dev" : "npm install"
234
+ GeneratorMessages.add_warning(<<~MSG.strip)
235
+ ⚠️ Failed to install #{dependency_type}.
236
+
237
+ The following packages could not be installed automatically:
238
+ #{packages.map { |pkg| " • #{pkg}" }.join("\n")}
239
+
240
+ This could be due to network issues or missing package manager.
241
+ You can install them manually later by running:
242
+ #{install_command} #{packages.join(' ')}
243
+ MSG
244
+ end
245
+
246
+ def copy_webpack_main_config(base_path, config)
247
+ webpack_config_path = "config/webpack/webpack.config.js"
248
+
249
+ if File.exist?(webpack_config_path)
250
+ existing_content = File.read(webpack_config_path)
251
+
252
+ # Check if it's the standard Shakapacker config that we can safely replace
253
+ if standard_shakapacker_config?(existing_content)
254
+ # Remove the file first to avoid conflict prompt, then recreate it
255
+ remove_file(webpack_config_path, verbose: false)
256
+ # Show what we're doing
257
+ puts " #{set_color('replace', :green)} #{webpack_config_path} " \
258
+ "(auto-upgrading from standard Shakapacker to React on Rails config)"
259
+ template("#{base_path}/#{webpack_config_path}.tt", webpack_config_path, config)
260
+ elsif react_on_rails_config?(existing_content)
261
+ puts " #{set_color('identical', :blue)} #{webpack_config_path} " \
262
+ "(already React on Rails compatible)"
263
+ # Skip - don't need to do anything
264
+ else
265
+ handle_custom_webpack_config(base_path, config, webpack_config_path)
266
+ end
267
+ else
268
+ # File doesn't exist, create it
269
+ template("#{base_path}/#{webpack_config_path}.tt", webpack_config_path, config)
270
+ end
271
+ end
272
+
273
+ def handle_custom_webpack_config(base_path, config, webpack_config_path)
274
+ # Custom config - ask user
275
+ puts "\n#{set_color('NOTICE:', :yellow)} Your webpack.config.js appears to be customized."
276
+ puts "React on Rails needs to replace it with an environment-specific loader."
277
+ puts "Your current config will be backed up to webpack.config.js.backup"
278
+
279
+ if yes?("Replace webpack.config.js with React on Rails version? (Y/n)")
280
+ # Create backup
281
+ backup_path = "#{webpack_config_path}.backup"
282
+ if File.exist?(webpack_config_path)
283
+ FileUtils.cp(webpack_config_path, backup_path)
284
+ puts " #{set_color('create', :green)} #{backup_path} (backup of your custom config)"
285
+ end
286
+
287
+ template("#{base_path}/#{webpack_config_path}.tt", webpack_config_path, config)
288
+ else
289
+ puts " #{set_color('skip', :yellow)} #{webpack_config_path}"
290
+ puts " #{set_color('WARNING:', :red)} React on Rails may not work correctly " \
291
+ "without the environment-specific webpack config"
292
+ end
293
+ end
294
+
295
+ def standard_shakapacker_config?(content)
296
+ # Get the expected default config based on Shakapacker version
297
+ expected_configs = shakapacker_default_configs
298
+
299
+ # Check if the content matches any of the known default configurations
300
+ expected_configs.any? { |config| content_matches_template?(content, config) }
301
+ end
302
+
303
+ def content_matches_template?(content, template)
304
+ # Normalize whitespace and compare
305
+ normalize_config_content(content) == normalize_config_content(template)
306
+ end
307
+
308
+ def normalize_config_content(content)
309
+ # Remove comments, normalize whitespace, and clean up for comparison
310
+ content.gsub(%r{//.*$}, "") # Remove single-line comments
311
+ .gsub(%r{/\*.*?\*/}m, "") # Remove multi-line comments
312
+ .gsub(/\s+/, " ") # Normalize whitespace
313
+ .strip
314
+ end
315
+
316
+ def shakapacker_default_configs
317
+ configs = []
318
+
319
+ # Shakapacker v7+ (generateWebpackConfig function)
320
+ configs << <<~CONFIG
321
+ // See the shakacode/shakapacker README and docs directory for advice on customizing your webpackConfig.
322
+ const { generateWebpackConfig } = require('shakapacker')
323
+
324
+ const webpackConfig = generateWebpackConfig()
325
+
326
+ module.exports = webpackConfig
327
+ CONFIG
328
+
329
+ # Shakapacker v6 (webpackConfig object)
330
+ configs << <<~CONFIG
331
+ const { webpackConfig } = require('shakapacker')
332
+
333
+ // See the shakacode/shakapacker README and docs directory for advice on customizing your webpackConfig.
334
+
335
+ module.exports = webpackConfig
336
+ CONFIG
337
+
338
+ # Also check without comments for variations
339
+ configs << <<~CONFIG
340
+ const { generateWebpackConfig } = require('shakapacker')
341
+ const webpackConfig = generateWebpackConfig()
342
+ module.exports = webpackConfig
343
+ CONFIG
344
+
345
+ configs << <<~CONFIG
346
+ const { webpackConfig } = require('shakapacker')
347
+ module.exports = webpackConfig
348
+ CONFIG
349
+
350
+ configs
351
+ end
352
+
353
+ def react_on_rails_config?(content)
354
+ # Check if it already has React on Rails environment-specific loading
355
+ content.include?("envSpecificConfig") || content.include?("env.nodeEnv")
356
+ end
357
+
152
358
  # From https://github.com/rails/rails/blob/4c940b2dbfb457f67c6250b720f63501d74a45fd/railties/lib/rails/generators/rails/app/app_generator.rb
153
359
  def app_name
154
360
  @app_name ||= (defined_app_const_base? ? defined_app_name : File.basename(destination_root))
@@ -7,6 +7,7 @@ module ReactOnRails
7
7
  module Generators
8
8
  class DevTestsGenerator < Rails::Generators::Base
9
9
  include GeneratorHelper
10
+
10
11
  Rails::Generators.hide_namespace(namespace)
11
12
  source_root(File.expand_path("templates/dev_tests", __dir__))
12
13
 
@@ -1,11 +1,41 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "package_json"
4
3
  require "rainbow"
4
+ require "json"
5
5
 
6
6
  module GeneratorHelper
7
7
  def package_json
8
+ # Lazy load package_json gem only when actually needed for dependency management
9
+
10
+ require "package_json" unless defined?(PackageJson)
8
11
  @package_json ||= PackageJson.read
12
+ rescue LoadError
13
+ puts "Warning: package_json gem not available. This is expected before Shakapacker installation."
14
+ puts "Dependencies will be installed using the default package manager after Shakapacker setup."
15
+ nil
16
+ rescue StandardError => e
17
+ puts "Warning: Could not read package.json: #{e.message}"
18
+ puts "This is normal before Shakapacker creates the package.json file."
19
+ nil
20
+ end
21
+
22
+ # Safe wrapper for package_json operations
23
+ def add_npm_dependencies(packages, dev: false)
24
+ pj = package_json
25
+ return false unless pj
26
+
27
+ begin
28
+ if dev
29
+ pj.manager.add(packages, type: :dev)
30
+ else
31
+ pj.manager.add(packages)
32
+ end
33
+ true
34
+ rescue StandardError => e
35
+ puts "Warning: Could not add packages via package_json gem: #{e.message}"
36
+ puts "Will fall back to direct npm commands."
37
+ false
38
+ end
9
39
  end
10
40
 
11
41
  # Takes a relative path from the destination root, such as `.gitignore` or `app/assets/javascripts/application.js`
@@ -61,4 +91,8 @@ module GeneratorHelper
61
91
  def add_documentation_reference(message, source)
62
92
  "#{message} \n#{source}"
63
93
  end
94
+
95
+ def component_extension(options)
96
+ options.typescript? ? "tsx" : "jsx"
97
+ end
64
98
  end
@@ -38,37 +38,158 @@ module GeneratorMessages
38
38
  @output = []
39
39
  end
40
40
 
41
- def helpful_message_after_installation
41
+ def helpful_message_after_installation(component_name: "HelloWorld", route: "hello_world")
42
+ process_manager_section = build_process_manager_section
43
+ testing_section = build_testing_section
44
+ package_manager = detect_package_manager
45
+ shakapacker_status = build_shakapacker_status_section
46
+
42
47
  <<~MSG
43
48
 
44
- What to do next:
49
+ ╔════════════════════════════════════════════════════════════════════════╗
50
+ ║ 🎉 React on Rails Successfully Installed! ║
51
+ ╚════════════════════════════════════════════════════════════════════════╝
52
+ #{process_manager_section}#{shakapacker_status}
53
+
54
+ 📋 QUICK START:
55
+ ─────────────────────────────────────────────────────────────────────────
56
+ 1. Install dependencies:
57
+ #{Rainbow("bundle && #{package_manager} install").cyan}
58
+
59
+ 2. Start the app:
60
+ ./bin/dev # HMR (Hot Module Replacement) mode
61
+ ./bin/dev static # Static bundles (no HMR, faster initial load)
62
+ ./bin/dev prod # Production-like mode for testing
63
+ ./bin/dev help # See all available options
64
+
65
+ 3. Visit: #{Rainbow(route ? "http://localhost:3000/#{route}" : 'http://localhost:3000').cyan.underline}
66
+ ✨ KEY FEATURES:
67
+ ─────────────────────────────────────────────────────────────────────────
68
+ • Auto-registration enabled - Your layout only needs:
69
+ <%= javascript_pack_tag %>
70
+ <%= stylesheet_pack_tag %>
71
+
72
+ • Server-side rendering - Enabled with prerender option in app/views/hello_world/index.html.erb:
73
+ <%= react_component("#{component_name}", props: @hello_world_props, prerender: true) %>
74
+
75
+ 📚 LEARN MORE:
76
+ ─────────────────────────────────────────────────────────────────────────
77
+ • Documentation: #{Rainbow('https://www.shakacode.com/react-on-rails/docs/').cyan.underline}
78
+ • Webpack customization: #{Rainbow('https://github.com/shakacode/shakapacker#webpack-configuration').cyan.underline}
79
+
80
+ 💡 TIP: Run 'bin/dev help' for development server options and troubleshooting#{testing_section}
81
+ MSG
82
+ end
45
83
 
46
- - See the documentation on https://github.com/shakacode/shakapacker#webpack-configuration
47
- for how to customize the default webpack configuration.
84
+ private
85
+
86
+ def build_process_manager_section
87
+ process_manager = detect_process_manager
88
+ if process_manager
89
+ if process_manager == "overmind"
90
+ "\n📦 #{Rainbow("#{process_manager} detected ✓").green} " \
91
+ "#{Rainbow('(Recommended for easier debugging)').blue}"
92
+ else
93
+ "\n📦 #{Rainbow("#{process_manager} detected ✓").green}"
94
+ end
95
+ else
96
+ <<~INSTALL
97
+
98
+ ⚠️ No process manager detected. Install one:
99
+ #{Rainbow('brew install overmind').yellow.bold} # Recommended (easier debugging)
100
+ #{Rainbow('gem install foreman').yellow} # Alternative
101
+ INSTALL
102
+ end
103
+ end
48
104
 
49
- - Include your webpack assets to your application layout.
105
+ def build_testing_section
106
+ # Check if we have any spec files to determine if testing setup is needed
107
+ has_spec_files = File.exist?("spec/rails_helper.rb") || File.exist?("spec/spec_helper.rb")
50
108
 
51
- <%= javascript_pack_tag 'hello-world-bundle' %>
109
+ return "" if has_spec_files
52
110
 
53
- - To start Rails server run:
111
+ <<~TESTING
54
112
 
55
- ./bin/dev # Running with HMR
56
113
 
57
- or
114
+ 🧪 TESTING SETUP (Optional):
115
+ ─────────────────────────────────────────────────────────────────────────
116
+ For JavaScript testing with asset compilation, add this to your RSpec config:
58
117
 
59
- ./bin/dev-static # Running with statically created bundles, without HMR
118
+ # In spec/rails_helper.rb or spec/spec_helper.rb:
119
+ ReactOnRails::TestHelper.configure_rspec_to_compile_assets(config)
120
+ TESTING
121
+ end
60
122
 
61
- - To server render, change this line app/views/hello_world/index.html.erb to
62
- `prerender: true` to see server rendering (right click on page and select "view source").
123
+ def detect_process_manager
124
+ if system("which overmind > /dev/null 2>&1")
125
+ "overmind"
126
+ elsif system("which foreman > /dev/null 2>&1")
127
+ "foreman"
128
+ end
129
+ end
63
130
 
64
- <%= react_component("HelloWorldApp", props: @hello_world_props, prerender: true) %>
131
+ def build_shakapacker_status_section
132
+ version_warning = check_shakapacker_version_warning
133
+
134
+ if File.exist?(".shakapacker_just_installed")
135
+ base_message = <<~SHAKAPACKER
136
+
137
+ 📦 SHAKAPACKER SETUP:
138
+ ─────────────────────────────────────────────────────────────────────────
139
+ #{Rainbow('✓ Added to Gemfile automatically').green}
140
+ #{Rainbow('✓ Installer ran successfully').green}
141
+ #{Rainbow('✓ Webpack integration configured').green}
142
+ SHAKAPACKER
143
+ base_message + version_warning
144
+ elsif File.exist?("bin/shakapacker") && File.exist?("bin/shakapacker-dev-server")
145
+ "\n📦 #{Rainbow('Shakapacker already configured ✓').green}#{version_warning}"
146
+ else
147
+ "\n📦 #{Rainbow('Shakapacker setup may be incomplete').yellow}#{version_warning}"
148
+ end
149
+ end
65
150
 
66
- Alternative steps to run the app:
151
+ def check_shakapacker_version_warning
152
+ # Try to detect Shakapacker version from Gemfile.lock
153
+ return "" unless File.exist?("Gemfile.lock")
67
154
 
68
- - We recommend using Procfile.dev with foreman, overmind, or a similar program. Alternately, you can run each of the processes listed in Procfile.dev in a separate tab in your terminal.
155
+ gemfile_lock_content = File.read("Gemfile.lock")
156
+ shakapacker_match = gemfile_lock_content.match(/shakapacker \((\d+\.\d+\.\d+)\)/)
69
157
 
70
- - Visit http://localhost:3000/hello_world and see your React On Rails app running!
71
- MSG
158
+ return "" unless shakapacker_match
159
+
160
+ version = shakapacker_match[1]
161
+ major_version = version.split(".").first.to_i
162
+
163
+ if major_version < 8
164
+ <<~WARNING
165
+
166
+ ⚠️ #{Rainbow('IMPORTANT: Upgrade Recommended').yellow.bold}
167
+ ─────────────────────────────────────────────────────────────────────────
168
+ You are using Shakapacker #{version}. React on Rails v15+ works best with
169
+ Shakapacker 8.0+ for optimal Hot Module Replacement and build performance.
170
+
171
+ To upgrade: #{Rainbow('bundle update shakapacker').cyan}
172
+
173
+ Learn more: #{Rainbow('https://github.com/shakacode/shakapacker').cyan.underline}
174
+ WARNING
175
+ else
176
+ ""
177
+ end
178
+ rescue StandardError
179
+ # If version detection fails, don't show a warning to avoid noise
180
+ ""
181
+ end
182
+
183
+ def detect_package_manager
184
+ # Check for lock files to determine package manager
185
+ if File.exist?("yarn.lock")
186
+ "yarn"
187
+ elsif File.exist?("pnpm-lock.yaml")
188
+ "pnpm"
189
+ else
190
+ # Default to npm (Shakapacker 8.x default) - covers package-lock.json and no lockfile
191
+ "npm"
192
+ end
72
193
  end
73
194
  end
74
195
  end