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.
- checksums.yaml +4 -4
- data/.rspec +2 -0
- data/.rubocop.yml +85 -0
- data/Gemfile.development_dependencies +8 -7
- data/Gemfile.lock +158 -119
- data/Steepfile +56 -0
- data/lib/generators/react_on_rails/base_generator.rb +43 -120
- data/lib/generators/react_on_rails/dev_tests_generator.rb +2 -1
- data/lib/generators/react_on_rails/generator_helper.rb +102 -2
- data/lib/generators/react_on_rails/install_generator.rb +36 -156
- data/lib/generators/react_on_rails/js_dependency_manager.rb +383 -0
- data/lib/generators/react_on_rails/templates/base/base/.dev-services.yml.example +76 -0
- data/lib/generators/react_on_rails/templates/base/base/bin/shakapacker-precompile-hook +30 -0
- data/lib/generators/react_on_rails/templates/base/base/bin/switch-bundler +141 -0
- data/lib/generators/react_on_rails/templates/base/base/config/initializers/react_on_rails.rb.tt +44 -45
- data/lib/generators/react_on_rails/templates/base/base/config/{shakapacker.yml → shakapacker.yml.tt} +28 -3
- data/lib/generators/react_on_rails/templates/base/base/config/webpack/development.js.tt +15 -9
- data/lib/generators/react_on_rails/templates/base/base/config/webpack/serverWebpackConfig.js.tt +42 -6
- data/lib/react_on_rails/configuration.rb +149 -32
- data/lib/react_on_rails/controller.rb +3 -3
- data/lib/react_on_rails/dev/pack_generator.rb +168 -2
- data/lib/react_on_rails/dev/process_manager.rb +136 -14
- data/lib/react_on_rails/dev/server_manager.rb +194 -26
- data/lib/react_on_rails/dev/service_checker.rb +200 -0
- data/lib/react_on_rails/doctor.rb +341 -12
- data/lib/react_on_rails/engine.rb +75 -1
- data/lib/react_on_rails/git_utils.rb +3 -1
- data/lib/react_on_rails/helper.rb +70 -192
- data/lib/react_on_rails/locales/base.rb +17 -5
- data/lib/react_on_rails/packer_utils.rb +79 -2
- data/lib/react_on_rails/packs_generator.rb +57 -39
- data/lib/react_on_rails/prerender_error.rb +74 -17
- data/lib/react_on_rails/pro_helper.rb +64 -0
- data/lib/react_on_rails/react_component/render_options.rb +7 -7
- data/lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb +2 -5
- data/lib/react_on_rails/smart_error.rb +326 -0
- data/lib/react_on_rails/system_checker.rb +8 -9
- data/lib/react_on_rails/test_helper/webpack_assets_status_checker.rb +16 -7
- data/lib/react_on_rails/utils.rb +241 -55
- data/lib/react_on_rails/version.rb +1 -1
- data/lib/react_on_rails/version_checker.rb +383 -35
- data/lib/tasks/generate_packs.rake +12 -6
- data/lib/tasks/locale.rake +6 -1
- data/rakelib/docker.rake +26 -0
- data/rakelib/dummy_apps.rake +30 -0
- data/rakelib/example_type.rb +121 -0
- data/rakelib/examples_config.yml +52 -0
- data/rakelib/lint.rake +52 -0
- data/rakelib/node_package.rake +15 -0
- data/rakelib/rbs.rake +70 -0
- data/rakelib/run_rspec.rake +223 -0
- data/rakelib/shakapacker_examples.rake +171 -0
- data/rakelib/task_helpers.rb +134 -0
- data/rakelib/update_changelog.rake +73 -0
- data/react_on_rails.gemspec +4 -3
- data/sig/README.md +52 -0
- data/sig/react_on_rails/configuration.rbs +96 -0
- data/sig/react_on_rails/controller.rbs +15 -0
- data/sig/react_on_rails/dev/file_manager.rbs +15 -0
- data/sig/react_on_rails/dev/pack_generator.rbs +19 -0
- data/sig/react_on_rails/dev/process_manager.rbs +22 -0
- data/sig/react_on_rails/dev/server_manager.rbs +39 -0
- data/sig/react_on_rails/dev/service_checker.rbs +22 -0
- data/sig/react_on_rails/error.rbs +4 -0
- data/sig/react_on_rails/generators/js_dependency_manager.rbs +123 -0
- data/sig/react_on_rails/git_utils.rbs +8 -0
- data/sig/react_on_rails/helper.rbs +65 -0
- data/sig/react_on_rails/json_parse_error.rbs +10 -0
- data/sig/react_on_rails/locales.rbs +46 -0
- data/sig/react_on_rails/packer_utils.rbs +15 -0
- data/sig/react_on_rails/prerender_error.rbs +21 -0
- data/sig/react_on_rails/server_rendering_pool.rbs +12 -0
- data/sig/react_on_rails/smart_error.rbs +28 -0
- data/sig/react_on_rails/test_helper.rbs +11 -0
- data/sig/react_on_rails/utils.rbs +34 -0
- data/sig/react_on_rails/version_checker.rbs +12 -0
- data/sig/react_on_rails.rbs +17 -0
- metadata +49 -32
- data/AI_AGENT_INSTRUCTIONS.md +0 -63
- data/CHANGELOG.md +0 -1836
- data/CLAUDE.md +0 -135
- data/CODING_AGENTS.md +0 -313
- data/CONTRIBUTING.md +0 -668
- data/Dockerfile_tests +0 -12
- data/KUDOS.md +0 -114
- data/LICENSE.md +0 -47
- data/LICENSES/README.md +0 -14
- data/NEWS.md +0 -62
- data/PROJECTS.md +0 -63
- data/REACT-ON-RAILS-PRO-LICENSE.md +0 -129
- data/README.md +0 -217
- data/SUMMARY.md +0 -88
- data/TODO.md +0 -135
- data/bin/lefthook/check-trailing-newlines +0 -38
- data/bin/lefthook/get-changed-files +0 -26
- data/bin/lefthook/prettier-format +0 -26
- data/bin/lefthook/ruby-autofix +0 -26
- data/bin/lefthook/ruby-lint +0 -27
- data/docker-compose.yml +0 -11
- data/eslint.config.ts +0 -232
- data/knip.ts +0 -114
- data/lib/react_on_rails/pro/NOTICE +0 -21
- data/lib/react_on_rails/pro/helper.rb +0 -122
- data/lib/react_on_rails/pro/utils.rb +0 -53
- data/tsconfig.eslint.json +0 -6
- 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 '
|
|
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 '
|
|
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 '
|
|
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 '
|
|
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
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
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
|
-
|
|
287
|
-
|
|
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
|
-
|
|
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("
|
|
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
|
-
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|