react_on_rails 16.1.2 → 16.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.
Files changed (106) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +2 -0
  3. data/.rubocop.yml +85 -0
  4. data/Gemfile.development_dependencies +8 -7
  5. data/Gemfile.lock +158 -119
  6. data/Steepfile +56 -0
  7. data/lib/generators/react_on_rails/base_generator.rb +43 -120
  8. data/lib/generators/react_on_rails/dev_tests_generator.rb +2 -1
  9. data/lib/generators/react_on_rails/generator_helper.rb +102 -2
  10. data/lib/generators/react_on_rails/install_generator.rb +36 -156
  11. data/lib/generators/react_on_rails/js_dependency_manager.rb +383 -0
  12. data/lib/generators/react_on_rails/templates/base/base/.dev-services.yml.example +76 -0
  13. data/lib/generators/react_on_rails/templates/base/base/bin/shakapacker-precompile-hook +30 -0
  14. data/lib/generators/react_on_rails/templates/base/base/bin/switch-bundler +141 -0
  15. data/lib/generators/react_on_rails/templates/base/base/config/initializers/react_on_rails.rb.tt +44 -45
  16. data/lib/generators/react_on_rails/templates/base/base/config/{shakapacker.yml → shakapacker.yml.tt} +28 -3
  17. data/lib/generators/react_on_rails/templates/base/base/config/webpack/development.js.tt +15 -9
  18. data/lib/generators/react_on_rails/templates/base/base/config/webpack/serverWebpackConfig.js.tt +42 -6
  19. data/lib/react_on_rails/configuration.rb +149 -32
  20. data/lib/react_on_rails/controller.rb +3 -3
  21. data/lib/react_on_rails/dev/pack_generator.rb +168 -2
  22. data/lib/react_on_rails/dev/process_manager.rb +136 -14
  23. data/lib/react_on_rails/dev/server_manager.rb +194 -26
  24. data/lib/react_on_rails/dev/service_checker.rb +200 -0
  25. data/lib/react_on_rails/doctor.rb +341 -12
  26. data/lib/react_on_rails/engine.rb +75 -1
  27. data/lib/react_on_rails/git_utils.rb +3 -1
  28. data/lib/react_on_rails/helper.rb +70 -192
  29. data/lib/react_on_rails/locales/base.rb +17 -5
  30. data/lib/react_on_rails/packer_utils.rb +79 -2
  31. data/lib/react_on_rails/packs_generator.rb +57 -39
  32. data/lib/react_on_rails/prerender_error.rb +74 -17
  33. data/lib/react_on_rails/pro_helper.rb +64 -0
  34. data/lib/react_on_rails/react_component/render_options.rb +7 -7
  35. data/lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb +2 -5
  36. data/lib/react_on_rails/smart_error.rb +326 -0
  37. data/lib/react_on_rails/system_checker.rb +8 -9
  38. data/lib/react_on_rails/test_helper/webpack_assets_status_checker.rb +16 -7
  39. data/lib/react_on_rails/utils.rb +241 -55
  40. data/lib/react_on_rails/version.rb +1 -1
  41. data/lib/react_on_rails/version_checker.rb +383 -35
  42. data/lib/tasks/generate_packs.rake +12 -6
  43. data/lib/tasks/locale.rake +6 -1
  44. data/rakelib/docker.rake +26 -0
  45. data/rakelib/dummy_apps.rake +30 -0
  46. data/rakelib/example_type.rb +121 -0
  47. data/rakelib/examples_config.yml +52 -0
  48. data/rakelib/lint.rake +52 -0
  49. data/rakelib/node_package.rake +15 -0
  50. data/rakelib/rbs.rake +70 -0
  51. data/rakelib/run_rspec.rake +223 -0
  52. data/rakelib/shakapacker_examples.rake +171 -0
  53. data/rakelib/task_helpers.rb +134 -0
  54. data/rakelib/update_changelog.rake +73 -0
  55. data/react_on_rails.gemspec +4 -3
  56. data/sig/README.md +52 -0
  57. data/sig/react_on_rails/configuration.rbs +96 -0
  58. data/sig/react_on_rails/controller.rbs +15 -0
  59. data/sig/react_on_rails/dev/file_manager.rbs +15 -0
  60. data/sig/react_on_rails/dev/pack_generator.rbs +19 -0
  61. data/sig/react_on_rails/dev/process_manager.rbs +22 -0
  62. data/sig/react_on_rails/dev/server_manager.rbs +39 -0
  63. data/sig/react_on_rails/dev/service_checker.rbs +22 -0
  64. data/sig/react_on_rails/error.rbs +4 -0
  65. data/sig/react_on_rails/generators/js_dependency_manager.rbs +123 -0
  66. data/sig/react_on_rails/git_utils.rbs +8 -0
  67. data/sig/react_on_rails/helper.rbs +65 -0
  68. data/sig/react_on_rails/json_parse_error.rbs +10 -0
  69. data/sig/react_on_rails/locales.rbs +46 -0
  70. data/sig/react_on_rails/packer_utils.rbs +15 -0
  71. data/sig/react_on_rails/prerender_error.rbs +21 -0
  72. data/sig/react_on_rails/server_rendering_pool.rbs +12 -0
  73. data/sig/react_on_rails/smart_error.rbs +28 -0
  74. data/sig/react_on_rails/test_helper.rbs +11 -0
  75. data/sig/react_on_rails/utils.rbs +34 -0
  76. data/sig/react_on_rails/version_checker.rbs +12 -0
  77. data/sig/react_on_rails.rbs +17 -0
  78. metadata +49 -32
  79. data/AI_AGENT_INSTRUCTIONS.md +0 -63
  80. data/CHANGELOG.md +0 -1836
  81. data/CLAUDE.md +0 -135
  82. data/CODING_AGENTS.md +0 -313
  83. data/CONTRIBUTING.md +0 -668
  84. data/Dockerfile_tests +0 -12
  85. data/KUDOS.md +0 -114
  86. data/LICENSE.md +0 -47
  87. data/LICENSES/README.md +0 -14
  88. data/NEWS.md +0 -62
  89. data/PROJECTS.md +0 -63
  90. data/REACT-ON-RAILS-PRO-LICENSE.md +0 -129
  91. data/README.md +0 -217
  92. data/SUMMARY.md +0 -88
  93. data/TODO.md +0 -135
  94. data/bin/lefthook/check-trailing-newlines +0 -38
  95. data/bin/lefthook/get-changed-files +0 -26
  96. data/bin/lefthook/prettier-format +0 -26
  97. data/bin/lefthook/ruby-autofix +0 -26
  98. data/bin/lefthook/ruby-lint +0 -27
  99. data/docker-compose.yml +0 -11
  100. data/eslint.config.ts +0 -232
  101. data/knip.ts +0 -114
  102. data/lib/react_on_rails/pro/NOTICE +0 -21
  103. data/lib/react_on_rails/pro/helper.rb +0 -122
  104. data/lib/react_on_rails/pro/utils.rb +0 -53
  105. data/tsconfig.eslint.json +0 -6
  106. data/tsconfig.json +0 -19
@@ -0,0 +1,326 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rainbow"
4
+
5
+ module ReactOnRails
6
+ # SmartError provides enhanced error messages with actionable suggestions
7
+ # rubocop:disable Metrics/ClassLength
8
+ class SmartError < Error
9
+ attr_reader :component_name, :error_type, :props, :js_code, :additional_context
10
+
11
+ def initialize(error_type:, component_name: nil, props: nil, js_code: nil, **additional_context)
12
+ @error_type = error_type
13
+ @component_name = component_name
14
+ @props = props
15
+ @js_code = js_code
16
+ @additional_context = additional_context
17
+
18
+ message = build_error_message
19
+ super(message)
20
+ end
21
+
22
+ def solution
23
+ case error_type
24
+ when :component_not_registered
25
+ component_not_registered_solution
26
+ when :missing_auto_loaded_bundle
27
+ missing_auto_loaded_bundle_solution
28
+ when :hydration_mismatch
29
+ hydration_mismatch_solution
30
+ when :server_rendering_error
31
+ server_rendering_error_solution
32
+ when :redux_store_not_found
33
+ redux_store_not_found_solution
34
+ when :configuration_error
35
+ configuration_error_solution
36
+ else
37
+ default_solution
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def build_error_message
44
+ header = Rainbow("❌ React on Rails Error: #{error_type_title}").red.bright
45
+
46
+ message = <<~MSG
47
+ #{header}
48
+
49
+ #{error_description}
50
+
51
+ #{Rainbow('💡 Suggested Solution:').yellow.bright}
52
+ #{solution}
53
+
54
+ #{additional_info}
55
+ #{troubleshooting_section}
56
+ MSG
57
+
58
+ message.strip
59
+ end
60
+
61
+ def error_type_title
62
+ case error_type
63
+ when :component_not_registered
64
+ "Component '#{component_name}' Not Registered"
65
+ when :missing_auto_loaded_bundle
66
+ "Auto-loaded Bundle Missing"
67
+ when :hydration_mismatch
68
+ "Hydration Mismatch"
69
+ when :server_rendering_error
70
+ "Server Rendering Failed"
71
+ when :redux_store_not_found
72
+ "Redux Store Not Found"
73
+ when :configuration_error
74
+ "Configuration Error"
75
+ else
76
+ "Unknown Error"
77
+ end
78
+ end
79
+
80
+ # rubocop:disable Metrics/CyclomaticComplexity
81
+ def error_description
82
+ case error_type
83
+ when :component_not_registered
84
+ <<~DESC
85
+ Component '#{component_name}' was not found in the component registry.
86
+
87
+ React on Rails offers two approaches:
88
+ • Auto-bundling (recommended): Components load automatically, no registration needed
89
+ • Manual registration: Traditional approach requiring explicit registration
90
+ DESC
91
+ when :missing_auto_loaded_bundle
92
+ <<~DESC
93
+ Component '#{component_name}' is configured for auto-loading but its bundle is missing.
94
+ Expected location: #{additional_context[:expected_path]}
95
+ DESC
96
+ when :hydration_mismatch
97
+ <<~DESC
98
+ The server-rendered HTML doesn't match what React rendered on the client.
99
+ Component: #{component_name}
100
+ DESC
101
+ when :server_rendering_error
102
+ <<~DESC
103
+ An error occurred while server-side rendering component '#{component_name}'.
104
+ #{additional_context[:error_message]}
105
+ DESC
106
+ when :redux_store_not_found
107
+ <<~DESC
108
+ Redux store '#{additional_context[:store_name]}' was not found.
109
+ Available stores: #{additional_context[:available_stores]&.join(', ') || 'none'}
110
+ DESC
111
+ when :configuration_error
112
+ <<~DESC
113
+ Invalid configuration detected.
114
+ #{additional_context[:details]}
115
+ DESC
116
+ else
117
+ "An unexpected error occurred."
118
+ end
119
+ end
120
+
121
+ # rubocop:enable Metrics/CyclomaticComplexity
122
+
123
+ # rubocop:disable Metrics/AbcSize
124
+ def component_not_registered_solution
125
+ suggestions = []
126
+
127
+ # Check for similar component names
128
+ if component_name && !component_name.empty?
129
+ similar = find_similar_components(component_name)
130
+ suggestions << "Did you mean one of these? #{similar.map { |s| Rainbow(s).green }.join(', ')}" if similar.any?
131
+ end
132
+
133
+ suggestions << <<~SOLUTION
134
+ #{Rainbow('🚀 Recommended: Use Auto-Bundling (No Registration Required!)').green.bright}
135
+
136
+ 1. Enable auto-bundling in your view:
137
+ #{Rainbow("<%= react_component(\"#{component_name}\", props: {}, auto_load_bundle: true) %>").cyan}
138
+
139
+ 2. Place your component in the components directory:
140
+ #{Rainbow("app/javascript/#{ReactOnRails.configuration.components_subdirectory || 'components'}/#{component_name}/#{component_name}.jsx").cyan}
141
+ #{' '}
142
+ Component structure:
143
+ #{Rainbow("#{ReactOnRails.configuration.components_subdirectory || 'components'}/").cyan}
144
+ #{Rainbow("└── #{component_name}/").cyan}
145
+ #{Rainbow(" └── #{component_name}.jsx").cyan} (must export default)
146
+
147
+ 3. Generate the bundle:
148
+ #{Rainbow('bundle exec rake react_on_rails:generate_packs').cyan}
149
+
150
+ #{Rainbow("✨ That's it! No manual registration needed.").yellow}
151
+
152
+ ─────────────────────────────────────────────
153
+
154
+ #{Rainbow('Alternative: Manual Registration').gray}
155
+
156
+ If you prefer manual registration:
157
+ 1. Register in your entry file:
158
+ #{Rainbow("ReactOnRails.register({ #{component_name}: #{component_name} });").cyan}
159
+
160
+ 2. Import the component:
161
+ #{Rainbow("import #{component_name} from './components/#{component_name}';").cyan}
162
+ SOLUTION
163
+
164
+ suggestions.join("\n")
165
+ end
166
+
167
+ # rubocop:enable Metrics/AbcSize
168
+ def missing_auto_loaded_bundle_solution
169
+ <<~SOLUTION
170
+ 1. Run the pack generation task:
171
+ #{Rainbow('bundle exec rake react_on_rails:generate_packs').cyan}
172
+
173
+ 2. Ensure your component is in the correct directory:
174
+ #{Rainbow("app/javascript/#{ReactOnRails.configuration.components_subdirectory || 'components'}/#{component_name}/").cyan}
175
+
176
+ 3. Check that the component file follows naming conventions:
177
+ - Component file: #{Rainbow("#{component_name}.jsx").cyan} or #{Rainbow("#{component_name}.tsx").cyan}
178
+ - Must export default
179
+
180
+ 4. Verify webpack/shakapacker is configured for nested entries:
181
+ #{Rainbow("config.nested_entries_dir = 'components'").cyan}
182
+ SOLUTION
183
+ end
184
+
185
+ def hydration_mismatch_solution
186
+ <<~SOLUTION
187
+ Common causes and solutions:
188
+
189
+ 1. **Random IDs or timestamps**: Use consistent values between server and client
190
+ #{Rainbow('// Bad: Math.random() or Date.now()').red}
191
+ #{Rainbow('// Good: Use props or deterministic values').green}
192
+
193
+ 2. **Browser-only APIs**: Check for client-side before using:
194
+ #{Rainbow("if (typeof window !== 'undefined') { ... }").cyan}
195
+
196
+ 3. **Different data**: Ensure props are identical on server and client
197
+ - Check your redux store initialization
198
+ - Verify railsContext is consistent
199
+
200
+ 4. **Conditional rendering**: Avoid using user agent or viewport checks
201
+
202
+ Debug tips:
203
+ - Set #{Rainbow('prerender: false').cyan} temporarily to isolate the issue
204
+ - Check browser console for hydration warnings
205
+ - Compare server HTML with client render
206
+ SOLUTION
207
+ end
208
+
209
+ def server_rendering_error_solution
210
+ <<~SOLUTION
211
+ 1. Check your JavaScript console output:
212
+ #{Rainbow("tail -f log/development.log | grep 'React on Rails'").cyan}
213
+
214
+ 2. Common issues:
215
+ - Missing Node.js dependencies: #{Rainbow('cd client && npm install').cyan}
216
+ - Syntax errors in component code
217
+ - Using browser-only APIs without checks
218
+
219
+ 3. Debug server rendering:
220
+ - Set #{Rainbow('config.trace = true').cyan} in your configuration
221
+ - Set #{Rainbow('config.development_mode = true').cyan} for better errors
222
+ - Check #{Rainbow('config.server_bundle_js_file').cyan} points to correct file
223
+
224
+ 4. Verify your server bundle:
225
+ #{Rainbow('bin/shakapacker').cyan} or #{Rainbow('bin/webpack').cyan}
226
+ SOLUTION
227
+ end
228
+
229
+ def redux_store_not_found_solution
230
+ <<~SOLUTION
231
+ 1. Register your Redux store:
232
+ #{Rainbow("ReactOnRails.registerStore({ #{additional_context[:store_name]}: #{additional_context[:store_name]} });").cyan}
233
+
234
+ 2. Ensure the store is imported:
235
+ #{Rainbow("import #{additional_context[:store_name]} from './store/#{additional_context[:store_name]}';").cyan}
236
+
237
+ 3. Initialize the store before rendering components that depend on it:
238
+ #{Rainbow("<%= redux_store('#{additional_context[:store_name]}', props: {}) %>").cyan}
239
+
240
+ 4. Check store dependencies in your component:
241
+ #{Rainbow("store_dependencies: ['#{additional_context[:store_name]}']").cyan}
242
+ SOLUTION
243
+ end
244
+
245
+ def configuration_error_solution
246
+ <<~SOLUTION
247
+ Review your React on Rails configuration:
248
+
249
+ 1. Check #{Rainbow('config/initializers/react_on_rails.rb').cyan}
250
+
251
+ 2. Common configuration issues:
252
+ - Invalid bundle paths
253
+ - Missing Node modules location
254
+ - Incorrect component subdirectory
255
+
256
+ 3. Run configuration doctor:
257
+ #{Rainbow('rake react_on_rails:doctor').cyan}
258
+ SOLUTION
259
+ end
260
+
261
+ def default_solution
262
+ <<~SOLUTION
263
+ 1. Check the browser console for JavaScript errors
264
+ 2. Review your server logs: #{Rainbow('tail -f log/development.log').cyan}
265
+ 3. Run diagnostics: #{Rainbow('rake react_on_rails:doctor').cyan}
266
+ 4. Set #{Rainbow('FULL_TEXT_ERRORS=true').cyan} for complete error output
267
+ SOLUTION
268
+ end
269
+
270
+ # rubocop:disable Metrics/AbcSize
271
+ def additional_info
272
+ info = []
273
+
274
+ info << "#{Rainbow('Component:').blue} #{component_name}" if component_name
275
+
276
+ if additional_context[:available_components]&.any?
277
+ info << "#{Rainbow('Registered components:').blue} #{additional_context[:available_components].join(', ')}"
278
+ end
279
+
280
+ info << "#{Rainbow('Rails Environment:').blue} development (detailed errors enabled)" if Rails.env.development?
281
+
282
+ info << "#{Rainbow('Auto-load bundles:').blue} enabled" if ReactOnRails.configuration.auto_load_bundle
283
+
284
+ return "" if info.empty?
285
+
286
+ "\n#{Rainbow('📋 Context:').blue.bright}\n#{info.join("\n")}"
287
+ end
288
+
289
+ # rubocop:enable Metrics/AbcSize
290
+ def troubleshooting_section
291
+ "\n#{Rainbow('🔧 Need More Help?').magenta.bright}\n#{Utils.default_troubleshooting_section}"
292
+ end
293
+
294
+ # rubocop:disable Metrics/CyclomaticComplexity
295
+ def find_similar_components(name)
296
+ return [] unless additional_context[:available_components]
297
+
298
+ available = additional_context[:available_components]
299
+ return [] if available.empty?
300
+
301
+ available = available.uniq
302
+
303
+ # Simple similarity check - could be enhanced with Levenshtein distance
304
+ similar = available.select do |comp|
305
+ comp.downcase.include?(name.downcase) || name.downcase.include?(comp.downcase)
306
+ end
307
+
308
+ # Also check for common naming patterns
309
+ if similar.empty?
310
+ # Check if user forgot to capitalize
311
+ capitalized = name.capitalize
312
+ similar = available.select { |comp| comp == capitalized }
313
+
314
+ # Check for common suffixes
315
+ if similar.empty? && !name.end_with?("Component")
316
+ with_suffix = "#{name}Component"
317
+ similar = available.select { |comp| comp == with_suffix }
318
+ end
319
+ end
320
+
321
+ similar.take(3) # Limit suggestions
322
+ # rubocop:enable Metrics/CyclomaticComplexity
323
+ end
324
+ end
325
+ # rubocop:enable Metrics/ClassLength
326
+ end
@@ -203,7 +203,7 @@ module ReactOnRails
203
203
  add_warning("⚠️ Could not parse package.json")
204
204
  end
205
205
 
206
- def check_package_version_sync # rubocop:disable Metrics/CyclomaticComplexity
206
+ def check_package_version_sync
207
207
  return unless File.exist?("package.json")
208
208
 
209
209
  begin
@@ -213,17 +213,20 @@ module ReactOnRails
213
213
 
214
214
  return unless npm_version && defined?(ReactOnRails::VERSION)
215
215
 
216
- # Clean version strings for comparison (remove ^, ~, =, etc.)
217
- clean_npm_version = npm_version.gsub(/[^0-9.]/, "")
216
+ # Normalize NPM version format to Ruby gem format for comparison
217
+ # Uses existing VersionSyntaxConverter to handle dash/dot differences
218
+ # (e.g., "16.2.0-beta.10" → "16.2.0.beta.10")
219
+ converter = ReactOnRails::VersionSyntaxConverter.new
220
+ normalized_npm_version = converter.npm_to_rubygem(npm_version)
218
221
  gem_version = ReactOnRails::VERSION
219
222
 
220
- if clean_npm_version == gem_version
223
+ if normalized_npm_version == gem_version
221
224
  add_success("✅ React on Rails gem and NPM package versions match (#{gem_version})")
222
225
  check_version_patterns(npm_version, gem_version)
223
226
  else
224
227
  # Check for major version differences
225
228
  gem_major = gem_version.split(".")[0].to_i
226
- npm_major = clean_npm_version.split(".")[0].to_i
229
+ npm_major = normalized_npm_version.split(".")[0].to_i
227
230
 
228
231
  if gem_major != npm_major # rubocop:disable Style/NegatedIfElseCondition
229
232
  add_error(<<~MSG.strip)
@@ -537,7 +540,6 @@ module ReactOnRails
537
540
  MSG
538
541
  end
539
542
 
540
- # rubocop:disable Metrics/CyclomaticComplexity
541
543
  def check_gemfile_version_patterns
542
544
  gemfile_path = ENV["BUNDLE_GEMFILE"] || "Gemfile"
543
545
  return unless File.exist?(gemfile_path)
@@ -568,9 +570,7 @@ module ReactOnRails
568
570
  # Ignore errors reading Gemfile
569
571
  end
570
572
  end
571
- # rubocop:enable Metrics/CyclomaticComplexity
572
573
 
573
- # rubocop:disable Metrics/CyclomaticComplexity
574
574
  def report_dependency_versions(package_json)
575
575
  all_deps = package_json["dependencies"]&.merge(package_json["devDependencies"] || {}) || {}
576
576
 
@@ -587,7 +587,6 @@ module ReactOnRails
587
587
  add_success("✅ React DOM #{react_dom_version}")
588
588
  end
589
589
  end
590
- # rubocop:enable Metrics/CyclomaticComplexity
591
590
 
592
591
  def report_shakapacker_version
593
592
  return unless File.exist?("Gemfile.lock")
@@ -47,16 +47,25 @@ module ReactOnRails
47
47
 
48
48
  private
49
49
 
50
+ def resolve_asset_path(bundle_name)
51
+ # Check if this is a Pro RSC manifest file
52
+ if ReactOnRails::Utils.react_on_rails_pro?
53
+ pro_config = ReactOnRailsPro.configuration
54
+ if bundle_name == pro_config.react_client_manifest_file
55
+ return ReactOnRailsPro::Utils.react_client_manifest_file_path
56
+ end
57
+ if bundle_name == pro_config.react_server_client_manifest_file
58
+ return ReactOnRailsPro::Utils.react_server_client_manifest_file_path
59
+ end
60
+ end
61
+
62
+ ReactOnRails::Utils.bundle_js_file_path(bundle_name)
63
+ end
64
+
50
65
  def all_compiled_assets
51
66
  @all_compiled_assets ||= begin
52
67
  webpack_generated_files = @webpack_generated_files.map do |bundle_name|
53
- if bundle_name == ReactOnRails.configuration.react_client_manifest_file
54
- ReactOnRails::Utils.react_client_manifest_file_path
55
- elsif bundle_name == ReactOnRails.configuration.react_server_client_manifest_file
56
- ReactOnRails::Utils.react_server_client_manifest_file_path
57
- else
58
- ReactOnRails::Utils.bundle_js_file_path(bundle_name)
59
- end
68
+ resolve_asset_path(bundle_name)
60
69
  end
61
70
 
62
71
  if webpack_generated_files.present?