react_on_rails 16.0.0 → 16.0.1.rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +117 -77
  3. data/CLAUDE.md +14 -2
  4. data/Gemfile.lock +1 -1
  5. data/LICENSE.md +15 -1
  6. data/README.md +68 -18
  7. data/eslint.config.ts +3 -0
  8. data/knip.ts +20 -9
  9. data/lib/generators/react_on_rails/USAGE +65 -0
  10. data/lib/generators/react_on_rails/base_generator.rb +7 -7
  11. data/lib/generators/react_on_rails/generator_helper.rb +4 -0
  12. data/lib/generators/react_on_rails/generator_messages.rb +2 -2
  13. data/lib/generators/react_on_rails/install_generator.rb +115 -7
  14. data/lib/generators/react_on_rails/react_no_redux_generator.rb +16 -4
  15. data/lib/generators/react_on_rails/react_with_redux_generator.rb +83 -14
  16. data/lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.client.tsx +25 -0
  17. data/lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.server.tsx +5 -0
  18. data/lib/generators/react_on_rails/templates/base/base/bin/dev +12 -24
  19. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/actions/helloWorldActionCreators.ts +18 -0
  20. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/components/HelloWorld.tsx +24 -0
  21. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/constants/helloWorldConstants.ts +6 -0
  22. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/containers/HelloWorldContainer.ts +20 -0
  23. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/reducers/helloWorldReducer.ts +22 -0
  24. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/startup/HelloWorldApp.client.tsx +23 -0
  25. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/startup/HelloWorldApp.server.tsx +5 -0
  26. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/store/helloWorldStore.ts +18 -0
  27. data/lib/react_on_rails/configuration.rb +10 -6
  28. data/lib/react_on_rails/dev/server_manager.rb +185 -28
  29. data/lib/react_on_rails/doctor.rb +1149 -0
  30. data/lib/react_on_rails/helper.rb +9 -78
  31. data/lib/react_on_rails/pro/NOTICE +21 -0
  32. data/lib/react_on_rails/pro/helper.rb +122 -0
  33. data/lib/react_on_rails/pro/utils.rb +53 -0
  34. data/lib/react_on_rails/react_component/render_options.rb +6 -2
  35. data/lib/react_on_rails/system_checker.rb +659 -0
  36. data/lib/react_on_rails/version.rb +1 -1
  37. data/lib/tasks/doctor.rake +51 -0
  38. data/lib/tasks/generate_packs.rake +127 -4
  39. data/package-lock.json +11984 -0
  40. metadata +21 -6
  41. data/lib/generators/react_on_rails/bin/dev +0 -46
@@ -11,15 +11,14 @@ require "addressable/uri"
11
11
  require "react_on_rails/utils"
12
12
  require "react_on_rails/json_output"
13
13
  require "active_support/concern"
14
+ require "react_on_rails/pro/helper"
14
15
 
15
16
  module ReactOnRails
16
17
  module Helper
17
18
  include ReactOnRails::Utils::Required
19
+ include ReactOnRails::Pro::Helper
18
20
 
19
21
  COMPONENT_HTML_KEY = "componentHtml"
20
- IMMEDIATE_HYDRATION_PRO_WARNING = "[REACT ON RAILS] The 'immediate_hydration' feature requires a " \
21
- "React on Rails Pro license. " \
22
- "Please visit https://shakacode.com/react-on-rails-pro to learn more."
23
22
 
24
23
  # react_component_name: can be a React function or class component or a "Render-Function".
25
24
  # "Render-Functions" differ from a React function in that they take two parameters, the
@@ -61,7 +60,6 @@ module ReactOnRails
61
60
  server_rendered_html = internal_result[:result]["html"]
62
61
  console_script = internal_result[:result]["consoleReplayScript"]
63
62
  render_options = internal_result[:render_options]
64
- badge = pro_warning_badge_if_needed(internal_result[:immediate_hydration_requested])
65
63
 
66
64
  case server_rendered_html
67
65
  when String
@@ -71,7 +69,7 @@ module ReactOnRails
71
69
  console_script: console_script,
72
70
  render_options: render_options
73
71
  )
74
- (badge + html).html_safe
72
+ html.html_safe
75
73
  when Hash
76
74
  msg = <<~MSG
77
75
  Use react_component_hash (not react_component) to return a Hash to your ruby view code. See
@@ -218,21 +216,19 @@ module ReactOnRails
218
216
  server_rendered_html = internal_result[:result]["html"]
219
217
  console_script = internal_result[:result]["consoleReplayScript"]
220
218
  render_options = internal_result[:render_options]
221
- badge = pro_warning_badge_if_needed(internal_result[:immediate_hydration_requested])
222
219
 
223
220
  if server_rendered_html.is_a?(String) && internal_result[:result]["hasErrors"]
224
221
  server_rendered_html = { COMPONENT_HTML_KEY => internal_result[:result]["html"] }
225
222
  end
226
223
 
227
224
  if server_rendered_html.is_a?(Hash)
228
- result = build_react_component_result_for_server_rendered_hash(
225
+ build_react_component_result_for_server_rendered_hash(
229
226
  server_rendered_html: server_rendered_html,
230
227
  component_specification_tag: internal_result[:tag],
231
228
  console_script: console_script,
232
229
  render_options: render_options
233
230
  )
234
- result[COMPONENT_HTML_KEY] = badge + result[COMPONENT_HTML_KEY]
235
- result
231
+
236
232
  else
237
233
  msg = <<~MSG
238
234
  Render-Function used by react_component_hash for #{component_name} is expected to return
@@ -260,8 +256,6 @@ module ReactOnRails
260
256
  # hydrate this store immediately instead of waiting for the page to load.
261
257
  def redux_store(store_name, props: {}, defer: false, immediate_hydration: nil)
262
258
  immediate_hydration = ReactOnRails.configuration.immediate_hydration if immediate_hydration.nil?
263
- badge = pro_warning_badge_if_needed(immediate_hydration)
264
- immediate_hydration = false unless support_pro_features?
265
259
 
266
260
  redux_store_data = { store_name: store_name,
267
261
  props: props,
@@ -273,7 +267,7 @@ module ReactOnRails
273
267
  else
274
268
  registered_stores << redux_store_data
275
269
  result = render_redux_store_data(redux_store_data)
276
- (badge + prepend_render_rails_context(result)).html_safe
270
+ prepend_render_rails_context(result).html_safe
277
271
  end
278
272
  end
279
273
 
@@ -452,33 +446,6 @@ module ReactOnRails
452
446
 
453
447
  # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity
454
448
 
455
- # Checks if React on Rails Pro features are available
456
- # @return [Boolean] true if Pro license is valid, false otherwise
457
- def support_pro_features?
458
- ReactOnRails::Utils.react_on_rails_pro_licence_valid?
459
- end
460
-
461
- def pro_warning_badge_if_needed(immediate_hydration)
462
- return "".html_safe unless immediate_hydration
463
- return "".html_safe if support_pro_features?
464
-
465
- puts IMMEDIATE_HYDRATION_PRO_WARNING
466
- Rails.logger.warn IMMEDIATE_HYDRATION_PRO_WARNING
467
-
468
- tooltip_text = "The 'immediate_hydration' feature requires a React on Rails Pro license. Click to learn more."
469
-
470
- badge_html = <<~HTML
471
- <a href="https://shakacode.com/react-on-rails-pro" target="_blank" rel="noopener noreferrer" title="#{tooltip_text}">
472
- <div style="position: fixed; top: 0; right: 0; width: 180px; height: 180px; overflow: hidden; z-index: 9999; pointer-events: none;">
473
- <div style="position: absolute; top: 50px; right: -40px; transform: rotate(45deg); background-color: rgba(220, 53, 69, 0.85); color: white; padding: 7px 40px; text-align: center; font-weight: bold; font-family: sans-serif; font-size: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.3); pointer-events: auto;">
474
- React On Rails Pro Required
475
- </div>
476
- </div>
477
- </a>
478
- HTML
479
- badge_html.strip.html_safe
480
- end
481
-
482
449
  def run_stream_inside_fiber
483
450
  unless ReactOnRails::Utils.react_on_rails_pro?
484
451
  raise ReactOnRails::Error,
@@ -675,31 +642,10 @@ module ReactOnRails
675
642
  # server has already rendered the HTML.
676
643
 
677
644
  render_options = create_render_options(react_component_name, options)
678
- # Capture the originally requested value so we can show a badge while still disabling the feature.
679
- immediate_hydration_requested = render_options.immediate_hydration
680
- render_options.set_option(:immediate_hydration, false) unless support_pro_features?
681
645
 
682
646
  # Setup the page_loaded_js, which is the same regardless of prerendering or not!
683
647
  # The reason is that React is smart about not doing extra work if the server rendering did its job.
684
- component_specification_tag = content_tag(:script,
685
- json_safe_and_pretty(render_options.client_props).html_safe,
686
- type: "application/json",
687
- class: "js-react-on-rails-component",
688
- id: "js-react-on-rails-component-#{render_options.dom_id}",
689
- "data-component-name" => render_options.react_component_name,
690
- "data-trace" => (render_options.trace ? true : nil),
691
- "data-dom-id" => render_options.dom_id,
692
- "data-store-dependencies" => render_options.store_dependencies&.to_json,
693
- "data-immediate-hydration" =>
694
- (render_options.immediate_hydration ? true : nil))
695
-
696
- if render_options.immediate_hydration
697
- component_specification_tag.concat(
698
- content_tag(:script, %(
699
- typeof ReactOnRails === 'object' && ReactOnRails.reactOnRailsComponentLoaded('#{render_options.dom_id}');
700
- ).html_safe)
701
- )
702
- end
648
+ component_specification_tag = generate_component_script(render_options)
703
649
 
704
650
  load_pack_for_generated_component(react_component_name, render_options)
705
651
  # Create the HTML rendering part
@@ -708,27 +654,12 @@ typeof ReactOnRails === 'object' && ReactOnRails.reactOnRailsComponentLoaded('#{
708
654
  {
709
655
  render_options: render_options,
710
656
  tag: component_specification_tag,
711
- result: result,
712
- immediate_hydration_requested: immediate_hydration_requested
657
+ result: result
713
658
  }
714
659
  end
715
660
 
716
661
  def render_redux_store_data(redux_store_data)
717
- store_hydration_data = content_tag(:script,
718
- json_safe_and_pretty(redux_store_data[:props]).html_safe,
719
- type: "application/json",
720
- "data-js-react-on-rails-store" => redux_store_data[:store_name].html_safe,
721
- "data-immediate-hydration" =>
722
- (redux_store_data[:immediate_hydration] ? true : nil))
723
-
724
- if redux_store_data[:immediate_hydration]
725
- store_hydration_data.concat(
726
- content_tag(:script, <<~JS.strip_heredoc.html_safe
727
- typeof ReactOnRails === 'object' && ReactOnRails.reactOnRailsStoreLoaded('#{redux_store_data[:store_name]}');
728
- JS
729
- )
730
- )
731
- end
662
+ store_hydration_data = generate_store_script(redux_store_data)
732
663
 
733
664
  prepend_render_rails_context(store_hydration_data)
734
665
  end
@@ -0,0 +1,21 @@
1
+ # React on Rails Pro License
2
+
3
+ The files in this directory and its subdirectories are licensed under the **React on Rails Pro** license, which is separate from the MIT license that covers the core React on Rails functionality.
4
+
5
+ ## License Terms
6
+
7
+ These files are proprietary software and are **NOT** covered by the MIT license found in the root LICENSE.md file. Usage requires a valid React on Rails Pro license.
8
+
9
+ ## Distribution
10
+
11
+ Files in this directory will be **omitted** from future distributions of the open source React on Rails Ruby gem. They are exclusively available to React on Rails Pro licensees.
12
+
13
+ ## License Reference
14
+
15
+ For the complete React on Rails Pro license terms, see: `REACT-ON-RAILS-PRO-LICENSE.md` in the root directory of this repository.
16
+
17
+ ## More Information
18
+
19
+ For React on Rails Pro licensing information and to obtain a license, please visit:
20
+ - [React on Rails Pro](https://www.shakacode.com/react-on-rails-pro/)
21
+ - Contact: [react_on_rails@shakacode.com](mailto:react_on_rails@shakacode.com)
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ # /*
4
+ # * Copyright (c) 2025 Shakacode LLC
5
+ # *
6
+ # * This file is NOT licensed under the MIT (open source) license.
7
+ # * It is part of the React on Rails Pro offering and is licensed separately.
8
+ # *
9
+ # * Unauthorized copying, modification, distribution, or use of this file,
10
+ # * via any medium, is strictly prohibited without a valid license agreement
11
+ # * from Shakacode LLC.
12
+ # *
13
+ # * For licensing terms, please see:
14
+ # * https://github.com/shakacode/react_on_rails/blob/master/REACT-ON-RAILS-PRO-LICENSE.md
15
+ # */
16
+
17
+ module ReactOnRails
18
+ module Pro
19
+ module Helper
20
+ IMMEDIATE_HYDRATION_PRO_WARNING = "[REACT ON RAILS] The 'immediate_hydration' feature requires a " \
21
+ "React on Rails Pro license. " \
22
+ "Please visit https://shakacode.com/react-on-rails-pro to learn more."
23
+
24
+ # Generates the complete component specification script tag.
25
+ # Handles both immediate hydration (Pro feature) and standard cases.
26
+ def generate_component_script(render_options)
27
+ # Setup the page_loaded_js, which is the same regardless of prerendering or not!
28
+ # The reason is that React is smart about not doing extra work if the server rendering did its job.
29
+ component_specification_tag = content_tag(:script,
30
+ json_safe_and_pretty(render_options.client_props).html_safe,
31
+ type: "application/json",
32
+ class: "js-react-on-rails-component",
33
+ id: "js-react-on-rails-component-#{render_options.dom_id}",
34
+ "data-component-name" => render_options.react_component_name,
35
+ "data-trace" => (render_options.trace ? true : nil),
36
+ "data-dom-id" => render_options.dom_id,
37
+ "data-store-dependencies" =>
38
+ render_options.store_dependencies&.to_json,
39
+ "data-immediate-hydration" =>
40
+ (render_options.immediate_hydration ? true : nil))
41
+
42
+ # Add immediate invocation script if immediate hydration is enabled
43
+ spec_tag = if render_options.immediate_hydration
44
+ # Escape dom_id for JavaScript context
45
+ escaped_dom_id = escape_javascript(render_options.dom_id)
46
+ immediate_script = content_tag(:script, %(
47
+ typeof ReactOnRails === 'object' && ReactOnRails.reactOnRailsComponentLoaded('#{escaped_dom_id}');
48
+ ).html_safe)
49
+ "#{component_specification_tag}\n#{immediate_script}"
50
+ else
51
+ component_specification_tag
52
+ end
53
+
54
+ pro_warning_badge = pro_warning_badge_if_needed(render_options.explicitly_disabled_pro_options)
55
+ "#{pro_warning_badge}\n#{spec_tag}".html_safe
56
+ end
57
+
58
+ # Generates the complete store hydration script tag.
59
+ # Handles both immediate hydration (Pro feature) and standard cases.
60
+ def generate_store_script(redux_store_data)
61
+ pro_options_check_result = ReactOnRails::Pro::Utils.disable_pro_render_options_if_not_licensed(redux_store_data)
62
+ redux_store_data = pro_options_check_result[:raw_options]
63
+ explicitly_disabled_pro_options = pro_options_check_result[:explicitly_disabled_pro_options]
64
+
65
+ store_hydration_data = content_tag(:script,
66
+ json_safe_and_pretty(redux_store_data[:props]).html_safe,
67
+ type: "application/json",
68
+ "data-js-react-on-rails-store" => redux_store_data[:store_name].html_safe,
69
+ "data-immediate-hydration" =>
70
+ (redux_store_data[:immediate_hydration] ? true : nil))
71
+
72
+ # Add immediate invocation script if immediate hydration is enabled and Pro license is valid
73
+ store_hydration_scripts = if redux_store_data[:immediate_hydration]
74
+ # Escape store_name for JavaScript context
75
+ escaped_store_name = escape_javascript(redux_store_data[:store_name])
76
+ immediate_script = content_tag(:script, <<~JS.strip_heredoc.html_safe
77
+ typeof ReactOnRails === 'object' && ReactOnRails.reactOnRailsStoreLoaded('#{escaped_store_name}');
78
+ JS
79
+ )
80
+ "#{store_hydration_data}\n#{immediate_script}"
81
+ else
82
+ store_hydration_data
83
+ end
84
+
85
+ pro_warning_badge = pro_warning_badge_if_needed(explicitly_disabled_pro_options)
86
+ "#{pro_warning_badge}\n#{store_hydration_scripts}".html_safe
87
+ end
88
+
89
+ def pro_warning_badge_if_needed(explicitly_disabled_pro_options)
90
+ return "" unless explicitly_disabled_pro_options.any?
91
+
92
+ disabled_features_message = disabled_pro_features_message(explicitly_disabled_pro_options)
93
+ warning_message = "[REACT ON RAILS] #{disabled_features_message}\n" \
94
+ "Please visit https://shakacode.com/react-on-rails-pro to learn more."
95
+ puts warning_message
96
+ Rails.logger.warn warning_message
97
+
98
+ tooltip_text = "#{disabled_features_message} Click to learn more."
99
+
100
+ <<~HTML.strip
101
+ <a href="https://shakacode.com/react-on-rails-pro" target="_blank" rel="noopener noreferrer" title="#{tooltip_text}">
102
+ <div style="position: fixed; top: 0; right: 0; width: 180px; height: 180px; overflow: hidden; z-index: 9999; pointer-events: none;">
103
+ <div style="position: absolute; top: 50px; right: -40px; transform: rotate(45deg); background-color: rgba(220, 53, 69, 0.85); color: white; padding: 7px 40px; text-align: center; font-weight: bold; font-family: sans-serif; font-size: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.3); pointer-events: auto;">
104
+ React On Rails Pro Required
105
+ </div>
106
+ </div>
107
+ </a>
108
+ HTML
109
+ end
110
+
111
+ def disabled_pro_features_message(explicitly_disabled_pro_options)
112
+ return "".html_safe unless explicitly_disabled_pro_options.any?
113
+
114
+ feature_list = explicitly_disabled_pro_options.join(", ")
115
+ feature_word = explicitly_disabled_pro_options.size == 1 ? "feature" : "features"
116
+ "The '#{feature_list}' #{feature_word} " \
117
+ "#{explicitly_disabled_pro_options.size == 1 ? 'requires' : 'require'} a " \
118
+ "React on Rails Pro license. "
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ # /*
4
+ # * Copyright (c) 2025 Shakacode LLC
5
+ # *
6
+ # * This file is NOT licensed under the MIT (open source) license.
7
+ # * It is part of the React on Rails Pro offering and is licensed separately.
8
+ # *
9
+ # * Unauthorized copying, modification, distribution, or use of this file,
10
+ # * via any medium, is strictly prohibited without a valid license agreement
11
+ # * from Shakacode LLC.
12
+ # *
13
+ # * For licensing terms, please see:
14
+ # * https://github.com/shakacode/react_on_rails/blob/master/REACT-ON-RAILS-PRO-LICENSE.md
15
+ # */
16
+
17
+ module ReactOnRails
18
+ module Pro
19
+ module Utils
20
+ PRO_ONLY_OPTIONS = %i[immediate_hydration].freeze
21
+
22
+ # Checks if React on Rails Pro features are available
23
+ # @return [Boolean] true if Pro license is valid, false otherwise
24
+ def self.support_pro_features?
25
+ ReactOnRails::Utils.react_on_rails_pro_licence_valid?
26
+ end
27
+
28
+ def self.disable_pro_render_options_if_not_licensed(raw_options)
29
+ if support_pro_features?
30
+ return {
31
+ raw_options: raw_options,
32
+ explicitly_disabled_pro_options: []
33
+ }
34
+ end
35
+
36
+ raw_options_after_disable = raw_options.dup
37
+
38
+ explicitly_disabled_pro_options = PRO_ONLY_OPTIONS.select do |option|
39
+ # Use global configuration if it's not overridden in the options
40
+ next ReactOnRails.configuration.send(option) if raw_options[option].nil?
41
+
42
+ raw_options[option]
43
+ end
44
+ explicitly_disabled_pro_options.each { |option| raw_options_after_disable[option] = false }
45
+
46
+ {
47
+ raw_options: raw_options_after_disable,
48
+ explicitly_disabled_pro_options: explicitly_disabled_pro_options
49
+ }
50
+ end
51
+ end
52
+ end
53
+ end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "react_on_rails/utils"
4
+ require "react_on_rails/pro/utils"
4
5
 
5
6
  module ReactOnRails
6
7
  module ReactComponent
@@ -14,10 +15,13 @@ module ReactOnRails
14
15
  # TODO: remove the required for named params
15
16
  def initialize(react_component_name: required("react_component_name"), options: required("options"))
16
17
  @react_component_name = react_component_name.camelize
17
- @options = options
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]
18
22
  end
19
23
 
20
- attr_reader :react_component_name
24
+ attr_reader :react_component_name, :explicitly_disabled_pro_options
21
25
 
22
26
  def throw_js_errors
23
27
  options.fetch(:throw_js_errors, false)