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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +1 -1
  3. data/lib/generators/USAGE +1 -1
  4. data/lib/generators/react_on_rails/generator_messages.rb +20 -18
  5. data/lib/generators/react_on_rails/install_generator.rb +128 -12
  6. data/lib/generators/react_on_rails/pro_generator.rb +1 -1
  7. data/lib/generators/react_on_rails/pro_setup.rb +3 -3
  8. data/lib/generators/react_on_rails/react_with_redux_generator.rb +2 -1
  9. data/lib/generators/react_on_rails/rsc_generator.rb +1 -1
  10. data/lib/generators/react_on_rails/templates/base/base/bin/dev +1 -1
  11. data/lib/generators/react_on_rails/templates/base/base/config/initializers/react_on_rails.rb.tt +3 -3
  12. data/lib/generators/react_on_rails/templates/pro/base/client/node-renderer.js +8 -4
  13. data/lib/generators/react_on_rails/templates/pro/base/config/initializers/react_on_rails_pro.rb.tt +1 -1
  14. data/lib/generators/react_on_rails/templates/rsc/base/app/controllers/hello_server_controller.rb.tt +1 -1
  15. data/lib/generators/react_on_rails/templates/rsc/base/app/javascript/src/HelloServer/components/HelloServer.jsx +1 -1
  16. data/lib/generators/react_on_rails/templates/rsc/base/app/javascript/src/HelloServer/components/HelloServer.tsx +1 -1
  17. data/lib/generators/react_on_rails/templates/rsc/base/app/views/hello_server/index.html.erb +1 -1
  18. data/lib/generators/react_on_rails/templates/rsc/base/config/webpack/rscWebpackConfig.js.tt +1 -1
  19. data/lib/react_on_rails/config_path_resolver.rb +50 -0
  20. data/lib/react_on_rails/configuration.rb +1 -1
  21. data/lib/react_on_rails/dev/process_manager.rb +16 -1
  22. data/lib/react_on_rails/dev/server_manager.rb +2 -2
  23. data/lib/react_on_rails/doctor.rb +389 -25
  24. data/lib/react_on_rails/git_utils.rb +95 -23
  25. data/lib/react_on_rails/helper.rb +3 -3
  26. data/lib/react_on_rails/packer_utils.rb +1 -1
  27. data/lib/react_on_rails/packs_generator.rb +3 -3
  28. data/lib/react_on_rails/react_component/render_options.rb +48 -0
  29. data/lib/react_on_rails/server_rendering_js_code.rb +1 -1
  30. data/lib/react_on_rails/system_checker.rb +42 -19
  31. data/lib/react_on_rails/test_helper/webpack_assets_compiler.rb +1 -2
  32. data/lib/react_on_rails/utils.rb +1 -1
  33. data/lib/react_on_rails/version.rb +1 -1
  34. data/lib/react_on_rails/version_synchronizer.rb +250 -0
  35. data/lib/tasks/generate_packs.rake +4 -4
  36. data/lib/tasks/sync_versions.rake +23 -0
  37. data/rakelib/examples_config.yml +1 -1
  38. data/rakelib/update_changelog.rake +3 -3
  39. data/react_on_rails.gemspec +1 -1
  40. data/sig/react_on_rails/dev/process_manager.rbs +2 -0
  41. metadata +6 -3
@@ -1,34 +1,106 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "English"
4
+ require "open3"
4
5
 
5
6
  module ReactOnRails
6
7
  module GitUtils
8
+ CI_TRUTHY_VALUES = %w[1 true yes].freeze
9
+
10
+ DIRTY_WORKTREE_WARNING = <<~MSG.strip
11
+ You have uncommitted changes. The generator will continue, but reviewing the
12
+ generated diff is easier if you commit or stash your current work first.
13
+ MSG
14
+
15
+ MISSING_GIT_WARNING = <<~MSG.strip
16
+ Git is not installed. The generator will continue, but version control makes it
17
+ much easier to review the generated changes and back out partial installs.
18
+ MSG
19
+
20
+ NOT_A_GIT_REPOSITORY_WARNING = <<~MSG.strip
21
+ Git is installed, but this directory is not a Git repository yet. The generator
22
+ will continue, but initializing Git makes it much easier to review the generated
23
+ changes and back out partial installs.
24
+ MSG
25
+
7
26
  def self.uncommitted_changes?(message_handler, git_installed: true)
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"
11
-
12
- status = `git status --porcelain`
13
- return false if git_installed && status&.empty?
14
-
15
- error = if git_installed
16
- <<~MSG.strip
17
- You have uncommitted changes. Please commit or stash them before continuing.
18
-
19
- The React on Rails generator creates many new files and it's important to keep
20
- your existing changes separate from the generated code for easier review.
21
- MSG
22
- else
23
- <<~MSG.strip
24
- Git is not installed. Please install Git and commit your changes before continuing.
25
-
26
- The React on Rails generator creates many new files and version control helps
27
- track what was generated versus your existing code.
28
- MSG
29
- end
30
- message_handler.add_error(error)
27
+ report_worktree_issues(message_handler, git_installed: git_installed, as_error: true)
28
+ end
29
+
30
+ def self.warn_if_uncommitted_changes(message_handler, git_installed: true)
31
+ report_worktree_issues(message_handler, git_installed: git_installed, as_error: false)
32
+ end
33
+
34
+ def self.skip_worktree_check?
35
+ truthy_env?(ENV.fetch("CI", nil)) || truthy_env?(ENV.fetch("COVERAGE", nil))
36
+ end
37
+
38
+ def self.truthy_env?(value)
39
+ CI_TRUTHY_VALUES.include?(value.to_s.strip.downcase)
40
+ end
41
+
42
+ def self.worktree_status
43
+ output, status = Open3.capture2e("git", "status", "--porcelain")
44
+ return :clean if status.success? && output.strip.empty?
45
+ # Exit code 128 is git's standard fatal error (e.g., not a git repository)
46
+ return :not_a_git_repository if status.exitstatus == 128
47
+
48
+ :dirty
49
+ rescue Errno::ENOENT
50
+ # git binary not found despite passing the cli_exists? check
51
+ :git_not_installed
52
+ end
53
+
54
+ def self.report_worktree_issues(message_handler, git_installed:, as_error:)
55
+ return false if skip_worktree_check?
56
+
57
+ status = git_installed ? worktree_status : :git_not_installed
58
+ return false if status == :clean
59
+
60
+ msg = worktree_message(status, as_error: as_error)
61
+ as_error ? message_handler.add_error(msg) : message_handler.add_warning(msg)
31
62
  true
32
63
  end
64
+ private_class_method :report_worktree_issues
65
+
66
+ def self.worktree_message(status, as_error:)
67
+ case status
68
+ when :not_a_git_repository
69
+ as_error ? not_a_git_repository_error_message : NOT_A_GIT_REPOSITORY_WARNING
70
+ when :git_not_installed
71
+ as_error ? missing_git_error_message : MISSING_GIT_WARNING
72
+ else
73
+ as_error ? dirty_worktree_error_message : DIRTY_WORKTREE_WARNING
74
+ end
75
+ end
76
+ private_class_method :worktree_message
77
+
78
+ def self.dirty_worktree_error_message
79
+ <<~MSG.strip
80
+ You have uncommitted changes. Please commit or stash them before continuing.
81
+
82
+ The React on Rails generator creates many new files and it's important to keep
83
+ your existing changes separate from the generated code for easier review.
84
+ MSG
85
+ end
86
+
87
+ def self.missing_git_error_message
88
+ <<~MSG.strip
89
+ Git is not installed. Please install Git and commit your changes before continuing.
90
+
91
+ The React on Rails generator creates many new files and version control helps
92
+ track what was generated versus your existing code.
93
+ MSG
94
+ end
95
+
96
+ def self.not_a_git_repository_error_message
97
+ <<~MSG.strip
98
+ Git is installed, but this directory is not a Git repository yet. Initialize Git
99
+ and commit or stash your changes before continuing.
100
+
101
+ The React on Rails generator creates many new files and version control helps
102
+ track what was generated versus your existing code.
103
+ MSG
104
+ end
33
105
  end
34
106
  end
@@ -74,7 +74,7 @@ module ReactOnRails
74
74
  when Hash
75
75
  msg = <<~MSG
76
76
  Use react_component_hash (not react_component) to return a Hash to your ruby view code. See
77
- https://github.com/shakacode/react_on_rails/blob/master/spec/dummy/client/app/startup/ReactHelmetServerApp.jsx
77
+ https://github.com/shakacode/react_on_rails/blob/main/react_on_rails/spec/dummy/client/app/startup/ReactHelmetApp.server.jsx
78
78
  for an example of the necessary javascript configuration.
79
79
  MSG
80
80
  raise ReactOnRails::Error, msg
@@ -88,7 +88,7 @@ module ReactOnRails
88
88
 
89
89
  If you're trying to use a Render-Function to return a Hash to your ruby view code, then use
90
90
  react_component_hash instead of react_component and see
91
- https://github.com/shakacode/react_on_rails/blob/master/spec/dummy/client/app/startup/ReactHelmetServerApp.jsx
91
+ https://github.com/shakacode/react_on_rails/blob/main/react_on_rails/spec/dummy/client/app/startup/ReactHelmetApp.server.jsx
92
92
  for an example of the JavaScript code.
93
93
  MSG
94
94
  raise ReactOnRails::Error, msg
@@ -135,7 +135,7 @@ module ReactOnRails
135
135
  else
136
136
  msg = <<~MSG
137
137
  Render-Function used by react_component_hash for #{component_name} is expected to return
138
- an Object. See https://github.com/shakacode/react_on_rails/blob/master/spec/dummy/client/app/startup/ReactHelmetServerApp.jsx
138
+ an Object. See https://github.com/shakacode/react_on_rails/blob/main/react_on_rails/spec/dummy/client/app/startup/ReactHelmetApp.server.jsx
139
139
  for an example of the JavaScript code.
140
140
  Note, your Render-Function must either take 2 params or have the property
141
141
  `.renderFunction = true` added to it to distinguish it from a React Function Component.
@@ -138,7 +138,7 @@ module ReactOnRails
138
138
  msg = <<~MSG
139
139
  **ERROR** ReactOnRails: `nested_entries` is configured to be disabled in shakapacker. Please update \
140
140
  config/shakapacker.yml to enable nested entries. for more information read
141
- https://www.shakacode.com/react-on-rails/docs/guides/file-system-based-automated-bundle-generation.md#enable-nested_entries-for-shakapacker
141
+ https://reactonrails.com/docs/core-concepts/auto-bundling-file-system-based-automated-bundle-generation/#enable-nested_entries-for-shakapacker
142
142
  MSG
143
143
 
144
144
  raise ReactOnRails::Error, msg
@@ -584,7 +584,7 @@ module ReactOnRails
584
584
  msg = <<~MSG
585
585
  **ERROR** ReactOnRails: client specific definition for Component '#{component_name}' overrides the \
586
586
  common definition. Please delete the common definition and have separate server and client files. For more \
587
- information, please see https://www.shakacode.com/react-on-rails/docs/guides/file-system-based-automated-bundle-generation.md
587
+ information, please see https://reactonrails.com/docs/core-concepts/auto-bundling-file-system-based-automated-bundle-generation/
588
588
  MSG
589
589
 
590
590
  raise ReactOnRails::Error, msg
@@ -594,7 +594,7 @@ module ReactOnRails
594
594
  msg = <<~MSG
595
595
  **ERROR** ReactOnRails: server specific definition for Component '#{component_name}' overrides the \
596
596
  common definition. Please delete the common definition and have separate server and client files. For more \
597
- information, please see https://www.shakacode.com/react-on-rails/docs/guides/file-system-based-automated-bundle-generation.md
597
+ information, please see https://reactonrails.com/docs/core-concepts/auto-bundling-file-system-based-automated-bundle-generation/
598
598
  MSG
599
599
 
600
600
  raise ReactOnRails::Error, msg
@@ -603,7 +603,7 @@ module ReactOnRails
603
603
  def raise_missing_client_component(component_name)
604
604
  msg = <<~MSG
605
605
  **ERROR** ReactOnRails: Component '#{component_name}' is missing a client specific file. For more \
606
- information, please see https://www.shakacode.com/react-on-rails/docs/guides/file-system-based-automated-bundle-generation.md
606
+ information, please see https://reactonrails.com/docs/core-concepts/auto-bundling-file-system-based-automated-bundle-generation/
607
607
  MSG
608
608
 
609
609
  raise ReactOnRails::Error, msg
@@ -4,11 +4,51 @@ require "react_on_rails/utils"
4
4
 
5
5
  module ReactOnRails
6
6
  module ReactComponent
7
+ # rubocop:disable Metrics/ClassLength
7
8
  class RenderOptions
8
9
  include Utils::Required
9
10
 
10
11
  attr_accessor :request_digest
11
12
 
13
+ PRERENDER_OVERRIDE_ENV_KEY = "REACT_ON_RAILS_PRERENDER_OVERRIDE"
14
+ PRERENDER_OVERRIDE_VALUES = { "true" => true, "false" => false }.freeze
15
+ PRERENDER_OVERRIDE_CACHE_MUTEX = Mutex.new
16
+ class << self
17
+ def prerender_env_override
18
+ PRERENDER_OVERRIDE_CACHE_MUTEX.synchronize do
19
+ raw_value = ENV.fetch(PRERENDER_OVERRIDE_ENV_KEY, nil)
20
+ cached_override = @prerender_env_override_cache
21
+ return cached_override[:value] if cached_override && cached_override[:raw_value] == raw_value
22
+
23
+ parsed_value = parse_prerender_env_override(raw_value)
24
+ @prerender_env_override_cache = { raw_value: raw_value, value: parsed_value }
25
+ parsed_value
26
+ end
27
+ end
28
+
29
+ def reset_prerender_env_override_cache!
30
+ PRERENDER_OVERRIDE_CACHE_MUTEX.synchronize do
31
+ @prerender_env_override_cache = nil
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def parse_prerender_env_override(raw_value)
38
+ return nil if raw_value.nil?
39
+
40
+ normalized_value = raw_value.strip.downcase
41
+ return PRERENDER_OVERRIDE_VALUES[normalized_value] if PRERENDER_OVERRIDE_VALUES.key?(normalized_value)
42
+
43
+ Rails.logger.warn(
44
+ "[REACT ON RAILS] Ignoring #{PRERENDER_OVERRIDE_ENV_KEY}=#{raw_value.inspect}. " \
45
+ "Expected 'true' or 'false'."
46
+ )
47
+ # Cache invalid values too so we warn once per unique raw env value.
48
+ nil
49
+ end
50
+ end
51
+
12
52
  NO_PROPS = {}.freeze
13
53
 
14
54
  # TODO: remove the required for named params
@@ -68,6 +108,9 @@ module ReactOnRails
68
108
  end
69
109
 
70
110
  def prerender
111
+ env_override = prerender_env_override
112
+ return env_override unless env_override.nil?
113
+
71
114
  retrieve_configuration_value_for(:prerender)
72
115
  end
73
116
 
@@ -172,6 +215,11 @@ module ReactOnRails
172
215
  ReactOnRailsPro.configuration.public_send(key)
173
216
  end
174
217
  end
218
+
219
+ def prerender_env_override
220
+ self.class.prerender_env_override
221
+ end
175
222
  end
223
+ # rubocop:enable Metrics/ClassLength
176
224
  end
177
225
  end
@@ -25,7 +25,7 @@ module ReactOnRails
25
25
  The `prerender` option to allow Server Side Rendering is marked as true but the ReactOnRails configuration
26
26
  for `server_bundle_js_file` is nil or not present in `config/initializers/react_on_rails.rb`.
27
27
  Set `config.server_bundle_js_file` to your javascript bundle to allow server side rendering.
28
- Read more at https://www.shakacode.com/react-on-rails/docs/guides/react-server-rendering/
28
+ Read more at https://reactonrails.com/docs/core-concepts/react-server-rendering/
29
29
  MSG
30
30
  raise ReactOnRails::Error, msg
31
31
  end
@@ -3,14 +3,19 @@
3
3
  require "erb"
4
4
  require "open3"
5
5
  require "yaml"
6
+ require_relative "config_path_resolver"
6
7
 
7
8
  module ReactOnRails
8
9
  # SystemChecker provides validation methods for React on Rails setup
9
10
  # Used by install generator and doctor rake task
10
11
  # rubocop:disable Metrics/ClassLength
11
12
  class SystemChecker
13
+ include ConfigPathResolver
14
+
12
15
  attr_reader :messages
13
16
 
17
+ SUPPORTED_ASSETS_BUNDLERS = %w[webpack rspack].freeze
18
+
14
19
  def initialize
15
20
  @messages = []
16
21
  end
@@ -134,7 +139,8 @@ module ReactOnRails
134
139
  • bin/shakapacker
135
140
  • bin/shakapacker-dev-server
136
141
  • config/shakapacker.yml
137
- • config/{webpack,rspack}/{webpack,rspack}.config.{js,ts}
142
+ • config/webpack/webpack.config.{js,ts}
143
+ • config/rspack/rspack.config.{js,ts}
138
144
 
139
145
  Run: bundle exec rails shakapacker:install
140
146
  MSG
@@ -184,7 +190,7 @@ module ReactOnRails
184
190
  end
185
191
 
186
192
  def check_react_on_rails_npm_package
187
- package_json_path = "package.json"
193
+ package_json_path = resolved_package_json_path
188
194
  return unless File.exist?(package_json_path)
189
195
 
190
196
  package_json = JSON.parse(File.read(package_json_path))
@@ -205,10 +211,11 @@ module ReactOnRails
205
211
  end
206
212
 
207
213
  def check_package_version_sync
208
- return unless File.exist?("package.json")
214
+ package_json_path = resolved_package_json_path
215
+ return unless File.exist?(package_json_path)
209
216
 
210
217
  begin
211
- package_json = JSON.parse(File.read("package.json"))
218
+ package_json = JSON.parse(File.read(package_json_path))
212
219
  package_name, npm_version = react_on_rails_npm_package_details(package_json)
213
220
 
214
221
  return unless npm_version && defined?(ReactOnRails::VERSION)
@@ -256,7 +263,7 @@ module ReactOnRails
256
263
 
257
264
  # React dependencies validation
258
265
  def check_react_dependencies
259
- return unless File.exist?("package.json")
266
+ return unless File.exist?(resolved_package_json_path)
260
267
 
261
268
  package_json = parse_package_json
262
269
  return unless package_json
@@ -324,10 +331,13 @@ module ReactOnRails
324
331
  return paths_by_bundler[configured_bundler].first
325
332
  end
326
333
 
334
+ # Default to webpack when shakapacker.yml doesn't declare assets_bundler.
335
+ # Webpack is the longer-established default; rspack users typically set
336
+ # assets_bundler explicitly in shakapacker.yml.
327
337
  add_warning(
328
- "⚠️ Found both webpack and rspack configs. Could not determine active bundler; defaulting to rspack."
338
+ "⚠️ Found both webpack and rspack configs. Could not determine active bundler; defaulting to webpack."
329
339
  )
330
- paths_by_bundler["rspack"].first || paths_by_bundler["webpack"].first
340
+ paths_by_bundler["webpack"].first || paths_by_bundler["rspack"].first
331
341
  end
332
342
 
333
343
  def suggest_webpack_inspection(config_path)
@@ -369,10 +379,11 @@ module ReactOnRails
369
379
  end
370
380
 
371
381
  def bundle_analyzer_available?
372
- return false unless File.exist?("package.json")
382
+ package_json_path = resolved_package_json_path
383
+ return false unless File.exist?(package_json_path)
373
384
 
374
385
  begin
375
- package_json = JSON.parse(File.read("package.json"))
386
+ package_json = JSON.parse(File.read(package_json_path))
376
387
  all_deps = (package_json["dependencies"] || {}).merge(package_json["devDependencies"] || {})
377
388
  all_deps["webpack-bundle-analyzer"]
378
389
  rescue StandardError
@@ -519,16 +530,27 @@ module ReactOnRails
519
530
  end
520
531
 
521
532
  def configured_assets_bundler
522
- shakapacker_config_path = "config/shakapacker.yml"
523
- return nil unless File.exist?(shakapacker_config_path)
533
+ config = parsed_shakapacker_config
534
+ return nil unless config
524
535
 
525
- config_content = File.read(shakapacker_config_path)
526
- match = config_content.match(/^\s*assets_bundler:\s*["']?(webpack|rspack)["']?\s*$/)
527
- match&.captures&.first
528
- rescue StandardError
536
+ rails_env = ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
537
+ bundler_from_shakapacker_section(config, rails_env) || bundler_from_shakapacker_section(config, "default")
538
+ rescue StandardError, ScriptError
529
539
  nil
530
540
  end
531
541
 
542
+ def bundler_from_shakapacker_section(config, section_name)
543
+ section = config[section_name] || config[section_name.to_sym]
544
+ return nil unless section.is_a?(Hash)
545
+
546
+ normalize_assets_bundler(section["assets_bundler"] || section[:assets_bundler])
547
+ end
548
+
549
+ def normalize_assets_bundler(value)
550
+ normalized = value.to_s.strip.downcase
551
+ SUPPORTED_ASSETS_BUNDLERS.include?(normalized) ? normalized : nil
552
+ end
553
+
532
554
  def required_react_dependencies
533
555
  deps = {
534
556
  "react" => "React library",
@@ -622,8 +644,8 @@ module ReactOnRails
622
644
  # rubocop:enable Metrics/CyclomaticComplexity
623
645
 
624
646
  def parse_package_json
625
- JSON.parse(File.read("package.json"))
626
- rescue JSON::ParserError
647
+ JSON.parse(File.read(resolved_package_json_path))
648
+ rescue Errno::ENOENT, JSON::ParserError
627
649
  add_warning("⚠️ Could not parse package.json to check React dependencies")
628
650
  nil
629
651
  end
@@ -762,10 +784,11 @@ module ReactOnRails
762
784
  end
763
785
 
764
786
  def report_webpack_version
765
- return unless File.exist?("package.json")
787
+ package_json_path = resolved_package_json_path
788
+ return unless File.exist?(package_json_path)
766
789
 
767
790
  begin
768
- package_json = JSON.parse(File.read("package.json"))
791
+ package_json = JSON.parse(File.read(package_json_path))
769
792
  all_deps = (package_json["dependencies"] || {}).merge(package_json["devDependencies"] || {})
770
793
 
771
794
  webpack_version = all_deps["webpack"]
@@ -5,8 +5,7 @@
5
5
  module ReactOnRails
6
6
  module TestHelper
7
7
  class WebpackAssetsCompiler
8
- TESTING_DOCS_URL = "https://github.com/shakacode/react_on_rails/blob/master/" \
9
- "docs/oss/building-features/dev-server-and-testing.md"
8
+ TESTING_DOCS_URL = "https://reactonrails.com/docs/building-features/dev-server-and-testing/"
10
9
 
11
10
  def compile_assets
12
11
  if ReactOnRails.configuration.build_test_command.blank?
@@ -17,7 +17,7 @@ module ReactOnRails
17
17
  def self.immediate_hydration_pro_install_warning(name, type = "Component")
18
18
  "[REACT ON RAILS] Warning: immediate_hydration: true requires the React on Rails Pro gem to be installed.\n" \
19
19
  "#{type} '#{name}' will fall back to standard hydration behavior.\n" \
20
- "Visit https://www.shakacode.com/react-on-rails-pro/ for installation details."
20
+ "Visit https://reactonrails.com/docs/pro/ for installation details."
21
21
  end
22
22
 
23
23
  # Normalizes the immediate_hydration option value, enforcing Pro gem-availability requirements.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ReactOnRails
4
- VERSION = "16.4.0"
4
+ VERSION = "16.5.1"
5
5
  end