react_on_rails 14.2.1 โ†’ 16.1.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 (116) hide show
  1. checksums.yaml +4 -4
  2. data/AI_AGENT_INSTRUCTIONS.md +63 -0
  3. data/CHANGELOG.md +564 -85
  4. data/CLAUDE.md +135 -0
  5. data/CODING_AGENTS.md +313 -0
  6. data/CONTRIBUTING.md +448 -37
  7. data/Gemfile.development_dependencies +6 -1
  8. data/Gemfile.lock +13 -4
  9. data/KUDOS.md +22 -1
  10. data/LICENSE.md +30 -4
  11. data/LICENSES/README.md +14 -0
  12. data/NEWS.md +48 -48
  13. data/PROJECTS.md +45 -40
  14. data/REACT-ON-RAILS-PRO-LICENSE.md +129 -0
  15. data/README.md +113 -42
  16. data/SUMMARY.md +62 -52
  17. data/TODO.md +135 -0
  18. data/bin/lefthook/check-trailing-newlines +38 -0
  19. data/bin/lefthook/get-changed-files +26 -0
  20. data/bin/lefthook/prettier-format +26 -0
  21. data/bin/lefthook/ruby-autofix +26 -0
  22. data/bin/lefthook/ruby-lint +27 -0
  23. data/eslint.config.ts +232 -0
  24. data/knip.ts +40 -6
  25. data/lib/generators/USAGE +4 -5
  26. data/lib/generators/react_on_rails/USAGE +65 -0
  27. data/lib/generators/react_on_rails/base_generator.rb +276 -62
  28. data/lib/generators/react_on_rails/dev_tests_generator.rb +1 -0
  29. data/lib/generators/react_on_rails/generator_helper.rb +35 -1
  30. data/lib/generators/react_on_rails/generator_messages.rb +138 -17
  31. data/lib/generators/react_on_rails/install_generator.rb +474 -26
  32. data/lib/generators/react_on_rails/react_no_redux_generator.rb +19 -6
  33. data/lib/generators/react_on_rails/react_with_redux_generator.rb +110 -18
  34. data/lib/generators/react_on_rails/templates/.eslintrc +1 -1
  35. data/lib/generators/react_on_rails/templates/base/base/Procfile.dev +5 -0
  36. data/lib/generators/react_on_rails/templates/base/base/Procfile.dev-prod-assets +8 -0
  37. data/lib/generators/react_on_rails/templates/base/base/Procfile.dev-static-assets +2 -0
  38. data/lib/generators/react_on_rails/templates/base/base/app/javascript/bundles/HelloWorld/components/HelloWorld.jsx +0 -5
  39. data/lib/generators/react_on_rails/templates/base/base/app/javascript/bundles/HelloWorld/components/HelloWorld.module.css +2 -2
  40. data/lib/generators/react_on_rails/templates/base/base/app/javascript/bundles/HelloWorld/components/HelloWorldServer.js +1 -1
  41. data/lib/generators/react_on_rails/templates/base/base/app/javascript/packs/server-bundle.js +1 -8
  42. data/lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.client.jsx +21 -0
  43. data/lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.client.tsx +25 -0
  44. data/lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.module.css +4 -0
  45. data/lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.server.jsx +5 -0
  46. data/lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.server.tsx +5 -0
  47. data/lib/generators/react_on_rails/templates/base/base/app/views/hello_world/index.html.erb.tt +1 -1
  48. data/lib/generators/react_on_rails/templates/base/base/app/views/layouts/hello_world.html.erb +4 -2
  49. data/lib/generators/react_on_rails/templates/base/base/babel.config.js.tt +5 -2
  50. data/lib/generators/react_on_rails/templates/base/base/bin/dev +34 -0
  51. data/lib/generators/react_on_rails/templates/base/base/config/initializers/react_on_rails.rb.tt +14 -5
  52. data/lib/generators/react_on_rails/templates/base/base/config/shakapacker.yml +76 -7
  53. data/lib/generators/react_on_rails/templates/base/base/config/webpack/commonWebpackConfig.js.tt +1 -1
  54. data/lib/generators/react_on_rails/templates/base/base/config/webpack/development.js.tt +6 -10
  55. data/lib/generators/react_on_rails/templates/base/base/config/webpack/production.js.tt +2 -2
  56. data/lib/generators/react_on_rails/templates/base/base/config/webpack/serverWebpackConfig.js.tt +3 -2
  57. data/lib/generators/react_on_rails/templates/base/base/config/webpack/test.js.tt +2 -2
  58. data/lib/generators/react_on_rails/templates/dev_tests/spec/system/hello_world_spec.rb +0 -2
  59. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/actions/helloWorldActionCreators.ts +18 -0
  60. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/components/HelloWorld.jsx +0 -6
  61. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/components/HelloWorld.module.css +4 -0
  62. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/components/HelloWorld.tsx +24 -0
  63. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/constants/helloWorldConstants.ts +6 -0
  64. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/containers/HelloWorldContainer.ts +20 -0
  65. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/reducers/helloWorldReducer.js +1 -1
  66. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/reducers/helloWorldReducer.ts +22 -0
  67. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/startup/HelloWorldApp.client.tsx +23 -0
  68. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/startup/HelloWorldApp.server.jsx +5 -0
  69. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/startup/HelloWorldApp.server.tsx +5 -0
  70. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/store/helloWorldStore.ts +18 -0
  71. data/lib/react_on_rails/configuration.rb +141 -57
  72. data/lib/react_on_rails/controller.rb +6 -2
  73. data/lib/react_on_rails/dev/file_manager.rb +78 -0
  74. data/lib/react_on_rails/dev/pack_generator.rb +27 -0
  75. data/lib/react_on_rails/dev/process_manager.rb +61 -0
  76. data/lib/react_on_rails/dev/server_manager.rb +487 -0
  77. data/lib/react_on_rails/dev.rb +20 -0
  78. data/lib/react_on_rails/doctor.rb +1149 -0
  79. data/lib/react_on_rails/engine.rb +6 -0
  80. data/lib/react_on_rails/git_utils.rb +12 -2
  81. data/lib/react_on_rails/helper.rb +176 -74
  82. data/lib/react_on_rails/json_parse_error.rb +6 -1
  83. data/lib/react_on_rails/packer_utils.rb +61 -71
  84. data/lib/react_on_rails/packs_generator.rb +221 -19
  85. data/lib/react_on_rails/prerender_error.rb +4 -0
  86. data/lib/react_on_rails/pro/NOTICE +21 -0
  87. data/lib/react_on_rails/pro/helper.rb +122 -0
  88. data/lib/react_on_rails/pro/utils.rb +53 -0
  89. data/lib/react_on_rails/react_component/render_options.rb +38 -6
  90. data/lib/react_on_rails/server_rendering_js_code.rb +0 -1
  91. data/lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb +12 -5
  92. data/lib/react_on_rails/system_checker.rb +659 -0
  93. data/lib/react_on_rails/test_helper/webpack_assets_compiler.rb +1 -1
  94. data/lib/react_on_rails/test_helper/webpack_assets_status_checker.rb +6 -4
  95. data/lib/react_on_rails/test_helper.rb +2 -3
  96. data/lib/react_on_rails/utils.rb +139 -43
  97. data/lib/react_on_rails/version.rb +1 -1
  98. data/lib/react_on_rails/version_checker.rb +14 -20
  99. data/lib/react_on_rails/version_syntax_converter.rb +1 -1
  100. data/lib/react_on_rails.rb +1 -0
  101. data/lib/tasks/assets.rake +1 -1
  102. data/lib/tasks/doctor.rake +48 -0
  103. data/lib/tasks/generate_packs.rake +158 -1
  104. data/react_on_rails.gemspec +1 -0
  105. data/tsconfig.eslint.json +6 -0
  106. data/tsconfig.json +5 -3
  107. metadata +63 -14
  108. data/REACT-ON-RAILS-PRO-LICENSE +0 -95
  109. data/lib/generators/react_on_rails/adapt_for_older_shakapacker_generator.rb +0 -41
  110. data/lib/generators/react_on_rails/bin/dev +0 -30
  111. data/lib/generators/react_on_rails/bin/dev-static +0 -30
  112. data/lib/generators/react_on_rails/templates/base/base/Procfile.dev-static.tt +0 -9
  113. data/lib/generators/react_on_rails/templates/base/base/Procfile.dev.tt +0 -5
  114. data/lib/generators/react_on_rails/templates/base/base/app/javascript/packs/registration.js.tt +0 -8
  115. /data/lib/generators/react_on_rails/templates/base/base/config/webpack/{webpackConfig.js.tt โ†’ generateWebpackConfigs.js.tt} +0 -0
  116. /data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/startup/{HelloWorldApp.jsx โ†’ HelloWorldApp.client.jsx} +0 -0
@@ -1,127 +1,121 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "shakapacker"
4
+
3
5
  module ReactOnRails
4
6
  module PackerUtils
5
- def self.using_packer?
6
- using_shakapacker_const? || using_webpacker_const?
7
+ def self.dev_server_running?
8
+ Shakapacker.dev_server.running?
7
9
  end
8
10
 
9
- def self.using_shakapacker_const?
10
- return @using_shakapacker_const if defined?(@using_shakapacker_const)
11
-
12
- @using_shakapacker_const = ReactOnRails::Utils.gem_available?("shakapacker") &&
13
- shakapacker_version_requirement_met?([7, 0, 0])
11
+ def self.dev_server_url
12
+ "#{Shakapacker.dev_server.protocol}://#{Shakapacker.dev_server.host_with_port}"
14
13
  end
15
14
 
16
- def self.using_webpacker_const?
17
- return @using_webpacker_const if defined?(@using_webpacker_const)
15
+ def self.shakapacker_version
16
+ return @shakapacker_version if defined?(@shakapacker_version)
18
17
 
19
- @using_webpacker_const = (ReactOnRails::Utils.gem_available?("shakapacker") &&
20
- shakapacker_version_as_array[0] <= 6) ||
21
- ReactOnRails::Utils.gem_available?("webpacker")
18
+ @shakapacker_version = Gem.loaded_specs["shakapacker"].version.to_s
22
19
  end
23
20
 
24
- def self.packer_type
25
- return "shakapacker" if using_shakapacker_const?
26
- return "webpacker" if using_webpacker_const?
27
-
28
- nil
29
- end
21
+ def self.shakapacker_version_as_array
22
+ return @shakapacker_version_as_array if defined?(@shakapacker_version_as_array)
30
23
 
31
- def self.packer
32
- return nil unless using_packer?
24
+ match = shakapacker_version.match(ReactOnRails::VersionChecker::VERSION_PARTS_REGEX)
33
25
 
34
- if using_shakapacker_const?
35
- require "shakapacker"
36
- return ::Shakapacker
37
- end
38
- require "webpacker"
39
- ::Webpacker
26
+ # match[4] is the pre-release version, not normally a number but something like "beta.1" or `nil`
27
+ @shakapacker_version_as_array = [match[1].to_i, match[2].to_i, match[3].to_i, match[4]].compact
40
28
  end
41
29
 
42
- def self.dev_server_running?
43
- return false unless using_packer?
44
-
45
- packer.dev_server.running?
30
+ def self.shakapacker_version_requirement_met?(required_version)
31
+ @version_checks ||= {}
32
+ @version_checks[required_version] ||= Gem::Version.new(shakapacker_version) >= Gem::Version.new(required_version)
46
33
  end
47
34
 
48
- def self.shakapacker_version
49
- return @shakapacker_version if defined?(@shakapacker_version)
50
- return nil unless ReactOnRails::Utils.gem_available?("shakapacker")
51
-
52
- @shakapacker_version = Gem.loaded_specs["shakapacker"].version.to_s
35
+ def self.supports_async_loading?
36
+ shakapacker_version_requirement_met?("8.2.0")
53
37
  end
54
38
 
55
- def self.shakapacker_version_as_array
56
- match = shakapacker_version.match(ReactOnRails::VersionChecker::MAJOR_MINOR_PATCH_VERSION_REGEX)
57
-
58
- @shakapacker_version_as_array = [match[1].to_i, match[2].to_i, match[3].to_i]
39
+ def self.supports_basic_pack_generation?
40
+ shakapacker_version_requirement_met?(ReactOnRails::PacksGenerator::MINIMUM_SHAKAPACKER_VERSION)
59
41
  end
60
42
 
61
- def self.shakapacker_version_requirement_met?(required_version)
62
- req_ver = semver_to_string(required_version)
63
-
64
- Gem::Version.new(shakapacker_version) >= Gem::Version.new(req_ver)
43
+ def self.supports_autobundling?
44
+ min_version = ReactOnRails::PacksGenerator::MINIMUM_SHAKAPACKER_VERSION_FOR_AUTO_BUNDLING
45
+ ::Shakapacker.config.respond_to?(:nested_entries?) && shakapacker_version_requirement_met?(min_version)
65
46
  end
66
47
 
67
48
  # This returns either a URL for the webpack-dev-server, non-server bundle or
68
49
  # the hashed server bundle if using the same bundle for the client.
69
50
  # Otherwise returns a file path.
70
51
  def self.bundle_js_uri_from_packer(bundle_name)
71
- hashed_bundle_name = packer.manifest.lookup!(bundle_name)
52
+ hashed_bundle_name = ::Shakapacker.manifest.lookup!(bundle_name)
72
53
 
73
54
  # Support for hashing the server-bundle and having that built
74
55
  # the webpack-dev-server is provided by the config value
75
56
  # "same_bundle_for_client_and_server" where a value of true
76
57
  # would mean that the bundle is created by the webpack-dev-server
77
- is_server_bundle = bundle_name == ReactOnRails.configuration.server_bundle_js_file
58
+ is_bundle_running_on_server = (bundle_name == ReactOnRails.configuration.server_bundle_js_file) ||
59
+ (bundle_name == ReactOnRails.configuration.rsc_bundle_js_file)
78
60
 
79
- if packer.dev_server.running? && (!is_server_bundle ||
61
+ if ::Shakapacker.dev_server.running? && (!is_bundle_running_on_server ||
80
62
  ReactOnRails.configuration.same_bundle_for_client_and_server)
81
- "#{packer.dev_server.protocol}://#{packer.dev_server.host_with_port}#{hashed_bundle_name}"
63
+ "#{dev_server_url}#{hashed_bundle_name}"
82
64
  else
83
65
  File.expand_path(File.join("public", hashed_bundle_name)).to_s
84
66
  end
85
67
  end
86
68
 
87
- def self.precompile?
88
- return ::Webpacker.config.webpacker_precompile? if using_webpacker_const?
89
- return ::Shakapacker.config.shakapacker_precompile? if using_shakapacker_const?
69
+ def self.public_output_uri_path
70
+ "#{::Shakapacker.config.public_output_path.relative_path_from(::Shakapacker.config.public_path)}/"
71
+ end
90
72
 
91
- false
73
+ # The function doesn't ensure that the asset exists.
74
+ # - It just returns url to the asset if dev server is running
75
+ # - Otherwise it returns file path to the asset
76
+ def self.asset_uri_from_packer(asset_name)
77
+ if dev_server_running?
78
+ "#{dev_server_url}/#{public_output_uri_path}#{asset_name}"
79
+ else
80
+ File.join(packer_public_output_path, asset_name).to_s
81
+ end
82
+ end
83
+
84
+ def self.precompile?
85
+ ::Shakapacker.config.shakapacker_precompile?
92
86
  end
93
87
 
94
88
  def self.packer_source_path
95
- packer.config.source_path
89
+ ::Shakapacker.config.source_path
96
90
  end
97
91
 
98
92
  def self.packer_source_entry_path
99
- packer.config.source_entry_path
93
+ ::Shakapacker.config.source_entry_path
100
94
  end
101
95
 
102
96
  def self.nested_entries?
103
- packer.config.nested_entries?
97
+ ::Shakapacker.config.nested_entries?
104
98
  end
105
99
 
106
100
  def self.packer_public_output_path
107
- packer.config.public_output_path.to_s
101
+ ::Shakapacker.config.public_output_path.to_s
108
102
  end
109
103
 
110
104
  def self.manifest_exists?
111
- packer.config.public_manifest_path.exist?
105
+ ::Shakapacker.config.public_manifest_path.exist?
112
106
  end
113
107
 
114
108
  def self.packer_source_path_explicit?
115
- packer.config.send(:data)[:source_path].present?
109
+ ::Shakapacker.config.send(:data)[:source_path].present?
116
110
  end
117
111
 
118
112
  def self.check_manifest_not_cached
119
- return unless using_packer? && packer.config.cache_manifest?
113
+ return unless ::Shakapacker.config.cache_manifest?
120
114
 
121
115
  msg = <<-MSG.strip_heredoc
122
116
  ERROR: you have enabled cache_manifest in the #{Rails.env} env when using the
123
117
  ReactOnRails::TestHelper.configure_rspec_to_compile_assets helper
124
- To fix this: edit your config/#{packer_type}.yml file and set cache_manifest to false for test.
118
+ To fix this: edit your config/shakapacker.yml file and set cache_manifest to false for test.
125
119
  MSG
126
120
  puts wrap_message(msg)
127
121
  exit!
@@ -142,7 +136,7 @@ module ReactOnRails
142
136
  def self.raise_nested_entries_disabled
143
137
  msg = <<~MSG
144
138
  **ERROR** ReactOnRails: `nested_entries` is configured to be disabled in shakapacker. Please update \
145
- config/#{packer_type}.yml to enable nested entries. for more information read
139
+ config/shakapacker.yml to enable nested entries. for more information read
146
140
  https://www.shakacode.com/react-on-rails/docs/guides/file-system-based-automated-bundle-generation.md#enable-nested_entries-for-shakapacker
147
141
  MSG
148
142
 
@@ -151,26 +145,22 @@ module ReactOnRails
151
145
 
152
146
  def self.raise_shakapacker_version_incompatible_for_autobundling
153
147
  msg = <<~MSG
154
- **ERROR** ReactOnRails: Please upgrade Shakapacker to version #{semver_to_string(ReactOnRails::PacksGenerator::MINIMUM_SHAKAPACKER_VERSION)} or \
155
- above to use the automated bundle generation feature. The currently installed version is \
156
- #{semver_to_string(ReactOnRails::PackerUtils.shakapacker_version_as_array)}.
148
+ **ERROR** ReactOnRails: Please upgrade ::Shakapacker to version #{ReactOnRails::PacksGenerator::MINIMUM_SHAKAPACKER_VERSION_FOR_AUTO_BUNDLING} or \
149
+ above to use the automated bundle generation feature (which requires nested_entries support). \
150
+ The currently installed version is #{ReactOnRails::PackerUtils.shakapacker_version}. \
151
+ Basic pack generation requires ::Shakapacker #{ReactOnRails::PacksGenerator::MINIMUM_SHAKAPACKER_VERSION} or above.
157
152
  MSG
158
153
 
159
154
  raise ReactOnRails::Error, msg
160
155
  end
161
156
 
162
- def self.raise_shakapacker_not_installed
157
+ def self.raise_shakapacker_version_incompatible_for_basic_pack_generation
163
158
  msg = <<~MSG
164
- **ERROR** ReactOnRails: Missing Shakapacker gem. Please upgrade to use Shakapacker \
165
- #{semver_to_string(ReactOnRails::PacksGenerator::MINIMUM_SHAKAPACKER_VERSION)} or above to use the \
166
- automated bundle generation feature.
159
+ **ERROR** ReactOnRails: Please upgrade ::Shakapacker to version #{ReactOnRails::PacksGenerator::MINIMUM_SHAKAPACKER_VERSION} or \
160
+ above to use basic pack generation features. The currently installed version is #{ReactOnRails::PackerUtils.shakapacker_version}.
167
161
  MSG
168
162
 
169
163
  raise ReactOnRails::Error, msg
170
164
  end
171
-
172
- def self.semver_to_string(ary)
173
- "#{ary[0]}.#{ary[1]}.#{ary[2]}"
174
- end
175
165
  end
176
166
  end
@@ -1,12 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "fileutils"
4
+ require "set"
4
5
 
5
6
  module ReactOnRails
6
7
  # rubocop:disable Metrics/ClassLength
7
8
  class PacksGenerator
8
9
  CONTAINS_CLIENT_OR_SERVER_REGEX = /\.(server|client)($|\.)/
9
- MINIMUM_SHAKAPACKER_VERSION = [6, 5, 1].freeze
10
+ COMPONENT_EXTENSIONS = /\.(jsx?|tsx?)$/
11
+ MINIMUM_SHAKAPACKER_VERSION = "6.5.1"
12
+ # Auto-registration requires nested_entries support which was added in 7.0.0
13
+ MINIMUM_SHAKAPACKER_VERSION_FOR_AUTO_BUNDLING = "7.0.0"
10
14
 
11
15
  def self.instance
12
16
  @instance ||= PacksGenerator.new
@@ -16,13 +20,20 @@ module ReactOnRails
16
20
  return unless ReactOnRails.configuration.auto_load_bundle
17
21
 
18
22
  add_generated_pack_to_server_bundle
23
+
24
+ # Clean any non-generated files from directories
25
+ clean_non_generated_files_with_feedback
26
+
19
27
  are_generated_files_present_and_up_to_date = Dir.exist?(generated_packs_directory_path) &&
20
28
  File.exist?(generated_server_bundle_file_path) &&
21
29
  !stale_or_missing_packs?
22
30
 
23
- return if are_generated_files_present_and_up_to_date
31
+ if are_generated_files_present_and_up_to_date
32
+ puts Rainbow("โœ… Generated packs are up to date, no regeneration needed").green
33
+ return
34
+ end
24
35
 
25
- clean_generated_packs_directory
36
+ clean_generated_directories_with_feedback
26
37
  generate_packs
27
38
  end
28
39
 
@@ -44,11 +55,66 @@ module ReactOnRails
44
55
  puts(Rainbow("Generated Packs: #{output_path}").yellow)
45
56
  end
46
57
 
58
+ def first_js_statement_in_code(content) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
59
+ return "" if content.nil? || content.empty?
60
+
61
+ start_index = 0
62
+ content_length = content.length
63
+
64
+ while start_index < content_length
65
+ # Skip whitespace
66
+ start_index += 1 while start_index < content_length && content[start_index].match?(/\s/)
67
+
68
+ break if start_index >= content_length
69
+
70
+ current_chars = content[start_index, 2]
71
+
72
+ case current_chars
73
+ when "//"
74
+ # Single-line comment
75
+ newline_index = content.index("\n", start_index)
76
+ return "" if newline_index.nil?
77
+
78
+ start_index = newline_index + 1
79
+ when "/*"
80
+ # Multi-line comment
81
+ comment_end = content.index("*/", start_index)
82
+ return "" if comment_end.nil?
83
+
84
+ start_index = comment_end + 2
85
+ else
86
+ # Found actual content
87
+ next_line_index = content.index("\n", start_index)
88
+ return next_line_index ? content[start_index...next_line_index].strip : content[start_index..].strip
89
+ end
90
+ end
91
+
92
+ ""
93
+ end
94
+
95
+ def client_entrypoint?(file_path)
96
+ content = File.read(file_path)
97
+ # has "use client" directive. It can be "use client" or 'use client'
98
+ first_js_statement_in_code(content).match?(/^["']use client["'](?:;|\s|$)/)
99
+ end
100
+
47
101
  def pack_file_contents(file_path)
48
102
  registered_component_name = component_name(file_path)
49
- <<~FILE_CONTENT
103
+ load_server_components = ReactOnRails::Utils.rsc_support_enabled?
104
+
105
+ if load_server_components && !client_entrypoint?(file_path)
106
+ return <<~FILE_CONTENT.strip
107
+ import registerServerComponent from 'react-on-rails/registerServerComponent/client';
108
+
109
+ registerServerComponent("#{registered_component_name}");
110
+ FILE_CONTENT
111
+ end
112
+
113
+ relative_component_path = relative_component_path_from_generated_pack(file_path)
114
+
115
+ <<~FILE_CONTENT.strip
50
116
  import ReactOnRails from 'react-on-rails/client';
51
- import #{registered_component_name} from '#{relative_component_path_from_generated_pack(file_path)}';
117
+ import #{registered_component_name} from '#{relative_component_path}';
52
118
 
53
119
  ReactOnRails.register({#{registered_component_name}});
54
120
  FILE_CONTENT
@@ -61,27 +127,46 @@ module ReactOnRails
61
127
  puts(Rainbow("Generated Server Bundle: #{generated_server_bundle_file_path}").orange)
62
128
  end
63
129
 
130
+ def build_server_pack_content(component_on_server_imports, server_components, client_components)
131
+ content = <<~FILE_CONTENT
132
+ import ReactOnRails from 'react-on-rails';
133
+
134
+ #{component_on_server_imports.join("\n")}\n
135
+ FILE_CONTENT
136
+
137
+ if server_components.any?
138
+ content += <<~FILE_CONTENT
139
+ import registerServerComponent from 'react-on-rails/registerServerComponent/server';
140
+ registerServerComponent({#{server_components.join(",\n")}});\n
141
+ FILE_CONTENT
142
+ end
143
+
144
+ content + "ReactOnRails.register({#{client_components.join(",\n")}});"
145
+ end
146
+
64
147
  def generated_server_pack_file_content
65
148
  common_components_for_server_bundle = common_component_to_path.delete_if { |k| server_component_to_path.key?(k) }
66
149
  component_for_server_registration_to_path = common_components_for_server_bundle.merge(server_component_to_path)
67
150
 
68
- server_component_imports = component_for_server_registration_to_path.map do |name, component_path|
151
+ component_on_server_imports = component_for_server_registration_to_path.map do |name, component_path|
69
152
  "import #{name} from '#{relative_path(generated_server_bundle_file_path, component_path)}';"
70
153
  end
71
154
 
72
- components_to_register = component_for_server_registration_to_path.keys
155
+ load_server_components = ReactOnRails::Utils.rsc_support_enabled?
156
+ server_components = component_for_server_registration_to_path.keys.delete_if do |name|
157
+ next true unless load_server_components
73
158
 
74
- <<~FILE_CONTENT
75
- import ReactOnRails from 'react-on-rails';
76
-
77
- #{server_component_imports.join("\n")}
159
+ component_path = component_for_server_registration_to_path[name]
160
+ client_entrypoint?(component_path)
161
+ end
162
+ client_components = component_for_server_registration_to_path.keys - server_components
78
163
 
79
- ReactOnRails.register({#{components_to_register.join(",\n")}});
80
- FILE_CONTENT
164
+ build_server_pack_content(component_on_server_imports, server_components, client_components)
81
165
  end
82
166
 
83
167
  def add_generated_pack_to_server_bundle
84
168
  return if ReactOnRails.configuration.make_generated_server_bundle_the_entrypoint
169
+ return if ReactOnRails.configuration.server_bundle_js_file.blank?
85
170
 
86
171
  relative_path_to_generated_server_bundle = relative_path(server_bundle_entrypoint,
87
172
  generated_server_bundle_file_path)
@@ -109,9 +194,112 @@ module ReactOnRails
109
194
  "#{generated_nonentrypoints_path}/#{generated_server_bundle_file_name}.js"
110
195
  end
111
196
 
112
- def clean_generated_packs_directory
113
- FileUtils.rm_rf(generated_packs_directory_path)
114
- FileUtils.mkdir_p(generated_packs_directory_path)
197
+ def clean_non_generated_files_with_feedback
198
+ directories_to_clean = [generated_packs_directory_path, generated_server_bundle_directory_path].compact.uniq
199
+ expected_files = build_expected_files_set
200
+
201
+ puts Rainbow("๐Ÿงน Cleaning non-generated files...").yellow
202
+
203
+ total_deleted = directories_to_clean.sum do |dir_path|
204
+ clean_unexpected_files_from_directory(dir_path, expected_files)
205
+ end
206
+
207
+ display_cleanup_summary(total_deleted)
208
+ end
209
+
210
+ def build_expected_files_set
211
+ expected_pack_files = Set.new
212
+ common_component_to_path.each_value { |path| expected_pack_files << generated_pack_path(path) }
213
+ client_component_to_path.each_value { |path| expected_pack_files << generated_pack_path(path) }
214
+
215
+ if ReactOnRails.configuration.server_bundle_js_file.present?
216
+ expected_server_bundle = generated_server_bundle_file_path
217
+ end
218
+
219
+ { pack_files: expected_pack_files, server_bundle: expected_server_bundle }
220
+ end
221
+
222
+ def clean_unexpected_files_from_directory(dir_path, expected_files)
223
+ return 0 unless Dir.exist?(dir_path)
224
+
225
+ existing_files = Dir.glob("#{dir_path}/**/*").select { |f| File.file?(f) }
226
+ unexpected_files = find_unexpected_files(existing_files, dir_path, expected_files)
227
+
228
+ if unexpected_files.any?
229
+ delete_unexpected_files(unexpected_files, dir_path)
230
+ unexpected_files.length
231
+ else
232
+ puts Rainbow(" No unexpected files found in #{dir_path}").cyan
233
+ 0
234
+ end
235
+ end
236
+
237
+ def find_unexpected_files(existing_files, dir_path, expected_files)
238
+ existing_files.reject do |file|
239
+ if dir_path == generated_server_bundle_directory_path
240
+ file == expected_files[:server_bundle]
241
+ else
242
+ expected_files[:pack_files].include?(file)
243
+ end
244
+ end
245
+ end
246
+
247
+ def delete_unexpected_files(unexpected_files, dir_path)
248
+ puts Rainbow(" Deleting #{unexpected_files.length} unexpected files from #{dir_path}:").cyan
249
+ unexpected_files.each do |file|
250
+ puts Rainbow(" - #{File.basename(file)}").blue
251
+ File.delete(file)
252
+ end
253
+ end
254
+
255
+ def display_cleanup_summary(total_deleted)
256
+ if total_deleted.positive?
257
+ puts Rainbow("๐Ÿ—‘๏ธ Deleted #{total_deleted} unexpected files total").red
258
+ else
259
+ puts Rainbow("โœจ No unexpected files to delete").green
260
+ end
261
+ end
262
+
263
+ def clean_generated_directories_with_feedback
264
+ directories_to_clean = [
265
+ generated_packs_directory_path,
266
+ generated_server_bundle_directory_path
267
+ ].compact.uniq
268
+
269
+ puts Rainbow("๐Ÿงน Cleaning generated directories...").yellow
270
+
271
+ total_deleted = directories_to_clean.sum { |dir_path| clean_directory_with_feedback(dir_path) }
272
+
273
+ if total_deleted.positive?
274
+ puts Rainbow("๐Ÿ—‘๏ธ Deleted #{total_deleted} generated files total").red
275
+ else
276
+ puts Rainbow("โœจ No files to delete, directories are clean").green
277
+ end
278
+ end
279
+
280
+ def clean_directory_with_feedback(dir_path)
281
+ return create_directory_with_feedback(dir_path) unless Dir.exist?(dir_path)
282
+
283
+ files = Dir.glob("#{dir_path}/**/*").select { |f| File.file?(f) }
284
+
285
+ if files.any?
286
+ puts Rainbow(" Deleting #{files.length} files from #{dir_path}:").cyan
287
+ files.each { |file| puts Rainbow(" - #{File.basename(file)}").blue }
288
+ FileUtils.rm_rf(dir_path)
289
+ FileUtils.mkdir_p(dir_path)
290
+ files.length
291
+ else
292
+ puts Rainbow(" Directory #{dir_path} is already empty").cyan
293
+ FileUtils.rm_rf(dir_path)
294
+ FileUtils.mkdir_p(dir_path)
295
+ 0
296
+ end
297
+ end
298
+
299
+ def create_directory_with_feedback(dir_path)
300
+ puts Rainbow(" Directory #{dir_path} does not exist, creating...").cyan
301
+ FileUtils.mkdir_p(dir_path)
302
+ 0
115
303
  end
116
304
 
117
305
  def server_bundle_entrypoint
@@ -125,6 +313,13 @@ module ReactOnRails
125
313
  "#{source_entry_path}/generated"
126
314
  end
127
315
 
316
+ def generated_server_bundle_directory_path
317
+ return nil if ReactOnRails.configuration.make_generated_server_bundle_the_entrypoint
318
+
319
+ source_entrypoint_parent = Pathname(ReactOnRails::PackerUtils.packer_source_entry_path).parent
320
+ "#{source_entrypoint_parent}/generated"
321
+ end
322
+
128
323
  def relative_component_path_from_generated_pack(ror_component_path)
129
324
  component_file_pathname = Pathname.new(ror_component_path)
130
325
  component_generated_pack_path = generated_pack_path(ror_component_path)
@@ -155,14 +350,20 @@ module ReactOnRails
155
350
  paths.to_h { |path| [component_name(path), path] }
156
351
  end
157
352
 
353
+ def filter_component_files(paths)
354
+ paths.grep(COMPONENT_EXTENSIONS)
355
+ end
356
+
158
357
  def common_component_to_path
159
358
  common_components_paths = Dir.glob("#{components_search_path}/*").grep_v(CONTAINS_CLIENT_OR_SERVER_REGEX)
160
- component_name_to_path(common_components_paths)
359
+ filtered_paths = filter_component_files(common_components_paths)
360
+ component_name_to_path(filtered_paths)
161
361
  end
162
362
 
163
363
  def client_component_to_path
164
364
  client_render_components_paths = Dir.glob("#{components_search_path}/*.client.*")
165
- client_specific_components = component_name_to_path(client_render_components_paths)
365
+ filtered_client_paths = filter_component_files(client_render_components_paths)
366
+ client_specific_components = component_name_to_path(filtered_client_paths)
166
367
 
167
368
  duplicate_components = common_component_to_path.slice(*client_specific_components.keys)
168
369
  duplicate_components.each_key { |component| raise_client_component_overrides_common(component) }
@@ -172,7 +373,8 @@ module ReactOnRails
172
373
 
173
374
  def server_component_to_path
174
375
  server_render_components_paths = Dir.glob("#{components_search_path}/*.server.*")
175
- server_specific_components = component_name_to_path(server_render_components_paths)
376
+ filtered_server_paths = filter_component_files(server_render_components_paths)
377
+ server_specific_components = component_name_to_path(filtered_server_paths)
176
378
 
177
379
  duplicate_components = common_component_to_path.slice(*server_specific_components.keys)
178
380
  duplicate_components.each_key { |component| raise_server_component_overrides_common(component) }
@@ -83,6 +83,10 @@ module ReactOnRails
83
83
  MSG
84
84
 
85
85
  end
86
+
87
+ # Add help and support information
88
+ message << "\n#{Utils.default_troubleshooting_section}\n"
89
+
86
90
  [backtrace, message]
87
91
  end
88
92
  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)