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
@@ -16,43 +16,51 @@ module ReactOnRails
16
16
  @instance ||= PacksGenerator.new
17
17
  end
18
18
 
19
+ def react_on_rails_npm_package
20
+ return "react-on-rails-pro" if ReactOnRails::Utils.react_on_rails_pro?
21
+
22
+ "react-on-rails"
23
+ end
24
+
19
25
  def generate_packs_if_stale
20
26
  return unless ReactOnRails.configuration.auto_load_bundle
21
27
 
28
+ verbose = ENV["REACT_ON_RAILS_VERBOSE"] == "true"
29
+
22
30
  add_generated_pack_to_server_bundle
23
31
 
24
32
  # Clean any non-generated files from directories
25
- clean_non_generated_files_with_feedback
33
+ clean_non_generated_files_with_feedback(verbose: verbose)
26
34
 
27
35
  are_generated_files_present_and_up_to_date = Dir.exist?(generated_packs_directory_path) &&
28
36
  File.exist?(generated_server_bundle_file_path) &&
29
37
  !stale_or_missing_packs?
30
38
 
31
39
  if are_generated_files_present_and_up_to_date
32
- puts Rainbow("✅ Generated packs are up to date, no regeneration needed").green
40
+ puts Rainbow("✅ Generated packs are up to date, no regeneration needed").green if verbose
33
41
  return
34
42
  end
35
43
 
36
- clean_generated_directories_with_feedback
37
- generate_packs
44
+ clean_generated_directories_with_feedback(verbose: verbose)
45
+ generate_packs(verbose: verbose)
38
46
  end
39
47
 
40
48
  private
41
49
 
42
- def generate_packs
43
- common_component_to_path.each_value { |component_path| create_pack(component_path) }
44
- client_component_to_path.each_value { |component_path| create_pack(component_path) }
50
+ def generate_packs(verbose: false)
51
+ common_component_to_path.each_value { |component_path| create_pack(component_path, verbose: verbose) }
52
+ client_component_to_path.each_value { |component_path| create_pack(component_path, verbose: verbose) }
45
53
 
46
- create_server_pack if ReactOnRails.configuration.server_bundle_js_file.present?
54
+ create_server_pack(verbose: verbose) if ReactOnRails.configuration.server_bundle_js_file.present?
47
55
  end
48
56
 
49
- def create_pack(file_path)
57
+ def create_pack(file_path, verbose: false)
50
58
  output_path = generated_pack_path(file_path)
51
59
  content = pack_file_contents(file_path)
52
60
 
53
61
  File.write(output_path, content)
54
62
 
55
- puts(Rainbow("Generated Packs: #{output_path}").yellow)
63
+ puts(Rainbow("Generated Packs: #{output_path}").yellow) if verbose
56
64
  end
57
65
 
58
66
  def first_js_statement_in_code(content) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
@@ -104,7 +112,7 @@ module ReactOnRails
104
112
 
105
113
  if load_server_components && !client_entrypoint?(file_path)
106
114
  return <<~FILE_CONTENT.strip
107
- import registerServerComponent from 'react-on-rails/registerServerComponent/client';
115
+ import registerServerComponent from '#{react_on_rails_npm_package}/registerServerComponent/client';
108
116
 
109
117
  registerServerComponent("#{registered_component_name}");
110
118
  FILE_CONTENT
@@ -113,30 +121,30 @@ module ReactOnRails
113
121
  relative_component_path = relative_component_path_from_generated_pack(file_path)
114
122
 
115
123
  <<~FILE_CONTENT.strip
116
- import ReactOnRails from 'react-on-rails/client';
124
+ import ReactOnRails from '#{react_on_rails_npm_package}/client';
117
125
  import #{registered_component_name} from '#{relative_component_path}';
118
126
 
119
127
  ReactOnRails.register({#{registered_component_name}});
120
128
  FILE_CONTENT
121
129
  end
122
130
 
123
- def create_server_pack
131
+ def create_server_pack(verbose: false)
124
132
  File.write(generated_server_bundle_file_path, generated_server_pack_file_content)
125
133
 
126
134
  add_generated_pack_to_server_bundle
127
- puts(Rainbow("Generated Server Bundle: #{generated_server_bundle_file_path}").orange)
135
+ puts(Rainbow("Generated Server Bundle: #{generated_server_bundle_file_path}").orange) if verbose
128
136
  end
129
137
 
130
138
  def build_server_pack_content(component_on_server_imports, server_components, client_components)
131
139
  content = <<~FILE_CONTENT
132
- import ReactOnRails from 'react-on-rails';
140
+ import ReactOnRails from '#{react_on_rails_npm_package}';
133
141
 
134
142
  #{component_on_server_imports.join("\n")}\n
135
143
  FILE_CONTENT
136
144
 
137
145
  if server_components.any?
138
146
  content += <<~FILE_CONTENT
139
- import registerServerComponent from 'react-on-rails/registerServerComponent/server';
147
+ import registerServerComponent from '#{react_on_rails_npm_package}/registerServerComponent/server';
140
148
  registerServerComponent({#{server_components.join(",\n")}});\n
141
149
  FILE_CONTENT
142
150
  end
@@ -194,17 +202,17 @@ module ReactOnRails
194
202
  "#{generated_nonentrypoints_path}/#{generated_server_bundle_file_name}.js"
195
203
  end
196
204
 
197
- def clean_non_generated_files_with_feedback
205
+ def clean_non_generated_files_with_feedback(verbose: false)
198
206
  directories_to_clean = [generated_packs_directory_path, generated_server_bundle_directory_path].compact.uniq
199
207
  expected_files = build_expected_files_set
200
208
 
201
- puts Rainbow("🧹 Cleaning non-generated files...").yellow
209
+ puts Rainbow("🧹 Cleaning non-generated files...").yellow if verbose
202
210
 
203
211
  total_deleted = directories_to_clean.sum do |dir_path|
204
- clean_unexpected_files_from_directory(dir_path, expected_files)
212
+ clean_unexpected_files_from_directory(dir_path, expected_files, verbose: verbose)
205
213
  end
206
214
 
207
- display_cleanup_summary(total_deleted)
215
+ display_cleanup_summary(total_deleted, verbose: verbose) if verbose
208
216
  end
209
217
 
210
218
  def build_expected_files_set
@@ -219,17 +227,17 @@ module ReactOnRails
219
227
  { pack_files: expected_pack_files, server_bundle: expected_server_bundle }
220
228
  end
221
229
 
222
- def clean_unexpected_files_from_directory(dir_path, expected_files)
230
+ def clean_unexpected_files_from_directory(dir_path, expected_files, verbose: false)
223
231
  return 0 unless Dir.exist?(dir_path)
224
232
 
225
233
  existing_files = Dir.glob("#{dir_path}/**/*").select { |f| File.file?(f) }
226
234
  unexpected_files = find_unexpected_files(existing_files, dir_path, expected_files)
227
235
 
228
236
  if unexpected_files.any?
229
- delete_unexpected_files(unexpected_files, dir_path)
237
+ delete_unexpected_files(unexpected_files, dir_path, verbose: verbose)
230
238
  unexpected_files.length
231
239
  else
232
- puts Rainbow(" No unexpected files found in #{dir_path}").cyan
240
+ puts Rainbow(" No unexpected files found in #{dir_path}").cyan if verbose
233
241
  0
234
242
  end
235
243
  end
@@ -244,15 +252,21 @@ module ReactOnRails
244
252
  end
245
253
  end
246
254
 
247
- def delete_unexpected_files(unexpected_files, dir_path)
248
- puts Rainbow(" Deleting #{unexpected_files.length} unexpected files from #{dir_path}:").cyan
249
- unexpected_files.each do |file|
250
- puts Rainbow(" - #{File.basename(file)}").blue
251
- File.delete(file)
255
+ def delete_unexpected_files(unexpected_files, dir_path, verbose: false)
256
+ if verbose
257
+ puts Rainbow(" Deleting #{unexpected_files.length} unexpected files from #{dir_path}:").cyan
258
+ unexpected_files.each do |file|
259
+ puts Rainbow(" - #{File.basename(file)}").blue
260
+ File.delete(file)
261
+ end
262
+ else
263
+ unexpected_files.each { |file| File.delete(file) }
252
264
  end
253
265
  end
254
266
 
255
- def display_cleanup_summary(total_deleted)
267
+ def display_cleanup_summary(total_deleted, verbose: false)
268
+ return unless verbose
269
+
256
270
  if total_deleted.positive?
257
271
  puts Rainbow("🗑️ Deleted #{total_deleted} unexpected files total").red
258
272
  else
@@ -260,15 +274,17 @@ module ReactOnRails
260
274
  end
261
275
  end
262
276
 
263
- def clean_generated_directories_with_feedback
277
+ def clean_generated_directories_with_feedback(verbose: false)
264
278
  directories_to_clean = [
265
279
  generated_packs_directory_path,
266
280
  generated_server_bundle_directory_path
267
281
  ].compact.uniq
268
282
 
269
- puts Rainbow("🧹 Cleaning generated directories...").yellow
283
+ puts Rainbow("🧹 Cleaning generated directories...").yellow if verbose
270
284
 
271
- total_deleted = directories_to_clean.sum { |dir_path| clean_directory_with_feedback(dir_path) }
285
+ total_deleted = directories_to_clean.sum { |dir_path| clean_directory_with_feedback(dir_path, verbose: verbose) }
286
+
287
+ return unless verbose
272
288
 
273
289
  if total_deleted.positive?
274
290
  puts Rainbow("🗑️ Deleted #{total_deleted} generated files total").red
@@ -277,27 +293,29 @@ module ReactOnRails
277
293
  end
278
294
  end
279
295
 
280
- def clean_directory_with_feedback(dir_path)
281
- return create_directory_with_feedback(dir_path) unless Dir.exist?(dir_path)
296
+ def clean_directory_with_feedback(dir_path, verbose: false)
297
+ return create_directory_with_feedback(dir_path, verbose: verbose) unless Dir.exist?(dir_path)
282
298
 
283
299
  files = Dir.glob("#{dir_path}/**/*").select { |f| File.file?(f) }
284
300
 
285
301
  if files.any?
286
- puts Rainbow(" Deleting #{files.length} files from #{dir_path}:").cyan
287
- files.each { |file| puts Rainbow(" - #{File.basename(file)}").blue }
302
+ if verbose
303
+ puts Rainbow(" Deleting #{files.length} files from #{dir_path}:").cyan
304
+ files.each { |file| puts Rainbow(" - #{File.basename(file)}").blue }
305
+ end
288
306
  FileUtils.rm_rf(dir_path)
289
307
  FileUtils.mkdir_p(dir_path)
290
308
  files.length
291
309
  else
292
- puts Rainbow(" Directory #{dir_path} is already empty").cyan
310
+ puts Rainbow(" Directory #{dir_path} is already empty").cyan if verbose
293
311
  FileUtils.rm_rf(dir_path)
294
312
  FileUtils.mkdir_p(dir_path)
295
313
  0
296
314
  end
297
315
  end
298
316
 
299
- def create_directory_with_feedback(dir_path)
300
- puts Rainbow(" Directory #{dir_path} does not exist, creating...").cyan
317
+ def create_directory_with_feedback(dir_path, verbose: false)
318
+ puts Rainbow(" Directory #{dir_path} does not exist, creating...").cyan if verbose
301
319
  FileUtils.mkdir_p(dir_path)
302
320
  0
303
321
  end
@@ -47,12 +47,16 @@ module ReactOnRails
47
47
 
48
48
  private
49
49
 
50
+ # rubocop:disable Metrics/AbcSize
50
51
  def calc_message(component_name, console_messages, err, js_code, props)
51
- message = +"ERROR in SERVER PRERENDERING\n"
52
+ header = Rainbow(" React on Rails Server Rendering Error").red.bright
53
+ message = +"#{header}\n\n"
54
+
55
+ message << Rainbow("Component: #{component_name}").yellow << "\n\n"
56
+
52
57
  if err
58
+ message << Rainbow("Error Details:").red.bright << "\n"
53
59
  message << <<~MSG
54
- Encountered error:
55
-
56
60
  #{err.inspect}
57
61
 
58
62
  MSG
@@ -61,33 +65,86 @@ module ReactOnRails
61
65
  err.backtrace.join("\n")
62
66
  else
63
67
  "#{Rails.backtrace_cleaner.clean(err.backtrace).join("\n")}\n" +
64
- Rainbow("The rest of the backtrace is hidden. " \
65
- "To see the full backtrace, set FULL_TEXT_ERRORS=true.").red
68
+ Rainbow("💡 Tip: Set FULL_TEXT_ERRORS=true to see the full backtrace").yellow
66
69
  end
67
70
  else
68
71
  backtrace = nil
69
72
  end
70
- message << <<~MSG
71
- when prerendering #{component_name} with props: #{Utils.smart_trim(props, MAX_ERROR_SNIPPET_TO_LOG)}
72
-
73
- code:
74
-
75
- #{Utils.smart_trim(js_code, MAX_ERROR_SNIPPET_TO_LOG)}
76
73
 
77
- MSG
74
+ # Add props information
75
+ message << Rainbow("Props:").blue.bright << "\n"
76
+ message << "#{Utils.smart_trim(props, MAX_ERROR_SNIPPET_TO_LOG)}\n\n"
78
77
 
79
- if console_messages
80
- message << <<~MSG
81
- console messages:
82
- #{console_messages}
83
- MSG
78
+ # Add code snippet
79
+ message << Rainbow("JavaScript Code:").blue.bright << "\n"
80
+ message << "#{Utils.smart_trim(js_code, MAX_ERROR_SNIPPET_TO_LOG)}\n\n"
84
81
 
82
+ if console_messages && console_messages.strip.present?
83
+ message << Rainbow("Console Output:").magenta.bright << "\n"
84
+ message << "#{console_messages}\n\n"
85
85
  end
86
86
 
87
+ # Add actionable suggestions
88
+ message << Rainbow("💡 Troubleshooting Steps:").yellow.bright << "\n"
89
+ message << build_troubleshooting_suggestions(component_name, err, console_messages)
90
+
87
91
  # Add help and support information
88
92
  message << "\n#{Utils.default_troubleshooting_section}\n"
89
93
 
90
94
  [backtrace, message]
95
+ # rubocop:enable Metrics/AbcSize
96
+ end
97
+
98
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
99
+ def build_troubleshooting_suggestions(component_name, err, console_messages)
100
+ suggestions = []
101
+
102
+ # Check for common error patterns
103
+ if err&.message&.include?("window is not defined") || console_messages&.include?("window is not defined")
104
+ suggestions << <<~SUGGESTION
105
+ 1. Browser API used on server - wrap with client-side check:
106
+ #{Rainbow("if (typeof window !== 'undefined') { ... }").cyan}
107
+ SUGGESTION
108
+ end
109
+
110
+ if err&.message&.include?("document is not defined") || console_messages&.include?("document is not defined")
111
+ suggestions << <<~SUGGESTION
112
+ 1. DOM API used on server - use React refs or useEffect:
113
+ #{Rainbow('useEffect(() => { /* DOM operations here */ }, [])').cyan}
114
+ SUGGESTION
115
+ end
116
+
117
+ if err&.message&.include?("Cannot read") || err&.message&.include?("undefined")
118
+ suggestions << <<~SUGGESTION
119
+ 1. Check for null/undefined values in props
120
+ 2. Add default props or use optional chaining:
121
+ #{Rainbow("props.data?.value || 'default'").cyan}
122
+ SUGGESTION
123
+ end
124
+
125
+ if err&.message&.include?("Hydration") || console_messages&.include?("Hydration")
126
+ suggestions << <<~SUGGESTION
127
+ 1. Server and client render mismatch - ensure consistent:
128
+ - Random values (use seed from props)
129
+ - Date/time values (pass from server)
130
+ - User agent checks (avoid or use props)
131
+ SUGGESTION
132
+ end
133
+
134
+ # Generic suggestions
135
+ suggestions << <<~SUGGESTION
136
+ • Temporarily disable SSR to isolate the issue:
137
+ #{Rainbow('prerender: false').cyan} in your view helper
138
+ • Check server logs for detailed errors:
139
+ #{Rainbow('tail -f log/development.log').cyan}
140
+ • Verify component registration:
141
+ #{Rainbow("ReactOnRails.register({ #{component_name}: #{component_name} })").cyan}
142
+ • Ensure server bundle is up to date:
143
+ #{Rainbow('bin/shakapacker').cyan} or #{Rainbow('yarn run build:server').cyan}
144
+ SUGGESTION
145
+
146
+ suggestions.join("\n")
147
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
91
148
  end
92
149
  end
93
150
  end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ReactOnRails
4
+ module ProHelper
5
+ # Generates the complete component specification script tag.
6
+ # Handles both immediate hydration (Pro feature) and standard cases.
7
+ def generate_component_script(render_options)
8
+ # Setup the page_loaded_js, which is the same regardless of prerendering or not!
9
+ # The reason is that React is smart about not doing extra work if the server rendering did its job.
10
+ component_specification_tag = content_tag(:script,
11
+ json_safe_and_pretty(render_options.client_props).html_safe,
12
+ type: "application/json",
13
+ class: "js-react-on-rails-component",
14
+ id: "js-react-on-rails-component-#{render_options.dom_id}",
15
+ "data-component-name" => render_options.react_component_name,
16
+ "data-trace" => (render_options.trace ? true : nil),
17
+ "data-dom-id" => render_options.dom_id,
18
+ "data-store-dependencies" =>
19
+ render_options.store_dependencies&.to_json,
20
+ "data-immediate-hydration" =>
21
+ (render_options.immediate_hydration ? true : nil))
22
+
23
+ # Add immediate invocation script if immediate hydration is enabled
24
+ spec_tag = if render_options.immediate_hydration
25
+ # Escape dom_id for JavaScript context
26
+ escaped_dom_id = escape_javascript(render_options.dom_id)
27
+ immediate_script = content_tag(:script, %(
28
+ typeof ReactOnRails === 'object' && ReactOnRails.reactOnRailsComponentLoaded('#{escaped_dom_id}');
29
+ ).html_safe)
30
+ "#{component_specification_tag}\n#{immediate_script}"
31
+ else
32
+ component_specification_tag
33
+ end
34
+
35
+ spec_tag.html_safe
36
+ end
37
+
38
+ # Generates the complete store hydration script tag.
39
+ # Handles both immediate hydration (Pro feature) and standard cases.
40
+ def generate_store_script(redux_store_data)
41
+ store_hydration_data = content_tag(:script,
42
+ json_safe_and_pretty(redux_store_data[:props]).html_safe,
43
+ type: "application/json",
44
+ "data-js-react-on-rails-store" => redux_store_data[:store_name].html_safe,
45
+ "data-immediate-hydration" =>
46
+ (redux_store_data[:immediate_hydration] ? true : nil))
47
+
48
+ # Add immediate invocation script if immediate hydration is enabled and Pro license is valid
49
+ store_hydration_scripts = if redux_store_data[:immediate_hydration]
50
+ # Escape store_name for JavaScript context
51
+ escaped_store_name = escape_javascript(redux_store_data[:store_name])
52
+ immediate_script = content_tag(:script, <<~JS.strip_heredoc.html_safe
53
+ typeof ReactOnRails === 'object' && ReactOnRails.reactOnRailsStoreLoaded('#{escaped_store_name}');
54
+ JS
55
+ )
56
+ "#{store_hydration_data}\n#{immediate_script}"
57
+ else
58
+ store_hydration_data
59
+ end
60
+
61
+ store_hydration_scripts.html_safe
62
+ end
63
+ end
64
+ end
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "react_on_rails/utils"
4
- require "react_on_rails/pro/utils"
5
4
 
6
5
  module ReactOnRails
7
6
  module ReactComponent
@@ -15,13 +14,10 @@ module ReactOnRails
15
14
  # TODO: remove the required for named params
16
15
  def initialize(react_component_name: required("react_component_name"), options: required("options"))
17
16
  @react_component_name = react_component_name.camelize
18
-
19
- result = ReactOnRails::Pro::Utils.disable_pro_render_options_if_not_licensed(options)
20
- @options = result[:raw_options]
21
- @explicitly_disabled_pro_options = result[:explicitly_disabled_pro_options]
17
+ @options = options
22
18
  end
23
19
 
24
- attr_reader :react_component_name, :explicitly_disabled_pro_options
20
+ attr_reader :react_component_name
25
21
 
26
22
  def throw_js_errors
27
23
  options.fetch(:throw_js_errors, false)
@@ -100,7 +96,11 @@ module ReactOnRails
100
96
  end
101
97
 
102
98
  def immediate_hydration
103
- retrieve_configuration_value_for(:immediate_hydration)
99
+ ReactOnRails::Utils.normalize_immediate_hydration(
100
+ options[:immediate_hydration],
101
+ react_component_name,
102
+ "Component"
103
+ )
104
104
  end
105
105
 
106
106
  def to_s
@@ -9,11 +9,10 @@ module ReactOnRails
9
9
  class RubyEmbeddedJavaScript
10
10
  class << self
11
11
  def reset_pool
12
- options = {
12
+ @js_context_pool = ConnectionPool.new(
13
13
  size: ReactOnRails.configuration.server_renderer_pool_size,
14
14
  timeout: ReactOnRails.configuration.server_renderer_timeout
15
- }
16
- @js_context_pool = ConnectionPool.new(options) { create_js_context }
15
+ ) { create_js_context }
17
16
  end
18
17
 
19
18
  def reset_pool_if_server_bundle_was_modified
@@ -50,7 +49,6 @@ module ReactOnRails
50
49
  # Note, js_code does not have to be based on React.
51
50
  # js_code MUST RETURN json stringify Object
52
51
  # Calling code will probably call 'html_safe' on return value before rendering to the view.
53
- # rubocop:disable Metrics/CyclomaticComplexity
54
52
  def exec_server_render_js(js_code, render_options, js_evaluator = nil)
55
53
  js_evaluator ||= self
56
54
  if render_options.trace
@@ -87,7 +85,6 @@ module ReactOnRails
87
85
  # We need to parse each chunk and replay the console messages.
88
86
  result.transform { |chunk| parse_result_and_replay_console_messages(chunk, render_options) }
89
87
  end
90
- # rubocop:enable Metrics/CyclomaticComplexity
91
88
 
92
89
  def trace_js_code_used(msg, js_code, file_name = "tmp/server-generated.js", force: false)
93
90
  return unless ReactOnRails.configuration.trace || force