react_on_rails 16.4.0 → 16.5.1
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/Gemfile.lock +1 -1
- data/lib/generators/USAGE +1 -1
- data/lib/generators/react_on_rails/generator_messages.rb +20 -18
- data/lib/generators/react_on_rails/install_generator.rb +128 -12
- data/lib/generators/react_on_rails/pro_generator.rb +1 -1
- data/lib/generators/react_on_rails/pro_setup.rb +3 -3
- data/lib/generators/react_on_rails/react_with_redux_generator.rb +2 -1
- data/lib/generators/react_on_rails/rsc_generator.rb +1 -1
- data/lib/generators/react_on_rails/templates/base/base/bin/dev +1 -1
- data/lib/generators/react_on_rails/templates/base/base/config/initializers/react_on_rails.rb.tt +3 -3
- data/lib/generators/react_on_rails/templates/pro/base/client/node-renderer.js +8 -4
- data/lib/generators/react_on_rails/templates/pro/base/config/initializers/react_on_rails_pro.rb.tt +1 -1
- data/lib/generators/react_on_rails/templates/rsc/base/app/controllers/hello_server_controller.rb.tt +1 -1
- data/lib/generators/react_on_rails/templates/rsc/base/app/javascript/src/HelloServer/components/HelloServer.jsx +1 -1
- data/lib/generators/react_on_rails/templates/rsc/base/app/javascript/src/HelloServer/components/HelloServer.tsx +1 -1
- data/lib/generators/react_on_rails/templates/rsc/base/app/views/hello_server/index.html.erb +1 -1
- data/lib/generators/react_on_rails/templates/rsc/base/config/webpack/rscWebpackConfig.js.tt +1 -1
- data/lib/react_on_rails/config_path_resolver.rb +50 -0
- data/lib/react_on_rails/configuration.rb +1 -1
- data/lib/react_on_rails/dev/process_manager.rb +16 -1
- data/lib/react_on_rails/dev/server_manager.rb +2 -2
- data/lib/react_on_rails/doctor.rb +389 -25
- data/lib/react_on_rails/git_utils.rb +95 -23
- data/lib/react_on_rails/helper.rb +3 -3
- data/lib/react_on_rails/packer_utils.rb +1 -1
- data/lib/react_on_rails/packs_generator.rb +3 -3
- data/lib/react_on_rails/react_component/render_options.rb +48 -0
- data/lib/react_on_rails/server_rendering_js_code.rb +1 -1
- data/lib/react_on_rails/system_checker.rb +42 -19
- data/lib/react_on_rails/test_helper/webpack_assets_compiler.rb +1 -2
- data/lib/react_on_rails/utils.rb +1 -1
- data/lib/react_on_rails/version.rb +1 -1
- data/lib/react_on_rails/version_synchronizer.rb +250 -0
- data/lib/tasks/generate_packs.rake +4 -4
- data/lib/tasks/sync_versions.rake +23 -0
- data/rakelib/examples_config.yml +1 -1
- data/rakelib/update_changelog.rake +3 -3
- data/react_on_rails.gemspec +1 -1
- data/sig/react_on_rails/dev/process_manager.rbs +2 -0
- metadata +6 -3
|
@@ -4,6 +4,7 @@ require "json"
|
|
|
4
4
|
require "erb"
|
|
5
5
|
require "yaml"
|
|
6
6
|
require_relative "utils"
|
|
7
|
+
require_relative "config_path_resolver"
|
|
7
8
|
require_relative "version_syntax_converter"
|
|
8
9
|
require_relative "system_checker"
|
|
9
10
|
|
|
@@ -39,6 +40,8 @@ end
|
|
|
39
40
|
module ReactOnRails
|
|
40
41
|
# rubocop:disable Metrics/ClassLength, Metrics/AbcSize
|
|
41
42
|
class Doctor
|
|
43
|
+
include ConfigPathResolver
|
|
44
|
+
|
|
42
45
|
MESSAGE_COLORS = {
|
|
43
46
|
error: :red,
|
|
44
47
|
warning: :yellow,
|
|
@@ -56,6 +59,7 @@ module ReactOnRails
|
|
|
56
59
|
@fix = fix
|
|
57
60
|
@checker = SystemChecker.new
|
|
58
61
|
@test_output_path_strategy = :unknown
|
|
62
|
+
@rails_environment_loaded = false
|
|
59
63
|
end
|
|
60
64
|
|
|
61
65
|
def run_diagnosis
|
|
@@ -86,7 +90,7 @@ module ReactOnRails
|
|
|
86
90
|
puts " • This diagnostic tool is available in React on Rails v16.0.0+"
|
|
87
91
|
puts " • For older versions, upgrade your gem to access this feature"
|
|
88
92
|
puts " • Run: bundle update react_on_rails"
|
|
89
|
-
puts " • Documentation: https://
|
|
93
|
+
puts " • Documentation: https://reactonrails.com/docs/"
|
|
90
94
|
end
|
|
91
95
|
|
|
92
96
|
def run_all_checks
|
|
@@ -101,7 +105,9 @@ module ReactOnRails
|
|
|
101
105
|
["Rails Integration", :check_rails],
|
|
102
106
|
["Webpack Configuration", :check_webpack],
|
|
103
107
|
["Testing Setup", :check_testing_setup],
|
|
104
|
-
["Development Environment", :check_development]
|
|
108
|
+
["Development Environment", :check_development],
|
|
109
|
+
["React on Rails Pro Setup", :check_pro_setup],
|
|
110
|
+
["React Server Components", :check_rsc_setup]
|
|
105
111
|
]
|
|
106
112
|
|
|
107
113
|
checks.each do |section_name, check_method|
|
|
@@ -473,10 +479,11 @@ module ReactOnRails
|
|
|
473
479
|
end
|
|
474
480
|
|
|
475
481
|
def check_npm_package_version
|
|
476
|
-
|
|
482
|
+
package_json_path = resolved_package_json_path
|
|
483
|
+
return unless File.exist?(package_json_path)
|
|
477
484
|
|
|
478
485
|
begin
|
|
479
|
-
package_json = JSON.parse(File.read(
|
|
486
|
+
package_json = JSON.parse(File.read(package_json_path))
|
|
480
487
|
all_deps = package_json["dependencies"]&.merge(package_json["devDependencies"] || {}) || {}
|
|
481
488
|
|
|
482
489
|
npm_version = all_deps["react-on-rails"]
|
|
@@ -520,10 +527,11 @@ module ReactOnRails
|
|
|
520
527
|
end
|
|
521
528
|
|
|
522
529
|
def check_npm_wildcards
|
|
523
|
-
|
|
530
|
+
package_json_path = resolved_package_json_path
|
|
531
|
+
return unless File.exist?(package_json_path)
|
|
524
532
|
|
|
525
533
|
begin
|
|
526
|
-
package_json = JSON.parse(File.read(
|
|
534
|
+
package_json = JSON.parse(File.read(package_json_path))
|
|
527
535
|
all_deps = package_json["dependencies"]&.merge(package_json["devDependencies"] || {}) || {}
|
|
528
536
|
|
|
529
537
|
npm_version = all_deps["react-on-rails"]
|
|
@@ -542,9 +550,10 @@ module ReactOnRails
|
|
|
542
550
|
end
|
|
543
551
|
|
|
544
552
|
def check_pro_package_consistency
|
|
545
|
-
|
|
553
|
+
package_json_path = resolved_package_json_path
|
|
554
|
+
return unless File.exist?(package_json_path)
|
|
546
555
|
|
|
547
|
-
package_json = JSON.parse(File.read(
|
|
556
|
+
package_json = JSON.parse(File.read(package_json_path))
|
|
548
557
|
all_deps = (package_json["dependencies"] || {}).merge(package_json["devDependencies"] || {})
|
|
549
558
|
has_base = all_deps.key?("react-on-rails")
|
|
550
559
|
has_pro = all_deps.key?("react-on-rails-pro")
|
|
@@ -606,8 +615,7 @@ module ReactOnRails
|
|
|
606
615
|
"config/initializers/react_on_rails.rb" => "React on Rails initializer",
|
|
607
616
|
"bin/dev" => "Development server launcher",
|
|
608
617
|
"bin/shakapacker" => "Shakapacker binary",
|
|
609
|
-
"bin/shakapacker-dev-server" => "Shakapacker dev server binary"
|
|
610
|
-
"config/webpack/webpack.config.js" => "Webpack configuration"
|
|
618
|
+
"bin/shakapacker-dev-server" => "Shakapacker dev server binary"
|
|
611
619
|
}
|
|
612
620
|
|
|
613
621
|
files_to_check.each do |file_path, description|
|
|
@@ -618,6 +626,16 @@ module ReactOnRails
|
|
|
618
626
|
end
|
|
619
627
|
end
|
|
620
628
|
|
|
629
|
+
webpack_config_path = resolved_webpack_config_path
|
|
630
|
+
if webpack_config_path
|
|
631
|
+
checker.add_success("✅ Webpack configuration: #{webpack_config_path}")
|
|
632
|
+
else
|
|
633
|
+
checker.add_warning("⚠️ Missing Webpack configuration: config/webpack/webpack.config.js")
|
|
634
|
+
checker.add_info(
|
|
635
|
+
"ℹ️ If your app uses a custom webpack config location, this warning may be informational."
|
|
636
|
+
)
|
|
637
|
+
end
|
|
638
|
+
|
|
621
639
|
check_layout_files
|
|
622
640
|
check_server_rendering_engine
|
|
623
641
|
end
|
|
@@ -649,20 +667,29 @@ module ReactOnRails
|
|
|
649
667
|
end
|
|
650
668
|
end
|
|
651
669
|
|
|
652
|
-
# rubocop:disable Metrics/CyclomaticComplexity
|
|
670
|
+
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
653
671
|
def check_server_rendering_engine
|
|
654
672
|
return unless defined?(ReactOnRails)
|
|
655
673
|
|
|
656
674
|
checker.add_info("\n🖥️ Server Rendering Engine:")
|
|
657
675
|
|
|
658
676
|
begin
|
|
659
|
-
|
|
660
|
-
|
|
677
|
+
uses_node_renderer = ReactOnRails::Utils.react_on_rails_pro? &&
|
|
678
|
+
pro_initializer_has_node_renderer?
|
|
679
|
+
|
|
680
|
+
if uses_node_renderer
|
|
681
|
+
checker.add_info(" Pro uses NodeRenderer for server rendering")
|
|
682
|
+
if defined?(ExecJS) && ExecJS.runtime
|
|
683
|
+
checker.add_info(" ExecJS available as fallback: #{ExecJS.runtime.name}")
|
|
684
|
+
else
|
|
685
|
+
checker.add_warning(" ⚠️ ExecJS fallback is enabled but ExecJS is not available")
|
|
686
|
+
checker.add_info(" 💡 Install mini_racer or set renderer_use_fallback_exec_js = false")
|
|
687
|
+
end
|
|
688
|
+
elsif defined?(ExecJS)
|
|
661
689
|
runtime_name = ExecJS.runtime.name if ExecJS.runtime
|
|
662
690
|
if runtime_name
|
|
663
691
|
checker.add_info(" ExecJS Runtime: #{runtime_name}")
|
|
664
692
|
|
|
665
|
-
# Provide more specific information about the runtime
|
|
666
693
|
case runtime_name
|
|
667
694
|
when /MiniRacer/
|
|
668
695
|
checker.add_info(" ℹ️ Using V8 via mini_racer gem (fast, isolated)")
|
|
@@ -683,7 +710,7 @@ module ReactOnRails
|
|
|
683
710
|
checker.add_warning(" ⚠️ Could not determine server rendering engine: #{e.message}")
|
|
684
711
|
end
|
|
685
712
|
end
|
|
686
|
-
# rubocop:enable Metrics/CyclomaticComplexity
|
|
713
|
+
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
687
714
|
|
|
688
715
|
def check_shakapacker_configuration_details
|
|
689
716
|
return unless File.exist?("config/shakapacker.yml")
|
|
@@ -733,7 +760,7 @@ module ReactOnRails
|
|
|
733
760
|
content = File.read(config_path)
|
|
734
761
|
|
|
735
762
|
checker.add_info("📋 React on Rails Configuration:")
|
|
736
|
-
checker.add_info("📍 Documentation: https://
|
|
763
|
+
checker.add_info("📍 Documentation: https://reactonrails.com/docs/configuration/")
|
|
737
764
|
|
|
738
765
|
# Analyze configuration settings
|
|
739
766
|
analyze_server_rendering_config(content)
|
|
@@ -930,7 +957,7 @@ module ReactOnRails
|
|
|
930
957
|
if /config\.rendering_extension\s*=\s*([^\s\n,]+)/.match?(content)
|
|
931
958
|
checker.add_info("\n🔌 Custom Extensions:")
|
|
932
959
|
checker.add_info(" rendering_extension: Custom rendering logic detected")
|
|
933
|
-
checker.add_info(" ℹ️ See: https://
|
|
960
|
+
checker.add_info(" ℹ️ See: https://reactonrails.com/docs/configuration/#rendering_extension")
|
|
934
961
|
end
|
|
935
962
|
|
|
936
963
|
# Check for rendering props extension
|
|
@@ -968,7 +995,7 @@ module ReactOnRails
|
|
|
968
995
|
deprecated_settings.each do |setting|
|
|
969
996
|
checker.add_warning(" #{setting}")
|
|
970
997
|
end
|
|
971
|
-
checker.add_info("📖 Migration guide: https://
|
|
998
|
+
checker.add_info("📖 Migration guide: https://reactonrails.com/docs/upgrading/upgrading-react-on-rails")
|
|
972
999
|
end
|
|
973
1000
|
|
|
974
1001
|
def check_breaking_changes_warnings
|
|
@@ -1036,7 +1063,7 @@ module ReactOnRails
|
|
|
1036
1063
|
|
|
1037
1064
|
checker.add_info("\n🚨 React on Rails v16+ Breaking Changes Detected:")
|
|
1038
1065
|
issues_found.each { |issue| checker.add_warning(" #{issue}") }
|
|
1039
|
-
checker.add_info("📖 Full migration guide: https://
|
|
1066
|
+
checker.add_info("📖 Full migration guide: https://reactonrails.com/docs/upgrading/upgrading-react-on-rails#upgrading-to-version-16")
|
|
1040
1067
|
end
|
|
1041
1068
|
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
1042
1069
|
|
|
@@ -1117,10 +1144,11 @@ module ReactOnRails
|
|
|
1117
1144
|
end
|
|
1118
1145
|
|
|
1119
1146
|
def npm_test_script?
|
|
1120
|
-
|
|
1147
|
+
package_json_path = resolved_package_json_path
|
|
1148
|
+
return false unless File.exist?(package_json_path)
|
|
1121
1149
|
|
|
1122
1150
|
begin
|
|
1123
|
-
package_json = JSON.parse(File.read(
|
|
1151
|
+
package_json = JSON.parse(File.read(package_json_path))
|
|
1124
1152
|
test_script = package_json.dig("scripts", "test")
|
|
1125
1153
|
test_script && !test_script.empty?
|
|
1126
1154
|
rescue StandardError
|
|
@@ -1216,7 +1244,7 @@ module ReactOnRails
|
|
|
1216
1244
|
if (prerender_set || uses_prerender) && !server_bundle_set
|
|
1217
1245
|
checker.add_warning(" ⚠️ Server rendering is enabled but server_bundle_js_file is not configured")
|
|
1218
1246
|
checker.add_info(" 💡 Set config.server_bundle_js_file = 'server-bundle.js' to enable SSR")
|
|
1219
|
-
checker.add_info(" 💡 See: https://
|
|
1247
|
+
checker.add_info(" 💡 See: https://reactonrails.com/docs/core-concepts/react-server-rendering/")
|
|
1220
1248
|
elsif server_bundle_set && !prerender_set && !uses_prerender
|
|
1221
1249
|
checker.add_info(" ℹ️ server_bundle_js_file is configured but prerender doesn't appear to be used")
|
|
1222
1250
|
checker.add_info(" 💡 Either use prerender: true in react_component calls or remove server_bundle_js_file")
|
|
@@ -1230,17 +1258,28 @@ module ReactOnRails
|
|
|
1230
1258
|
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
1231
1259
|
|
|
1232
1260
|
def uses_prerender_in_views?
|
|
1233
|
-
# Check view files for prerender: true
|
|
1234
1261
|
view_files = Dir.glob("app/views/**/*.{erb,haml,slim}")
|
|
1235
1262
|
view_files.any? do |file|
|
|
1236
1263
|
next unless File.exist?(file)
|
|
1237
1264
|
|
|
1238
|
-
File.read(file)
|
|
1265
|
+
content = File.read(file)
|
|
1266
|
+
# Match explicit prerender: true OR Pro streaming helpers that implicitly prerender
|
|
1267
|
+
content.match?(/prerender:\s*true/) ||
|
|
1268
|
+
content.match?(/stream_react_component|cached_stream_react_component|rsc_payload_react_component/)
|
|
1239
1269
|
end
|
|
1240
1270
|
rescue StandardError
|
|
1241
1271
|
false
|
|
1242
1272
|
end
|
|
1243
1273
|
|
|
1274
|
+
def pro_initializer_has_node_renderer?
|
|
1275
|
+
config_path = "config/initializers/react_on_rails_pro.rb"
|
|
1276
|
+
return false unless File.exist?(config_path)
|
|
1277
|
+
|
|
1278
|
+
File.read(config_path).match?(/server_renderer\s*=\s*["']NodeRenderer["']/)
|
|
1279
|
+
rescue StandardError
|
|
1280
|
+
false
|
|
1281
|
+
end
|
|
1282
|
+
|
|
1244
1283
|
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength, Metrics/BlockNesting
|
|
1245
1284
|
def check_build_test_configuration
|
|
1246
1285
|
config_path = "config/initializers/react_on_rails.rb"
|
|
@@ -1990,7 +2029,7 @@ module ReactOnRails
|
|
|
1990
2029
|
checker.add_info(" 💡 :async can cause race conditions. Options:")
|
|
1991
2030
|
checker.add_info(" 1. Upgrade to React on Rails Pro (recommended for :async support)")
|
|
1992
2031
|
checker.add_info(" 2. Change to :defer or :sync loading strategy")
|
|
1993
|
-
checker.add_info(" 📖 https://
|
|
2032
|
+
checker.add_info(" 📖 https://reactonrails.com/docs/configuration/")
|
|
1994
2033
|
end
|
|
1995
2034
|
|
|
1996
2035
|
def scan_view_files_for_async_pack_tag
|
|
@@ -2154,6 +2193,331 @@ module ReactOnRails
|
|
|
2154
2193
|
- Serve as single source of truth for server bundle location
|
|
2155
2194
|
MSG
|
|
2156
2195
|
end
|
|
2196
|
+
# ── Helpers for Pro/RSC checks ────────────────────────────────────
|
|
2197
|
+
|
|
2198
|
+
# Lazily load the Rails environment so that initializers (which configure
|
|
2199
|
+
# ReactOnRailsPro) have run before we read Pro/RSC config values.
|
|
2200
|
+
# Safe to call multiple times — only loads once.
|
|
2201
|
+
# Returns true if environment was loaded successfully, false otherwise.
|
|
2202
|
+
def ensure_rails_environment_loaded
|
|
2203
|
+
return true if @rails_environment_loaded
|
|
2204
|
+
return false if @rails_environment_attempted
|
|
2205
|
+
|
|
2206
|
+
@rails_environment_attempted = true
|
|
2207
|
+
|
|
2208
|
+
env_file = "config/environment.rb"
|
|
2209
|
+
return false unless File.exist?(env_file)
|
|
2210
|
+
|
|
2211
|
+
require File.expand_path(env_file)
|
|
2212
|
+
@rails_environment_loaded = true
|
|
2213
|
+
rescue StandardError, LoadError => e
|
|
2214
|
+
checker.add_warning(<<~MSG.strip)
|
|
2215
|
+
⚠️ Could not load Rails environment: #{e.message}
|
|
2216
|
+
|
|
2217
|
+
Pro/RSC diagnostics may reflect default values instead of your app's configuration.
|
|
2218
|
+
MSG
|
|
2219
|
+
false
|
|
2220
|
+
end
|
|
2221
|
+
|
|
2222
|
+
# Resolve the JavaScript source path from Shakapacker config.
|
|
2223
|
+
# Falls back to "app/javascript" if Shakapacker is not available.
|
|
2224
|
+
def resolve_js_source_path
|
|
2225
|
+
require "shakapacker"
|
|
2226
|
+
Shakapacker.config.source_path.to_s
|
|
2227
|
+
rescue LoadError, StandardError
|
|
2228
|
+
shakapacker_yml_source_path || "app/javascript"
|
|
2229
|
+
end
|
|
2230
|
+
|
|
2231
|
+
def shakapacker_yml_source_path
|
|
2232
|
+
config_path = "config/shakapacker.yml"
|
|
2233
|
+
return nil unless File.exist?(config_path)
|
|
2234
|
+
|
|
2235
|
+
config = parse_shakapacker_config(File.read(config_path))
|
|
2236
|
+
return nil unless config.is_a?(Hash)
|
|
2237
|
+
|
|
2238
|
+
default_config = config["default"] || {}
|
|
2239
|
+
normalize_yaml_scalar(default_config["source_path"]) if default_config.key?("source_path")
|
|
2240
|
+
rescue StandardError
|
|
2241
|
+
nil
|
|
2242
|
+
end
|
|
2243
|
+
|
|
2244
|
+
# ── React on Rails Pro Setup ──────────────────────────────────────
|
|
2245
|
+
|
|
2246
|
+
def check_pro_setup
|
|
2247
|
+
return unless ReactOnRails::Utils.react_on_rails_pro?
|
|
2248
|
+
|
|
2249
|
+
check_pro_initializer_existence
|
|
2250
|
+
ensure_rails_environment_loaded
|
|
2251
|
+
check_pro_renderer_mode
|
|
2252
|
+
check_base_package_imports
|
|
2253
|
+
end
|
|
2254
|
+
|
|
2255
|
+
def check_pro_initializer_existence
|
|
2256
|
+
initializer_path = "config/initializers/react_on_rails_pro.rb"
|
|
2257
|
+
if File.exist?(initializer_path)
|
|
2258
|
+
checker.add_success("✅ Pro initializer exists (#{initializer_path})")
|
|
2259
|
+
else
|
|
2260
|
+
checker.add_warning(<<~MSG.strip)
|
|
2261
|
+
⚠️ Pro initializer not found at #{initializer_path}.
|
|
2262
|
+
|
|
2263
|
+
Without this file, React on Rails Pro runs with all default settings.
|
|
2264
|
+
Run the Pro generator to create it:
|
|
2265
|
+
rails g react_on_rails:pro
|
|
2266
|
+
MSG
|
|
2267
|
+
end
|
|
2268
|
+
end
|
|
2269
|
+
|
|
2270
|
+
def check_pro_renderer_mode
|
|
2271
|
+
renderer = ReactOnRailsPro.configuration.server_renderer
|
|
2272
|
+
if renderer == "NodeRenderer"
|
|
2273
|
+
checker.add_success("✅ Pro renderer: NodeRenderer (dedicated Node.js process)")
|
|
2274
|
+
else
|
|
2275
|
+
checker.add_info("ℹ️ Pro renderer: #{renderer}")
|
|
2276
|
+
checker.add_info(" 💡 NodeRenderer provides better performance and is required for RSC")
|
|
2277
|
+
end
|
|
2278
|
+
rescue StandardError => e
|
|
2279
|
+
checker.add_warning("⚠️ Could not detect Pro renderer mode: #{e.message}")
|
|
2280
|
+
end
|
|
2281
|
+
|
|
2282
|
+
# The base 'react-on-rails' npm package is a transitive dependency of 'react-on-rails-pro',
|
|
2283
|
+
# so `import ... from 'react-on-rails'` resolves silently — loading the base package instead
|
|
2284
|
+
# of Pro. Components registered through the base package won't have Pro features (streaming,
|
|
2285
|
+
# caching, RSC), and may cause "component not registered" errors at runtime.
|
|
2286
|
+
BASE_PACKAGE_IMPORT_PATTERN = %r{\bfrom\s+['"]react-on-rails(?:/[^'"]*)?['"]}
|
|
2287
|
+
BASE_PACKAGE_REQUIRE_PATTERN = %r{\brequire\s*\(\s*['"]react-on-rails(?:/[^'"]*)?['"]\s*\)}
|
|
2288
|
+
|
|
2289
|
+
def check_base_package_imports # rubocop:disable Metrics/CyclomaticComplexity
|
|
2290
|
+
source_path = resolve_js_source_path
|
|
2291
|
+
js_extensions = %w[js jsx ts tsx]
|
|
2292
|
+
js_patterns = js_extensions.map { |ext| "#{source_path}/**/*.#{ext}" }
|
|
2293
|
+
files_with_base_import = []
|
|
2294
|
+
|
|
2295
|
+
js_patterns.each do |pattern|
|
|
2296
|
+
Dir.glob(pattern).each do |file|
|
|
2297
|
+
content = File.read(file)
|
|
2298
|
+
next unless content.match?(BASE_PACKAGE_IMPORT_PATTERN) || content.match?(BASE_PACKAGE_REQUIRE_PATTERN)
|
|
2299
|
+
|
|
2300
|
+
files_with_base_import << file
|
|
2301
|
+
end
|
|
2302
|
+
end
|
|
2303
|
+
|
|
2304
|
+
if files_with_base_import.empty?
|
|
2305
|
+
checker.add_success("✅ No base 'react-on-rails' imports found (Pro package used correctly)")
|
|
2306
|
+
else
|
|
2307
|
+
checker.add_warning(<<~MSG.strip)
|
|
2308
|
+
⚠️ Found imports from 'react-on-rails' instead of 'react-on-rails-pro':
|
|
2309
|
+
#{files_with_base_import.map { |f| " • #{f}" }.join("\n")}
|
|
2310
|
+
|
|
2311
|
+
The base package is a transitive dependency of Pro, so these imports resolve
|
|
2312
|
+
silently but load the base version without Pro features.
|
|
2313
|
+
|
|
2314
|
+
Fix: Update imports to use 'react-on-rails-pro':
|
|
2315
|
+
import ReactOnRails from 'react-on-rails-pro'; // server
|
|
2316
|
+
import ReactOnRails from 'react-on-rails-pro/client'; // client
|
|
2317
|
+
MSG
|
|
2318
|
+
end
|
|
2319
|
+
rescue StandardError => e
|
|
2320
|
+
checker.add_warning("⚠️ Could not scan for base package imports: #{e.message}")
|
|
2321
|
+
end
|
|
2322
|
+
|
|
2323
|
+
# ── React Server Components ────────────────────────────────────
|
|
2324
|
+
|
|
2325
|
+
# Candidate paths for RSC bundler configuration (webpack and rspack variants)
|
|
2326
|
+
RSC_BUNDLER_CONFIG_PATHS = %w[
|
|
2327
|
+
config/webpack/rscWebpackConfig.js
|
|
2328
|
+
config/rspack/rscWebpackConfig.js
|
|
2329
|
+
].freeze
|
|
2330
|
+
|
|
2331
|
+
def check_rsc_setup
|
|
2332
|
+
return unless ReactOnRails::Utils.react_on_rails_pro?
|
|
2333
|
+
|
|
2334
|
+
ensure_rails_environment_loaded
|
|
2335
|
+
pro_config = ReactOnRailsPro.configuration
|
|
2336
|
+
return unless pro_config.enable_rsc_support
|
|
2337
|
+
|
|
2338
|
+
checker.add_info("🔬 React Server Components: enabled")
|
|
2339
|
+
checker.add_info(" rsc_bundle_js_file: #{pro_config.rsc_bundle_js_file}")
|
|
2340
|
+
checker.add_info(" rsc_payload_generation_url_path: #{pro_config.rsc_payload_generation_url_path}")
|
|
2341
|
+
|
|
2342
|
+
check_rsc_renderer_mode(pro_config)
|
|
2343
|
+
check_rsc_payload_route
|
|
2344
|
+
check_rsc_bundler_config
|
|
2345
|
+
check_rsc_react_version
|
|
2346
|
+
check_rsc_procfile_watcher
|
|
2347
|
+
rescue StandardError => e
|
|
2348
|
+
checker.add_warning("⚠️ RSC setup check encountered an error: #{e.message}")
|
|
2349
|
+
end
|
|
2350
|
+
|
|
2351
|
+
def check_rsc_renderer_mode(pro_config)
|
|
2352
|
+
return if pro_config.server_renderer == "NodeRenderer"
|
|
2353
|
+
|
|
2354
|
+
checker.add_error(<<~MSG.strip)
|
|
2355
|
+
🚫 RSC requires NodeRenderer but current renderer is '#{pro_config.server_renderer}'.
|
|
2356
|
+
|
|
2357
|
+
React Server Components need a dedicated Node.js process for server rendering.
|
|
2358
|
+
|
|
2359
|
+
Fix: Set server_renderer to "NodeRenderer" in config/initializers/react_on_rails_pro.rb:
|
|
2360
|
+
config.server_renderer = "NodeRenderer"
|
|
2361
|
+
MSG
|
|
2362
|
+
end
|
|
2363
|
+
|
|
2364
|
+
def check_rsc_payload_route
|
|
2365
|
+
routes_file = "config/routes.rb"
|
|
2366
|
+
|
|
2367
|
+
unless File.exist?(routes_file)
|
|
2368
|
+
checker.add_warning("⚠️ config/routes.rb not found — cannot verify RSC payload route")
|
|
2369
|
+
return
|
|
2370
|
+
end
|
|
2371
|
+
|
|
2372
|
+
routes_content = File.read(routes_file)
|
|
2373
|
+
uncommented_route = routes_content.each_line.any? do |line|
|
|
2374
|
+
next if line.match?(/^\s*#/)
|
|
2375
|
+
|
|
2376
|
+
line.include?("rsc_payload_route")
|
|
2377
|
+
end
|
|
2378
|
+
if uncommented_route
|
|
2379
|
+
checker.add_success("✅ RSC payload route configured")
|
|
2380
|
+
else
|
|
2381
|
+
checker.add_error(<<~MSG.strip)
|
|
2382
|
+
🚫 RSC payload route not found in config/routes.rb.
|
|
2383
|
+
|
|
2384
|
+
Without this route, React Server Component payload requests will 404.
|
|
2385
|
+
|
|
2386
|
+
Fix: Add to config/routes.rb inside the Rails.application.routes.draw block:
|
|
2387
|
+
rsc_payload_route
|
|
2388
|
+
MSG
|
|
2389
|
+
end
|
|
2390
|
+
end
|
|
2391
|
+
|
|
2392
|
+
def check_rsc_bundler_config
|
|
2393
|
+
found_path = RSC_BUNDLER_CONFIG_PATHS.find { |path| File.exist?(path) }
|
|
2394
|
+
|
|
2395
|
+
if found_path
|
|
2396
|
+
checker.add_success("✅ RSC bundler config exists (#{found_path})")
|
|
2397
|
+
else
|
|
2398
|
+
checker.add_error(<<~MSG.strip)
|
|
2399
|
+
🚫 RSC bundler config not found.
|
|
2400
|
+
|
|
2401
|
+
Expected one of: #{RSC_BUNDLER_CONFIG_PATHS.join(' or ')}
|
|
2402
|
+
|
|
2403
|
+
This file defines the webpack/rspack configuration for the RSC bundle.
|
|
2404
|
+
|
|
2405
|
+
Fix: Run the RSC generator to create it:
|
|
2406
|
+
rails g react_on_rails:rsc
|
|
2407
|
+
MSG
|
|
2408
|
+
end
|
|
2409
|
+
end
|
|
2410
|
+
|
|
2411
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
|
2412
|
+
def check_rsc_react_version
|
|
2413
|
+
react_version = detect_react_version_from_deps
|
|
2414
|
+
unless react_version
|
|
2415
|
+
checker.add_info("ℹ️ Could not detect React version — skipping RSC version check")
|
|
2416
|
+
return
|
|
2417
|
+
end
|
|
2418
|
+
|
|
2419
|
+
major, minor, patch = react_version.split(".").map(&:to_i)
|
|
2420
|
+
|
|
2421
|
+
if major == 19 && minor.zero? && patch >= 4
|
|
2422
|
+
checker.add_success("✅ React #{react_version} is compatible with RSC")
|
|
2423
|
+
elsif major == 19 && minor.zero?
|
|
2424
|
+
checker.add_warning(<<~MSG.strip)
|
|
2425
|
+
⚠️ React #{react_version} has known security vulnerabilities fixed in 19.0.4+.
|
|
2426
|
+
|
|
2427
|
+
Upgrade to at least React 19.0.4:
|
|
2428
|
+
npm install react@~19.0.4 react-dom@~19.0.4
|
|
2429
|
+
MSG
|
|
2430
|
+
elsif major >= 19
|
|
2431
|
+
checker.add_warning(<<~MSG.strip)
|
|
2432
|
+
⚠️ React #{react_version} has not been verified with React on Rails Pro RSC.
|
|
2433
|
+
|
|
2434
|
+
RSC support currently targets React 19.0.x. React #{major}.#{minor}.x may work
|
|
2435
|
+
but has not been tested. Verified compatibility: React 19.0.4+.
|
|
2436
|
+
MSG
|
|
2437
|
+
else
|
|
2438
|
+
checker.add_error(<<~MSG.strip)
|
|
2439
|
+
🚫 React #{react_version} is not compatible with RSC.
|
|
2440
|
+
|
|
2441
|
+
React Server Components in React on Rails Pro requires React 19.x or higher.
|
|
2442
|
+
|
|
2443
|
+
Fix: npm install react@~19.0.4 react-dom@~19.0.4
|
|
2444
|
+
MSG
|
|
2445
|
+
end
|
|
2446
|
+
end
|
|
2447
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
|
2448
|
+
|
|
2449
|
+
def detect_react_version_from_deps
|
|
2450
|
+
# Prefer the actually installed version from node_modules over the declared
|
|
2451
|
+
# range in package.json. Declared ranges like "^19.0.0" would be misleading
|
|
2452
|
+
# (stripped to "19.0.0" even though 19.0.4+ may be installed).
|
|
2453
|
+
installed = installed_react_version
|
|
2454
|
+
return installed if installed
|
|
2455
|
+
|
|
2456
|
+
declared_react_version
|
|
2457
|
+
rescue StandardError
|
|
2458
|
+
nil
|
|
2459
|
+
end
|
|
2460
|
+
|
|
2461
|
+
def installed_react_version
|
|
2462
|
+
# Use Node's own module resolution to find the actually installed React,
|
|
2463
|
+
# which handles hoisted dependencies in monorepos and pnpm workspaces.
|
|
2464
|
+
stdout, _stderr, status = Open3.capture3("node", "-e",
|
|
2465
|
+
"console.log(require.resolve('react/package.json'))")
|
|
2466
|
+
return nil unless status.success?
|
|
2467
|
+
|
|
2468
|
+
resolved_path = stdout.strip
|
|
2469
|
+
return nil if resolved_path.empty? || !File.exist?(resolved_path)
|
|
2470
|
+
|
|
2471
|
+
version = JSON.parse(File.read(resolved_path))["version"]
|
|
2472
|
+
version if version&.match?(/\A\d+\.\d+\.\d+/)
|
|
2473
|
+
rescue StandardError
|
|
2474
|
+
nil
|
|
2475
|
+
end
|
|
2476
|
+
|
|
2477
|
+
def declared_react_version
|
|
2478
|
+
return nil unless File.exist?("package.json")
|
|
2479
|
+
|
|
2480
|
+
package_json = JSON.parse(File.read("package.json"))
|
|
2481
|
+
all_deps = (package_json["dependencies"] || {}).merge(package_json["devDependencies"] || {})
|
|
2482
|
+
version_str = all_deps["react"]
|
|
2483
|
+
return nil unless version_str
|
|
2484
|
+
|
|
2485
|
+
clean_version = version_str.gsub(/\A[^0-9]*/, "")
|
|
2486
|
+
clean_version if clean_version.match?(/\A\d+\.\d+\.\d+\z/)
|
|
2487
|
+
rescue StandardError
|
|
2488
|
+
nil
|
|
2489
|
+
end
|
|
2490
|
+
|
|
2491
|
+
def check_rsc_procfile_watcher
|
|
2492
|
+
procfile_path = "Procfile.dev"
|
|
2493
|
+
|
|
2494
|
+
unless File.exist?(procfile_path)
|
|
2495
|
+
checker.add_warning("⚠️ Procfile.dev not found — cannot verify RSC bundle watcher")
|
|
2496
|
+
checker.add_info(" 💡 If using a custom process manager, ensure RSC bundle is built separately")
|
|
2497
|
+
return
|
|
2498
|
+
end
|
|
2499
|
+
|
|
2500
|
+
uncommented_watcher = File.readlines(procfile_path).any? do |line|
|
|
2501
|
+
next if line.match?(/^\s*#/)
|
|
2502
|
+
|
|
2503
|
+
line.include?("RSC_BUNDLE_ONLY")
|
|
2504
|
+
end
|
|
2505
|
+
if uncommented_watcher
|
|
2506
|
+
checker.add_success("✅ RSC bundle watcher configured in Procfile.dev")
|
|
2507
|
+
else
|
|
2508
|
+
checker.add_warning(<<~MSG.strip)
|
|
2509
|
+
⚠️ RSC bundle watcher not found in Procfile.dev.
|
|
2510
|
+
|
|
2511
|
+
The RSC bundle needs to be built separately from client/server bundles.
|
|
2512
|
+
|
|
2513
|
+
If using Procfile.dev, add:
|
|
2514
|
+
rsc-bundle: RSC_BUNDLE_ONLY=yes bin/shakapacker --watch
|
|
2515
|
+
|
|
2516
|
+
If using a custom process manager, ensure the RSC bundle is built with
|
|
2517
|
+
the RSC_BUNDLE_ONLY=yes environment variable.
|
|
2518
|
+
MSG
|
|
2519
|
+
end
|
|
2520
|
+
end
|
|
2157
2521
|
end
|
|
2158
2522
|
# rubocop:enable Metrics/ClassLength
|
|
2159
2523
|
end
|