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
|
@@ -154,6 +154,7 @@ module ReactOnRails
|
|
|
154
154
|
def check_configuration_details
|
|
155
155
|
check_shakapacker_configuration_details
|
|
156
156
|
check_react_on_rails_configuration_details
|
|
157
|
+
check_server_bundle_prerender_consistency
|
|
157
158
|
end
|
|
158
159
|
|
|
159
160
|
def check_bin_dev_launcher
|
|
@@ -166,6 +167,7 @@ module ReactOnRails
|
|
|
166
167
|
|
|
167
168
|
def check_testing_setup
|
|
168
169
|
check_rspec_helper_setup
|
|
170
|
+
check_build_test_configuration
|
|
169
171
|
end
|
|
170
172
|
|
|
171
173
|
def check_development
|
|
@@ -173,6 +175,7 @@ module ReactOnRails
|
|
|
173
175
|
check_procfile_dev
|
|
174
176
|
check_bin_dev_script
|
|
175
177
|
check_gitignore
|
|
178
|
+
check_async_usage
|
|
176
179
|
end
|
|
177
180
|
|
|
178
181
|
def check_javascript_bundles
|
|
@@ -465,7 +468,6 @@ module ReactOnRails
|
|
|
465
468
|
check_npm_wildcards
|
|
466
469
|
end
|
|
467
470
|
|
|
468
|
-
# rubocop:disable Metrics/CyclomaticComplexity
|
|
469
471
|
def check_gem_wildcards
|
|
470
472
|
gemfile_path = ENV["BUNDLE_GEMFILE"] || "Gemfile"
|
|
471
473
|
return unless File.exist?(gemfile_path)
|
|
@@ -487,9 +489,7 @@ module ReactOnRails
|
|
|
487
489
|
# Ignore errors reading Gemfile
|
|
488
490
|
end
|
|
489
491
|
end
|
|
490
|
-
# rubocop:enable Metrics/CyclomaticComplexity
|
|
491
492
|
|
|
492
|
-
# rubocop:disable Metrics/CyclomaticComplexity
|
|
493
493
|
def check_npm_wildcards
|
|
494
494
|
return unless File.exist?("package.json")
|
|
495
495
|
|
|
@@ -511,7 +511,6 @@ module ReactOnRails
|
|
|
511
511
|
# Ignore other errors
|
|
512
512
|
end
|
|
513
513
|
end
|
|
514
|
-
# rubocop:enable Metrics/CyclomaticComplexity
|
|
515
514
|
|
|
516
515
|
def check_key_configuration_files
|
|
517
516
|
files_to_check = {
|
|
@@ -535,7 +534,6 @@ module ReactOnRails
|
|
|
535
534
|
check_server_rendering_engine
|
|
536
535
|
end
|
|
537
536
|
|
|
538
|
-
# rubocop:disable Metrics/CyclomaticComplexity
|
|
539
537
|
def check_layout_files
|
|
540
538
|
layout_files = Dir.glob("app/views/layouts/**/*.erb")
|
|
541
539
|
return if layout_files.empty?
|
|
@@ -562,7 +560,6 @@ module ReactOnRails
|
|
|
562
560
|
end
|
|
563
561
|
end
|
|
564
562
|
end
|
|
565
|
-
# rubocop:enable Metrics/CyclomaticComplexity
|
|
566
563
|
|
|
567
564
|
# rubocop:disable Metrics/CyclomaticComplexity
|
|
568
565
|
def check_server_rendering_engine
|
|
@@ -600,7 +597,6 @@ module ReactOnRails
|
|
|
600
597
|
end
|
|
601
598
|
# rubocop:enable Metrics/CyclomaticComplexity
|
|
602
599
|
|
|
603
|
-
# rubocop:disable Metrics/CyclomaticComplexity
|
|
604
600
|
def check_shakapacker_configuration_details
|
|
605
601
|
return unless File.exist?("config/shakapacker.yml")
|
|
606
602
|
|
|
@@ -629,7 +625,6 @@ module ReactOnRails
|
|
|
629
625
|
checker.add_warning(" ⚠️ Could not run 'rake shakapacker:info': #{e.message}")
|
|
630
626
|
end
|
|
631
627
|
end
|
|
632
|
-
# rubocop:enable Metrics/CyclomaticComplexity
|
|
633
628
|
|
|
634
629
|
def check_react_on_rails_configuration_details
|
|
635
630
|
check_react_on_rails_initializer
|
|
@@ -664,6 +659,7 @@ module ReactOnRails
|
|
|
664
659
|
end
|
|
665
660
|
end
|
|
666
661
|
|
|
662
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
|
667
663
|
def analyze_server_rendering_config(content)
|
|
668
664
|
checker.add_info("\n🖥️ Server Rendering:")
|
|
669
665
|
|
|
@@ -675,6 +671,19 @@ module ReactOnRails
|
|
|
675
671
|
checker.add_info(" server_bundle_js_file: server-bundle.js (default)")
|
|
676
672
|
end
|
|
677
673
|
|
|
674
|
+
# Server bundle output path
|
|
675
|
+
server_bundle_path_match = content.match(/config\.server_bundle_output_path\s*=\s*["']([^"']+)["']/)
|
|
676
|
+
default_path = ReactOnRails::DEFAULT_SERVER_BUNDLE_OUTPUT_PATH
|
|
677
|
+
rails_bundle_path = server_bundle_path_match ? server_bundle_path_match[1] : default_path
|
|
678
|
+
checker.add_info(" server_bundle_output_path: #{rails_bundle_path}")
|
|
679
|
+
|
|
680
|
+
# Enforce private server bundles
|
|
681
|
+
enforce_private_match = content.match(/config\.enforce_private_server_bundles\s*=\s*([^\s\n,]+)/)
|
|
682
|
+
checker.add_info(" enforce_private_server_bundles: #{enforce_private_match[1]}") if enforce_private_match
|
|
683
|
+
|
|
684
|
+
# Check Shakapacker integration and provide recommendations
|
|
685
|
+
check_shakapacker_private_output_path(rails_bundle_path)
|
|
686
|
+
|
|
678
687
|
# RSC bundle file (Pro feature)
|
|
679
688
|
rsc_bundle_match = content.match(/config\.rsc_bundle_js_file\s*=\s*["']([^"']+)["']/)
|
|
680
689
|
if rsc_bundle_match
|
|
@@ -699,9 +708,9 @@ module ReactOnRails
|
|
|
699
708
|
|
|
700
709
|
checker.add_info(" raise_on_prerender_error: #{raise_on_error_match[1]}")
|
|
701
710
|
end
|
|
702
|
-
# rubocop:enable Metrics/
|
|
711
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
|
703
712
|
|
|
704
|
-
# rubocop:disable Metrics/
|
|
713
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
|
705
714
|
def analyze_performance_config(content)
|
|
706
715
|
checker.add_info("\n⚡ Performance & Loading:")
|
|
707
716
|
|
|
@@ -732,10 +741,12 @@ module ReactOnRails
|
|
|
732
741
|
auto_load_match = content.match(/config\.auto_load_bundle\s*=\s*([^\s\n,]+)/)
|
|
733
742
|
checker.add_info(" auto_load_bundle: #{auto_load_match[1]}") if auto_load_match
|
|
734
743
|
|
|
735
|
-
#
|
|
744
|
+
# Deprecated immediate_hydration setting
|
|
736
745
|
immediate_hydration_match = content.match(/config\.immediate_hydration\s*=\s*([^\s\n,]+)/)
|
|
737
746
|
if immediate_hydration_match
|
|
738
|
-
checker.
|
|
747
|
+
checker.add_warning(" ⚠️ immediate_hydration: #{immediate_hydration_match[1]} (DEPRECATED)")
|
|
748
|
+
checker.add_info(" 💡 This setting is no longer used. Immediate hydration is now automatic for Pro users.")
|
|
749
|
+
checker.add_info(" 💡 Remove this line from your config/initializers/react_on_rails.rb file.")
|
|
739
750
|
end
|
|
740
751
|
|
|
741
752
|
# Component registry timeout
|
|
@@ -1108,6 +1119,124 @@ module ReactOnRails
|
|
|
1108
1119
|
end
|
|
1109
1120
|
end
|
|
1110
1121
|
|
|
1122
|
+
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
1123
|
+
def check_server_bundle_prerender_consistency
|
|
1124
|
+
config_path = "config/initializers/react_on_rails.rb"
|
|
1125
|
+
return unless File.exist?(config_path)
|
|
1126
|
+
|
|
1127
|
+
checker.add_info("\n🔍 Server Rendering Consistency:")
|
|
1128
|
+
|
|
1129
|
+
begin
|
|
1130
|
+
content = File.read(config_path)
|
|
1131
|
+
|
|
1132
|
+
# Check for server bundle configuration
|
|
1133
|
+
server_bundle_match = content.match(/config\.server_bundle_js_file\s*=\s*["']([^"']+)["']/)
|
|
1134
|
+
server_bundle_set = server_bundle_match && server_bundle_match[1].present?
|
|
1135
|
+
|
|
1136
|
+
# Check for global prerender setting
|
|
1137
|
+
prerender_match = content.match(/config\.prerender\s*=\s*(true)/)
|
|
1138
|
+
prerender_set = prerender_match
|
|
1139
|
+
|
|
1140
|
+
# Check if prerender is used in views
|
|
1141
|
+
uses_prerender = uses_prerender_in_views?
|
|
1142
|
+
|
|
1143
|
+
# Analyze the configuration
|
|
1144
|
+
if (prerender_set || uses_prerender) && !server_bundle_set
|
|
1145
|
+
checker.add_warning(" ⚠️ Server rendering is enabled but server_bundle_js_file is not configured")
|
|
1146
|
+
checker.add_info(" 💡 Set config.server_bundle_js_file = 'server-bundle.js' to enable SSR")
|
|
1147
|
+
checker.add_info(" 💡 See: https://www.shakacode.com/react-on-rails/docs/guides/server-rendering")
|
|
1148
|
+
elsif server_bundle_set && !prerender_set && !uses_prerender
|
|
1149
|
+
checker.add_info(" ℹ️ server_bundle_js_file is configured but prerender doesn't appear to be used")
|
|
1150
|
+
checker.add_info(" 💡 Either use prerender: true in react_component calls or remove server_bundle_js_file")
|
|
1151
|
+
else
|
|
1152
|
+
checker.add_success(" ✅ Server rendering configuration is consistent")
|
|
1153
|
+
end
|
|
1154
|
+
rescue StandardError => e
|
|
1155
|
+
checker.add_warning(" ⚠️ Could not analyze server rendering configuration: #{e.message}")
|
|
1156
|
+
end
|
|
1157
|
+
end
|
|
1158
|
+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
1159
|
+
|
|
1160
|
+
def uses_prerender_in_views?
|
|
1161
|
+
# Check view files for prerender: true
|
|
1162
|
+
view_files = Dir.glob("app/views/**/*.{erb,haml,slim}")
|
|
1163
|
+
view_files.any? do |file|
|
|
1164
|
+
next unless File.exist?(file)
|
|
1165
|
+
|
|
1166
|
+
File.read(file).match?(/prerender:\s*true/)
|
|
1167
|
+
end
|
|
1168
|
+
rescue StandardError
|
|
1169
|
+
false
|
|
1170
|
+
end
|
|
1171
|
+
|
|
1172
|
+
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength
|
|
1173
|
+
def check_build_test_configuration
|
|
1174
|
+
config_path = "config/initializers/react_on_rails.rb"
|
|
1175
|
+
shakapacker_yml = "config/shakapacker.yml"
|
|
1176
|
+
|
|
1177
|
+
return unless File.exist?(config_path)
|
|
1178
|
+
|
|
1179
|
+
checker.add_info("\n🧪 Test Asset Compilation:")
|
|
1180
|
+
|
|
1181
|
+
begin
|
|
1182
|
+
config_content = File.read(config_path)
|
|
1183
|
+
has_build_test_command = config_content.match(/^\s*config\.build_test_command\s*=\s*["']([^"']+)["']/)
|
|
1184
|
+
uses_test_helper = uses_react_on_rails_test_helper?
|
|
1185
|
+
|
|
1186
|
+
if File.exist?(shakapacker_yml)
|
|
1187
|
+
shakapacker_content = File.read(shakapacker_yml)
|
|
1188
|
+
# Match test section and look for compile: true
|
|
1189
|
+
has_compile_true = shakapacker_content.match(/^test:.*?^\s+compile:\s*true/m)
|
|
1190
|
+
|
|
1191
|
+
if has_build_test_command && has_compile_true
|
|
1192
|
+
checker.add_warning(" ⚠️ Both build_test_command and shakapacker compile: true are configured")
|
|
1193
|
+
checker.add_info(" 💡 These are mutually exclusive - use only one approach")
|
|
1194
|
+
checker.add_info(" 💡 Recommended: Use compile: true in shakapacker.yml (simpler)")
|
|
1195
|
+
checker.add_info(" 💡 Alternative: Use build_test_command with ReactOnRails::TestHelper (explicit control)")
|
|
1196
|
+
checker.add_info(" 📖 See: https://github.com/shakacode/react_on_rails/blob/master/docs/guides/testing-configuration.md")
|
|
1197
|
+
elsif has_build_test_command && !uses_test_helper
|
|
1198
|
+
checker.add_warning(" ⚠️ build_test_command is set but ReactOnRails::TestHelper is not configured")
|
|
1199
|
+
checker.add_info(" 💡 Add to spec/rails_helper.rb:")
|
|
1200
|
+
checker.add_info(" ReactOnRails::TestHelper.configure_rspec_to_compile_assets(config)")
|
|
1201
|
+
checker.add_info(" 💡 Or remove build_test_command and use compile: true in shakapacker.yml")
|
|
1202
|
+
elsif !has_build_test_command && uses_test_helper
|
|
1203
|
+
checker.add_error(" 🚫 ReactOnRails::TestHelper is configured but build_test_command is not set")
|
|
1204
|
+
checker.add_info(" 💡 Add to config/initializers/react_on_rails.rb:")
|
|
1205
|
+
checker.add_info(" config.build_test_command = 'RAILS_ENV=test bin/shakapacker'")
|
|
1206
|
+
checker.add_info(" 💡 Or remove TestHelper and use compile: true in shakapacker.yml")
|
|
1207
|
+
elsif !has_build_test_command && !has_compile_true && !uses_test_helper
|
|
1208
|
+
checker.add_warning(" ⚠️ No test asset compilation configured")
|
|
1209
|
+
checker.add_info(" 💡 Recommended: Add to shakapacker.yml test section:")
|
|
1210
|
+
checker.add_info(" compile: true")
|
|
1211
|
+
checker.add_info(" 📖 See: https://github.com/shakacode/react_on_rails/blob/master/docs/guides/testing-configuration.md")
|
|
1212
|
+
elsif has_compile_true
|
|
1213
|
+
checker.add_success(" ✅ Test assets configured via Shakapacker auto-compilation")
|
|
1214
|
+
checker.add_info(" (compile: true in shakapacker.yml)")
|
|
1215
|
+
elsif has_build_test_command && uses_test_helper
|
|
1216
|
+
checker.add_success(" ✅ Test assets configured via React on Rails test helper")
|
|
1217
|
+
checker.add_info(" (build_test_command + ReactOnRails::TestHelper)")
|
|
1218
|
+
end
|
|
1219
|
+
else
|
|
1220
|
+
checker.add_warning(" ⚠️ config/shakapacker.yml not found")
|
|
1221
|
+
end
|
|
1222
|
+
rescue StandardError => e
|
|
1223
|
+
checker.add_warning(" ⚠️ Could not analyze test configuration: #{e.message}")
|
|
1224
|
+
end
|
|
1225
|
+
end
|
|
1226
|
+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength
|
|
1227
|
+
|
|
1228
|
+
def uses_react_on_rails_test_helper?
|
|
1229
|
+
spec_helpers = ["spec/rails_helper.rb", "spec/spec_helper.rb", "test/test_helper.rb"]
|
|
1230
|
+
spec_helpers.any? do |helper|
|
|
1231
|
+
next unless File.exist?(helper)
|
|
1232
|
+
|
|
1233
|
+
content = File.read(helper)
|
|
1234
|
+
content.include?("configure_rspec_to_compile_assets") || content.include?("ensure_assets_compiled")
|
|
1235
|
+
end
|
|
1236
|
+
rescue StandardError
|
|
1237
|
+
false
|
|
1238
|
+
end
|
|
1239
|
+
|
|
1111
1240
|
def relativize_path(absolute_path)
|
|
1112
1241
|
return absolute_path unless absolute_path.is_a?(String)
|
|
1113
1242
|
|
|
@@ -1144,6 +1273,206 @@ module ReactOnRails
|
|
|
1144
1273
|
checker.add_info(" #{label}: <error reading value: #{e.message}>")
|
|
1145
1274
|
end
|
|
1146
1275
|
end
|
|
1276
|
+
|
|
1277
|
+
# Comment patterns used for filtering out commented async usage
|
|
1278
|
+
ERB_COMMENT_PATTERN = /<%\s*#.*javascript_pack_tag/
|
|
1279
|
+
HAML_COMMENT_PATTERN = /^\s*-#.*javascript_pack_tag/
|
|
1280
|
+
SLIM_COMMENT_PATTERN = %r{^\s*/.*javascript_pack_tag}
|
|
1281
|
+
HTML_COMMENT_PATTERN = /<!--.*javascript_pack_tag/
|
|
1282
|
+
|
|
1283
|
+
def check_async_usage
|
|
1284
|
+
# When Pro is installed, async is fully supported and is the default behavior
|
|
1285
|
+
# No need to check for async usage in this case
|
|
1286
|
+
return if ReactOnRails::Utils.react_on_rails_pro?
|
|
1287
|
+
|
|
1288
|
+
async_issues = []
|
|
1289
|
+
|
|
1290
|
+
# Check 1: javascript_pack_tag with :async in view files
|
|
1291
|
+
view_files_with_async = scan_view_files_for_async_pack_tag
|
|
1292
|
+
unless view_files_with_async.empty?
|
|
1293
|
+
async_issues << "javascript_pack_tag with :async found in view files:"
|
|
1294
|
+
view_files_with_async.each do |file|
|
|
1295
|
+
async_issues << " • #{file}"
|
|
1296
|
+
end
|
|
1297
|
+
end
|
|
1298
|
+
|
|
1299
|
+
# Check 2: generated_component_packs_loading_strategy = :async
|
|
1300
|
+
if config_has_async_loading_strategy?
|
|
1301
|
+
async_issues << "config.generated_component_packs_loading_strategy = :async in initializer"
|
|
1302
|
+
end
|
|
1303
|
+
|
|
1304
|
+
return if async_issues.empty?
|
|
1305
|
+
|
|
1306
|
+
# Report errors if async usage is found without Pro
|
|
1307
|
+
checker.add_error("🚫 :async usage detected without React on Rails Pro")
|
|
1308
|
+
async_issues.each { |issue| checker.add_error(" #{issue}") }
|
|
1309
|
+
checker.add_info(" 💡 :async can cause race conditions. Options:")
|
|
1310
|
+
checker.add_info(" 1. Upgrade to React on Rails Pro (recommended for :async support)")
|
|
1311
|
+
checker.add_info(" 2. Change to :defer or :sync loading strategy")
|
|
1312
|
+
checker.add_info(" 📖 https://www.shakacode.com/react-on-rails/docs/guides/configuration/")
|
|
1313
|
+
end
|
|
1314
|
+
|
|
1315
|
+
def scan_view_files_for_async_pack_tag
|
|
1316
|
+
view_patterns = ["app/views/**/*.erb", "app/views/**/*.haml", "app/views/**/*.slim"]
|
|
1317
|
+
files_with_async = view_patterns.flat_map { |pattern| scan_pattern_for_async(pattern) }
|
|
1318
|
+
files_with_async.compact
|
|
1319
|
+
rescue Errno::ENOENT, Encoding::InvalidByteSequenceError, Encoding::UndefinedConversionError => e
|
|
1320
|
+
# Log the error if Rails logger is available
|
|
1321
|
+
log_debug("Error scanning view files for async: #{e.message}")
|
|
1322
|
+
[]
|
|
1323
|
+
end
|
|
1324
|
+
|
|
1325
|
+
def scan_pattern_for_async(pattern)
|
|
1326
|
+
Dir.glob(pattern).filter_map do |file|
|
|
1327
|
+
next unless File.exist?(file)
|
|
1328
|
+
|
|
1329
|
+
content = File.read(file)
|
|
1330
|
+
next if content_has_only_commented_async?(content)
|
|
1331
|
+
next unless file_has_async_pack_tag?(content)
|
|
1332
|
+
|
|
1333
|
+
relativize_path(file)
|
|
1334
|
+
end
|
|
1335
|
+
end
|
|
1336
|
+
|
|
1337
|
+
def file_has_async_pack_tag?(content)
|
|
1338
|
+
# Match javascript_pack_tag with :async symbol or async: true hash syntax
|
|
1339
|
+
# Examples that should match:
|
|
1340
|
+
# - javascript_pack_tag "app", :async
|
|
1341
|
+
# - javascript_pack_tag "app", async: true
|
|
1342
|
+
# - javascript_pack_tag "app", :async, other_option: value
|
|
1343
|
+
# Examples that should NOT match:
|
|
1344
|
+
# - javascript_pack_tag "app", defer: "async" (async is a string value, not the option)
|
|
1345
|
+
# - javascript_pack_tag "app", :defer
|
|
1346
|
+
# Note: Theoretical edge case `data: { async: true }` would match but is extremely unlikely
|
|
1347
|
+
# in real code and represents a harmless false positive (showing a warning when not needed)
|
|
1348
|
+
# Use word boundary \b to ensure :async is not part of a longer symbol like :async_mode
|
|
1349
|
+
# [^<]* allows matching across newlines within ERB tags but stops at closing ERB tag
|
|
1350
|
+
content.match?(/javascript_pack_tag[^<]*(?::async\b|async:\s*true)/)
|
|
1351
|
+
end
|
|
1352
|
+
|
|
1353
|
+
def content_has_only_commented_async?(content)
|
|
1354
|
+
# Check if all occurrences of javascript_pack_tag with :async are in comments
|
|
1355
|
+
# Returns true if ONLY commented async usage exists (no active async usage)
|
|
1356
|
+
|
|
1357
|
+
# First check if there's any javascript_pack_tag with :async in the full content
|
|
1358
|
+
return true unless file_has_async_pack_tag?(content)
|
|
1359
|
+
|
|
1360
|
+
# Strategy: Remove all commented lines, then check if any :async remains
|
|
1361
|
+
# This handles both single-line and multi-line tags correctly
|
|
1362
|
+
uncommented_lines = content.each_line.reject do |line|
|
|
1363
|
+
line.match?(ERB_COMMENT_PATTERN) ||
|
|
1364
|
+
line.match?(HAML_COMMENT_PATTERN) ||
|
|
1365
|
+
line.match?(SLIM_COMMENT_PATTERN) ||
|
|
1366
|
+
line.match?(HTML_COMMENT_PATTERN)
|
|
1367
|
+
end
|
|
1368
|
+
|
|
1369
|
+
uncommented_content = uncommented_lines.join
|
|
1370
|
+
# If no async found in uncommented content, all async usage was commented
|
|
1371
|
+
!file_has_async_pack_tag?(uncommented_content)
|
|
1372
|
+
end
|
|
1373
|
+
|
|
1374
|
+
def config_has_async_loading_strategy?
|
|
1375
|
+
config_path = "config/initializers/react_on_rails.rb"
|
|
1376
|
+
return false unless File.exist?(config_path)
|
|
1377
|
+
|
|
1378
|
+
content = File.read(config_path)
|
|
1379
|
+
# Check if generated_component_packs_loading_strategy is set to :async
|
|
1380
|
+
# Filter out commented lines (lines starting with # after optional whitespace)
|
|
1381
|
+
content.each_line.any? do |line|
|
|
1382
|
+
# Skip lines that start with # (after optional whitespace)
|
|
1383
|
+
next if line.match?(/^\s*#/)
|
|
1384
|
+
|
|
1385
|
+
# Match: config.generated_component_packs_loading_strategy = :async
|
|
1386
|
+
# Use word boundary \b to ensure :async is the complete symbol, not part of :async_mode etc.
|
|
1387
|
+
line.match?(/config\.generated_component_packs_loading_strategy\s*=\s*:async\b/)
|
|
1388
|
+
end
|
|
1389
|
+
rescue Errno::ENOENT, Encoding::InvalidByteSequenceError, Encoding::UndefinedConversionError => e
|
|
1390
|
+
# Log the error if Rails logger is available
|
|
1391
|
+
log_debug("Error checking async loading strategy: #{e.message}")
|
|
1392
|
+
false
|
|
1393
|
+
end
|
|
1394
|
+
|
|
1395
|
+
def log_debug(message)
|
|
1396
|
+
Rails.logger&.debug(message)
|
|
1397
|
+
end
|
|
1398
|
+
|
|
1399
|
+
# Check Shakapacker private_output_path integration and provide recommendations
|
|
1400
|
+
def check_shakapacker_private_output_path(rails_bundle_path)
|
|
1401
|
+
return report_no_shakapacker unless defined?(::Shakapacker)
|
|
1402
|
+
return report_upgrade_shakapacker unless ::Shakapacker.config.respond_to?(:private_output_path)
|
|
1403
|
+
|
|
1404
|
+
check_shakapacker_9_private_output_path(rails_bundle_path)
|
|
1405
|
+
rescue StandardError => e
|
|
1406
|
+
checker.add_info("\n ℹ️ Could not check Shakapacker config: #{e.message}")
|
|
1407
|
+
end
|
|
1408
|
+
|
|
1409
|
+
def report_no_shakapacker
|
|
1410
|
+
checker.add_info("\n ℹ️ Shakapacker not detected - using manual configuration")
|
|
1411
|
+
end
|
|
1412
|
+
|
|
1413
|
+
def report_upgrade_shakapacker
|
|
1414
|
+
checker.add_info(<<~MSG.strip)
|
|
1415
|
+
\n 💡 Recommendation: Upgrade to Shakapacker 9.0+
|
|
1416
|
+
|
|
1417
|
+
Shakapacker 9.0+ adds 'private_output_path' in shakapacker.yml for server bundles.
|
|
1418
|
+
This eliminates the need to configure server_bundle_output_path separately.
|
|
1419
|
+
|
|
1420
|
+
Benefits:
|
|
1421
|
+
- Single source of truth in shakapacker.yml
|
|
1422
|
+
- Automatic detection by React on Rails
|
|
1423
|
+
- No configuration duplication
|
|
1424
|
+
MSG
|
|
1425
|
+
end
|
|
1426
|
+
|
|
1427
|
+
def check_shakapacker_9_private_output_path(rails_bundle_path)
|
|
1428
|
+
private_path = ::Shakapacker.config.private_output_path
|
|
1429
|
+
|
|
1430
|
+
if private_path
|
|
1431
|
+
report_shakapacker_path_status(private_path, rails_bundle_path)
|
|
1432
|
+
else
|
|
1433
|
+
report_configure_private_output_path(rails_bundle_path)
|
|
1434
|
+
end
|
|
1435
|
+
end
|
|
1436
|
+
|
|
1437
|
+
def report_shakapacker_path_status(private_path, rails_bundle_path)
|
|
1438
|
+
relative_path = ReactOnRails::Utils.normalize_to_relative_path(private_path)
|
|
1439
|
+
# Normalize both paths for comparison (remove trailing slashes)
|
|
1440
|
+
normalized_relative = relative_path.to_s.chomp("/")
|
|
1441
|
+
normalized_rails = rails_bundle_path.to_s.chomp("/")
|
|
1442
|
+
|
|
1443
|
+
if normalized_relative == normalized_rails
|
|
1444
|
+
checker.add_success("\n ✅ Using Shakapacker 9.0+ private_output_path: '#{relative_path}'")
|
|
1445
|
+
checker.add_info(" Auto-detected from shakapacker.yml - no manual config needed")
|
|
1446
|
+
else
|
|
1447
|
+
report_configuration_mismatch(relative_path, rails_bundle_path)
|
|
1448
|
+
end
|
|
1449
|
+
end
|
|
1450
|
+
|
|
1451
|
+
def report_configuration_mismatch(relative_path, rails_bundle_path)
|
|
1452
|
+
checker.add_warning(<<~MSG.strip)
|
|
1453
|
+
\n ⚠️ Configuration mismatch detected!
|
|
1454
|
+
|
|
1455
|
+
Shakapacker private_output_path: '#{relative_path}'
|
|
1456
|
+
React on Rails server_bundle_output_path: '#{rails_bundle_path}'
|
|
1457
|
+
|
|
1458
|
+
Recommendation: Remove server_bundle_output_path from your React on Rails
|
|
1459
|
+
initializer and let it auto-detect from shakapacker.yml private_output_path.
|
|
1460
|
+
MSG
|
|
1461
|
+
end
|
|
1462
|
+
|
|
1463
|
+
def report_configure_private_output_path(rails_bundle_path)
|
|
1464
|
+
checker.add_info(<<~MSG.strip)
|
|
1465
|
+
\n 💡 Recommendation: Configure private_output_path in shakapacker.yml
|
|
1466
|
+
|
|
1467
|
+
Add to config/shakapacker.yml:
|
|
1468
|
+
private_output_path: #{rails_bundle_path}
|
|
1469
|
+
|
|
1470
|
+
This will:
|
|
1471
|
+
- Keep webpack and Rails configs in sync automatically
|
|
1472
|
+
- Enable auto-detection by React on Rails
|
|
1473
|
+
- Serve as single source of truth for server bundle location
|
|
1474
|
+
MSG
|
|
1475
|
+
end
|
|
1147
1476
|
end
|
|
1148
1477
|
# rubocop:enable Metrics/ClassLength
|
|
1149
1478
|
end
|
|
@@ -4,8 +4,82 @@ require "rails/railtie"
|
|
|
4
4
|
|
|
5
5
|
module ReactOnRails
|
|
6
6
|
class Engine < ::Rails::Engine
|
|
7
|
+
# Validate package versions and compatibility on Rails startup
|
|
8
|
+
# This ensures the application fails fast if versions don't match or packages are misconfigured
|
|
9
|
+
# Skip validation during installation tasks (e.g., shakapacker:install) or generator runtime
|
|
10
|
+
initializer "react_on_rails.validate_version_and_package_compatibility" do
|
|
11
|
+
config.after_initialize do
|
|
12
|
+
next if Engine.skip_version_validation?
|
|
13
|
+
|
|
14
|
+
Rails.logger.info "[React on Rails] Validating package version and compatibility..."
|
|
15
|
+
VersionChecker.build.validate_version_and_package_compatibility!
|
|
16
|
+
Rails.logger.info "[React on Rails] Package validation successful"
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Determine if version validation should be skipped
|
|
21
|
+
#
|
|
22
|
+
# This method checks multiple conditions to determine if package version validation
|
|
23
|
+
# should be skipped. Validation is skipped during setup scenarios where the npm
|
|
24
|
+
# package isn't installed yet (e.g., during generator execution).
|
|
25
|
+
#
|
|
26
|
+
# @return [Boolean] true if validation should be skipped
|
|
27
|
+
#
|
|
28
|
+
# @note Thread Safety: ENV variables are process-global. In practice, Rails generators
|
|
29
|
+
# run in a single process, so concurrent execution is not a concern. If running
|
|
30
|
+
# generators concurrently (e.g., in parallel tests), ensure tests run in separate
|
|
31
|
+
# processes to avoid ENV variable conflicts.
|
|
32
|
+
#
|
|
33
|
+
# @example Testing with parallel processes
|
|
34
|
+
# # In RSpec configuration:
|
|
35
|
+
# config.before(:each) do |example|
|
|
36
|
+
# ENV.delete("REACT_ON_RAILS_SKIP_VALIDATION")
|
|
37
|
+
# end
|
|
38
|
+
#
|
|
39
|
+
# @note Manual ENV Setting: While this ENV variable is designed to be set by generators,
|
|
40
|
+
# users can manually set it (e.g., `REACT_ON_RAILS_SKIP_VALIDATION=true rails server`)
|
|
41
|
+
# to bypass validation. This should only be done temporarily during debugging or
|
|
42
|
+
# setup scenarios. The validation helps catch version mismatches early, so bypassing
|
|
43
|
+
# it in production is not recommended.
|
|
44
|
+
def self.skip_version_validation?
|
|
45
|
+
# Skip if explicitly disabled via environment variable (set by generators)
|
|
46
|
+
# Using ENV variable instead of ARGV because Rails can modify/clear ARGV during
|
|
47
|
+
# initialization, making ARGV unreliable for detecting generator context. The ENV
|
|
48
|
+
# variable persists through the entire Rails initialization process.
|
|
49
|
+
if ENV["REACT_ON_RAILS_SKIP_VALIDATION"] == "true"
|
|
50
|
+
Rails.logger.debug "[React on Rails] Skipping validation - disabled via environment variable"
|
|
51
|
+
return true
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Check package.json first as it's cheaper and handles more cases
|
|
55
|
+
if package_json_missing?
|
|
56
|
+
Rails.logger.debug "[React on Rails] Skipping validation - package.json not found"
|
|
57
|
+
return true
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Skip during generator runtime since packages are installed during execution
|
|
61
|
+
# This is a fallback check in case ENV wasn't set, though ENV is the primary mechanism
|
|
62
|
+
if running_generator?
|
|
63
|
+
Rails.logger.debug "[React on Rails] Skipping validation during generator runtime"
|
|
64
|
+
return true
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
false
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Check if we're running a Rails generator
|
|
71
|
+
# @return [Boolean] true if running a generator
|
|
72
|
+
def self.running_generator?
|
|
73
|
+
!ARGV.empty? && ARGV.first&.in?(%w[generate g])
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Check if package.json doesn't exist yet
|
|
77
|
+
# @return [Boolean] true if package.json is missing
|
|
78
|
+
def self.package_json_missing?
|
|
79
|
+
!File.exist?(VersionChecker::NodePackageVersion.package_json_path)
|
|
80
|
+
end
|
|
81
|
+
|
|
7
82
|
config.to_prepare do
|
|
8
|
-
VersionChecker.build.log_if_gem_and_node_package_versions_differ
|
|
9
83
|
ReactOnRails::ServerRenderingPool.reset_pool
|
|
10
84
|
end
|
|
11
85
|
|
|
@@ -5,7 +5,9 @@ require "English"
|
|
|
5
5
|
module ReactOnRails
|
|
6
6
|
module GitUtils
|
|
7
7
|
def self.uncommitted_changes?(message_handler, git_installed: true)
|
|
8
|
-
|
|
8
|
+
# Skip check in CI environments - CI often makes temporary modifications
|
|
9
|
+
# (e.g., script/convert for minimum version testing) before running generators
|
|
10
|
+
return false if ENV["CI"] == "true" || ENV["COVERAGE"] == "true"
|
|
9
11
|
|
|
10
12
|
status = `git status --porcelain`
|
|
11
13
|
return false if git_installed && status&.empty?
|