react_on_rails 15.0.0.alpha.2 → 15.0.0.rc.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 (32) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +443 -78
  3. data/CONTRIBUTING.md +53 -35
  4. data/Gemfile.development_dependencies +1 -1
  5. data/Gemfile.lock +4 -4
  6. data/KUDOS.md +22 -1
  7. data/NEWS.md +48 -48
  8. data/PROJECTS.md +45 -40
  9. data/README.md +24 -16
  10. data/SUMMARY.md +62 -52
  11. data/eslint.config.ts +213 -0
  12. data/knip.ts +19 -10
  13. data/lib/generators/USAGE +1 -1
  14. data/lib/generators/react_on_rails/dev_tests_generator.rb +4 -8
  15. data/lib/generators/react_on_rails/templates/.eslintrc +1 -1
  16. data/lib/generators/react_on_rails/templates/base/base/app/javascript/bundles/HelloWorld/components/HelloWorld.module.css +2 -2
  17. data/lib/generators/react_on_rails/templates/base/base/app/javascript/bundles/HelloWorld/components/HelloWorldServer.js +1 -1
  18. data/lib/generators/react_on_rails/templates/base/base/app/javascript/packs/registration.js.tt +1 -1
  19. data/lib/generators/react_on_rails/templates/base/base/config/shakapacker.yml +1 -1
  20. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/reducers/helloWorldReducer.js +1 -1
  21. data/lib/react_on_rails/configuration.rb +52 -8
  22. data/lib/react_on_rails/controller.rb +4 -2
  23. data/lib/react_on_rails/helper.rb +16 -4
  24. data/lib/react_on_rails/packs_generator.rb +4 -10
  25. data/lib/react_on_rails/react_component/render_options.rb +13 -1
  26. data/lib/react_on_rails/test_helper/webpack_assets_status_checker.rb +2 -0
  27. data/lib/react_on_rails/utils.rb +23 -0
  28. data/lib/react_on_rails/version.rb +1 -1
  29. data/lib/tasks/assets.rake +1 -1
  30. data/tsconfig.eslint.json +6 -0
  31. data/tsconfig.json +3 -2
  32. metadata +5 -5
@@ -10,6 +10,7 @@ module ReactOnRails
10
10
 
11
11
  DEFAULT_GENERATED_ASSETS_DIR = File.join(%w[public webpack], Rails.env).freeze
12
12
  DEFAULT_REACT_CLIENT_MANIFEST_FILE = "react-client-manifest.json"
13
+ DEFAULT_REACT_SERVER_CLIENT_MANIFEST_FILE = "react-server-client-manifest.json"
13
14
  DEFAULT_COMPONENT_REGISTRY_TIMEOUT = 5000
14
15
 
15
16
  def self.configuration
@@ -21,6 +22,7 @@ module ReactOnRails
21
22
  server_bundle_js_file: "",
22
23
  rsc_bundle_js_file: "",
23
24
  react_client_manifest_file: DEFAULT_REACT_CLIENT_MANIFEST_FILE,
25
+ react_server_client_manifest_file: DEFAULT_REACT_SERVER_CLIENT_MANIFEST_FILE,
24
26
  prerender: false,
25
27
  auto_load_bundle: false,
26
28
  replay_console: true,
@@ -49,7 +51,8 @@ module ReactOnRails
49
51
  # Maximum time in milliseconds to wait for client-side component registration after page load.
50
52
  # If exceeded, an error will be thrown for server-side rendered components not registered on the client.
51
53
  # Set to 0 to disable the timeout and wait indefinitely for component registration.
52
- component_registry_timeout: DEFAULT_COMPONENT_REGISTRY_TIMEOUT
54
+ component_registry_timeout: DEFAULT_COMPONENT_REGISTRY_TIMEOUT,
55
+ generated_component_packs_loading_strategy: nil
53
56
  )
54
57
  end
55
58
 
@@ -60,28 +63,29 @@ module ReactOnRails
60
63
  :generated_assets_dirs, :generated_assets_dir, :components_subdirectory,
61
64
  :webpack_generated_files, :rendering_extension, :build_test_command,
62
65
  :build_production_command, :i18n_dir, :i18n_yml_dir, :i18n_output_format,
63
- :i18n_yml_safe_load_options,
66
+ :i18n_yml_safe_load_options, :defer_generated_component_packs,
64
67
  :server_render_method, :random_dom_id, :auto_load_bundle,
65
68
  :same_bundle_for_client_and_server, :rendering_props_extension,
66
69
  :make_generated_server_bundle_the_entrypoint,
67
- :defer_generated_component_packs, :force_load, :rsc_bundle_js_file,
68
- :react_client_manifest_file, :component_registry_timeout
70
+ :generated_component_packs_loading_strategy, :force_load, :rsc_bundle_js_file,
71
+ :react_client_manifest_file, :react_server_client_manifest_file, :component_registry_timeout
69
72
 
70
73
  # rubocop:disable Metrics/AbcSize
71
74
  def initialize(node_modules_location: nil, server_bundle_js_file: nil, prerender: nil,
72
75
  replay_console: nil, make_generated_server_bundle_the_entrypoint: nil,
73
- trace: nil, development_mode: nil,
76
+ trace: nil, development_mode: nil, defer_generated_component_packs: nil,
74
77
  logging_on_server: nil, server_renderer_pool_size: nil,
75
78
  server_renderer_timeout: nil, raise_on_prerender_error: true,
76
79
  skip_display_none: nil, generated_assets_dirs: nil,
77
80
  generated_assets_dir: nil, webpack_generated_files: nil,
78
81
  rendering_extension: nil, build_test_command: nil,
79
- build_production_command: nil, defer_generated_component_packs: nil,
82
+ build_production_command: nil, generated_component_packs_loading_strategy: nil,
80
83
  same_bundle_for_client_and_server: nil,
81
84
  i18n_dir: nil, i18n_yml_dir: nil, i18n_output_format: nil, i18n_yml_safe_load_options: nil,
82
85
  random_dom_id: nil, server_render_method: nil, rendering_props_extension: nil,
83
86
  components_subdirectory: nil, auto_load_bundle: nil, force_load: nil,
84
- rsc_bundle_js_file: nil, react_client_manifest_file: nil, component_registry_timeout: nil)
87
+ rsc_bundle_js_file: nil, react_client_manifest_file: nil, react_server_client_manifest_file: nil,
88
+ component_registry_timeout: nil)
85
89
  self.node_modules_location = node_modules_location.present? ? node_modules_location : Rails.root
86
90
  self.generated_assets_dirs = generated_assets_dirs
87
91
  self.generated_assets_dir = generated_assets_dir
@@ -111,6 +115,7 @@ module ReactOnRails
111
115
  self.server_bundle_js_file = server_bundle_js_file
112
116
  self.rsc_bundle_js_file = rsc_bundle_js_file
113
117
  self.react_client_manifest_file = react_client_manifest_file
118
+ self.react_server_client_manifest_file = react_server_client_manifest_file
114
119
  self.same_bundle_for_client_and_server = same_bundle_for_client_and_server
115
120
  self.server_renderer_pool_size = self.development_mode ? 1 : server_renderer_pool_size
116
121
  self.server_renderer_timeout = server_renderer_timeout # seconds
@@ -124,6 +129,7 @@ module ReactOnRails
124
129
  self.make_generated_server_bundle_the_entrypoint = make_generated_server_bundle_the_entrypoint
125
130
  self.defer_generated_component_packs = defer_generated_component_packs
126
131
  self.force_load = force_load
132
+ self.generated_component_packs_loading_strategy = generated_component_packs_loading_strategy
127
133
  end
128
134
  # rubocop:enable Metrics/AbcSize
129
135
 
@@ -139,6 +145,7 @@ module ReactOnRails
139
145
  # check_deprecated_settings
140
146
  adjust_precompile_task
141
147
  check_component_registry_timeout
148
+ validate_generated_component_packs_loading_strategy
142
149
  end
143
150
 
144
151
  private
@@ -151,6 +158,42 @@ module ReactOnRails
151
158
  raise ReactOnRails::Error, "component_registry_timeout must be a positive integer"
152
159
  end
153
160
 
161
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
162
+ def validate_generated_component_packs_loading_strategy
163
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
164
+
165
+ if defer_generated_component_packs
166
+ if %i[async sync].include?(generated_component_packs_loading_strategy)
167
+ Rails.logger.warn "**WARNING** ReactOnRails: config.defer_generated_component_packs is " \
168
+ "superseded by config.generated_component_packs_loading_strategy"
169
+ else
170
+ Rails.logger.warn "[DEPRECATION] ReactOnRails: Use config." \
171
+ "generated_component_packs_loading_strategy = :defer rather than " \
172
+ "defer_generated_component_packs"
173
+ self.generated_component_packs_loading_strategy ||= :defer
174
+ end
175
+ end
176
+
177
+ msg = <<~MSG
178
+ ReactOnRails: Your current version of #{ReactOnRails::PackerUtils.packer_type.upcase_first} \
179
+ does not support async script loading, which may cause performance issues. Please either:
180
+ 1. Use :sync or :defer loading strategy instead of :async
181
+ 2. Upgrade to Shakapacker v8.2.0 or above to enable async script loading
182
+ MSG
183
+ if PackerUtils.shakapacker_version_requirement_met?([8, 2, 0])
184
+ self.generated_component_packs_loading_strategy ||= :async
185
+ elsif generated_component_packs_loading_strategy.nil?
186
+ Rails.logger.warn("**WARNING** #{msg}")
187
+ self.generated_component_packs_loading_strategy = :sync
188
+ elsif generated_component_packs_loading_strategy == :async
189
+ raise ReactOnRails::Error, "**ERROR** #{msg}"
190
+ end
191
+
192
+ return if %i[async defer sync].include?(generated_component_packs_loading_strategy)
193
+
194
+ raise ReactOnRails::Error, "generated_component_packs_loading_strategy must be either :async, :defer, or :sync"
195
+ end
196
+
154
197
  def check_autobundling_requirements
155
198
  raise_missing_components_subdirectory if auto_load_bundle && !components_subdirectory.present?
156
199
  return unless components_subdirectory.present?
@@ -266,7 +309,8 @@ module ReactOnRails
266
309
  "manifest.json",
267
310
  server_bundle_js_file,
268
311
  rsc_bundle_js_file,
269
- react_client_manifest_file
312
+ react_client_manifest_file,
313
+ react_server_client_manifest_file
270
314
  ].compact_blank
271
315
  end
272
316
 
@@ -12,9 +12,11 @@ module ReactOnRails
12
12
  #
13
13
  # Be sure to include view helper `redux_store_hydration_data` at the end of your layout or view
14
14
  # or else there will be no client side hydration of your stores.
15
- def redux_store(store_name, props: {})
15
+ def redux_store(store_name, props: {}, force_load: nil)
16
+ force_load = ReactOnRails.configuration.force_load if force_load.nil?
16
17
  redux_store_data = { store_name: store_name,
17
- props: props }
18
+ props: props,
19
+ force_load: force_load }
18
20
  @registered_stores_defer_render ||= []
19
21
  @registered_stores_defer_render << redux_store_data
20
22
  end
@@ -32,7 +32,7 @@ module ReactOnRails
32
32
  #
33
33
  # Exposing the react_component_name is necessary to both a plain ReactComponent as well as
34
34
  # a generator:
35
- # See README.md for how to "register" your react components.
35
+ # See README.md for how to "register" your React components.
36
36
  # See spec/dummy/client/app/packs/server-bundle.js and
37
37
  # spec/dummy/client/app/packs/client-bundle.js for examples of this.
38
38
  #
@@ -373,8 +373,14 @@ module ReactOnRails
373
373
  # TODO: v13 just use the version if existing
374
374
  rorPro: ReactOnRails::Utils.react_on_rails_pro?
375
375
  }
376
+
376
377
  if ReactOnRails::Utils.react_on_rails_pro?
377
378
  result[:rorProVersion] = ReactOnRails::Utils.react_on_rails_pro_version
379
+
380
+ if ReactOnRails::Utils.rsc_support_enabled?
381
+ rsc_payload_url = ReactOnRailsPro.configuration.rsc_payload_generation_url_path
382
+ result[:rscPayloadGenerationUrlPath] = rsc_payload_url
383
+ end
378
384
  end
379
385
 
380
386
  if defined?(request) && request.present?
@@ -422,8 +428,13 @@ module ReactOnRails
422
428
  is_component_pack_present = File.exist?(generated_components_pack_path(react_component_name))
423
429
  raise_missing_autoloaded_bundle(react_component_name) unless is_component_pack_present
424
430
  end
425
- append_javascript_pack_tag("generated/#{react_component_name}",
426
- defer: ReactOnRails.configuration.defer_generated_component_packs)
431
+
432
+ options = { defer: ReactOnRails.configuration.generated_component_packs_loading_strategy == :defer }
433
+ # Old versions of Shakapacker don't support async script tags.
434
+ # ReactOnRails.configure already validates if async loading is supported by the installed Shakapacker version.
435
+ # Therefore, we only need to pass the async option if the loading strategy is explicitly set to :async
436
+ options[:async] = true if ReactOnRails.configuration.generated_component_packs_loading_strategy == :async
437
+ append_javascript_pack_tag("generated/#{react_component_name}", **options)
427
438
  append_stylesheet_pack_tag("generated/#{react_component_name}")
428
439
  end
429
440
 
@@ -639,7 +650,8 @@ module ReactOnRails
639
650
  "data-trace" => (render_options.trace ? true : nil),
640
651
  "data-dom-id" => render_options.dom_id,
641
652
  "data-store-dependencies" => render_options.store_dependencies&.to_json,
642
- "data-force-load" => (render_options.force_load ? true : nil))
653
+ "data-force-load" => (render_options.force_load ? true : nil),
654
+ "data-render-request-id" => render_options.render_request_id)
643
655
 
644
656
  if render_options.force_load
645
657
  component_specification_tag.concat(
@@ -89,25 +89,20 @@ module ReactOnRails
89
89
 
90
90
  def pack_file_contents(file_path)
91
91
  registered_component_name = component_name(file_path)
92
- load_server_components = ReactOnRails::Utils.react_on_rails_pro? &&
93
- ReactOnRailsPro.configuration.enable_rsc_support
92
+ load_server_components = ReactOnRails::Utils.rsc_support_enabled?
94
93
 
95
94
  if load_server_components && !client_entrypoint?(file_path)
96
- rsc_payload_generation_url_path = ReactOnRailsPro.configuration.rsc_payload_generation_url_path
97
-
98
95
  return <<~FILE_CONTENT.strip
99
96
  import registerServerComponent from 'react-on-rails/registerServerComponent/client';
100
97
 
101
- registerServerComponent({
102
- rscPayloadGenerationUrlPath: "#{rsc_payload_generation_url_path}",
103
- }, "#{registered_component_name}")
98
+ registerServerComponent("#{registered_component_name}");
104
99
  FILE_CONTENT
105
100
  end
106
101
 
107
102
  relative_component_path = relative_component_path_from_generated_pack(file_path)
108
103
 
109
104
  <<~FILE_CONTENT.strip
110
- import ReactOnRails from 'react-on-rails';
105
+ import ReactOnRails from 'react-on-rails/client';
111
106
  import #{registered_component_name} from '#{relative_component_path}';
112
107
 
113
108
  ReactOnRails.register({#{registered_component_name}});
@@ -146,8 +141,7 @@ module ReactOnRails
146
141
  "import #{name} from '#{relative_path(generated_server_bundle_file_path, component_path)}';"
147
142
  end
148
143
 
149
- load_server_components = ReactOnRails::Utils.react_on_rails_pro? &&
150
- ReactOnRailsPro.configuration.enable_rsc_support
144
+ load_server_components = ReactOnRails::Utils.rsc_support_enabled?
151
145
  server_components = component_for_server_registration_to_path.keys.delete_if do |name|
152
146
  next true unless load_server_components
153
147
 
@@ -15,9 +15,17 @@ module ReactOnRails
15
15
  def initialize(react_component_name: required("react_component_name"), options: required("options"))
16
16
  @react_component_name = react_component_name.camelize
17
17
  @options = options
18
+ # The render_request_id serves as a unique identifier for each render request.
19
+ # We cannot rely solely on dom_id, as it should be unique for each component on the page,
20
+ # but the server can render the same page multiple times concurrently for different users.
21
+ # Therefore, we need an additional unique identifier that can be used both on the client and server.
22
+ # This ID can also be used to associate specific data with a particular rendered component
23
+ # on either the server or client.
24
+ # This ID is only present if RSC support is enabled because it's only used in that case.
25
+ @render_request_id = self.class.generate_request_id if ReactOnRails::Utils.rsc_support_enabled?
18
26
  end
19
27
 
20
- attr_reader :react_component_name
28
+ attr_reader :react_component_name, :render_request_id
21
29
 
22
30
  def throw_js_errors
23
31
  options.fetch(:throw_js_errors, false)
@@ -139,6 +147,10 @@ module ReactOnRails
139
147
  options[:store_dependencies]
140
148
  end
141
149
 
150
+ def self.generate_request_id
151
+ SecureRandom.uuid
152
+ end
153
+
142
154
  private
143
155
 
144
156
  attr_reader :options
@@ -52,6 +52,8 @@ module ReactOnRails
52
52
  webpack_generated_files = @webpack_generated_files.map do |bundle_name|
53
53
  if bundle_name == ReactOnRails.configuration.react_client_manifest_file
54
54
  ReactOnRails::Utils.react_client_manifest_file_path
55
+ elsif bundle_name == ReactOnRails.configuration.react_server_client_manifest_file
56
+ ReactOnRails::Utils.react_server_client_manifest_file_path
55
57
  else
56
58
  ReactOnRails::Utils.bundle_js_file_path(bundle_name)
57
59
  end
@@ -122,6 +122,20 @@ module ReactOnRails
122
122
  end
123
123
  end
124
124
 
125
+ # React Server Manifest is generated by the server bundle.
126
+ # So, it will never be served from the dev server.
127
+ def self.react_server_client_manifest_file_path
128
+ return @react_server_manifest_path if @react_server_manifest_path && !Rails.env.development?
129
+
130
+ asset_name = ReactOnRails.configuration.react_server_client_manifest_file
131
+ if asset_name.nil?
132
+ raise ReactOnRails::Error,
133
+ "react_server_client_manifest_file is nil, ensure it is set in your configuration"
134
+ end
135
+
136
+ @react_server_manifest_path = File.join(generated_assets_full_path, asset_name)
137
+ end
138
+
125
139
  def self.running_on_windows?
126
140
  (/cygwin|mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM) != nil
127
141
  end
@@ -199,6 +213,15 @@ module ReactOnRails
199
213
  end
200
214
  end
201
215
 
216
+ def self.rsc_support_enabled?
217
+ return false unless react_on_rails_pro?
218
+
219
+ return @rsc_support_enabled if defined?(@rsc_support_enabled)
220
+
221
+ rorp_config = ReactOnRailsPro.configuration
222
+ @rsc_support_enabled = rorp_config.respond_to?(:enable_rsc_support) && rorp_config.enable_rsc_support
223
+ end
224
+
202
225
  def self.full_text_errors_enabled?
203
226
  ENV["FULL_TEXT_ERRORS"] == "true"
204
227
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ReactOnRails
4
- VERSION = "15.0.0.alpha.2"
4
+ VERSION = "15.0.0.rc.1"
5
5
  end
@@ -26,7 +26,7 @@ namespace :react_on_rails do
26
26
  else
27
27
  # Left in this warning message in case this rake task is run directly
28
28
  msg = <<~MSG
29
- React on Rails is aborting webpack compilation from task react_on_rails:assets:webpack
29
+ React on Rails is aborting Webpack compilation from task react_on_rails:assets:webpack
30
30
  because you do not have the `config.build_production_command` defined.
31
31
  MSG
32
32
  puts Rainbow(msg).red
@@ -0,0 +1,6 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "allowSyntheticDefaultImports": true
5
+ }
6
+ }
data/tsconfig.json CHANGED
@@ -6,12 +6,13 @@
6
6
  // needed for Jest tests even though we don't use .tsx
7
7
  "jsx": "react-jsx",
8
8
  "lib": ["dom", "es2020"],
9
- "module": "node16",
10
9
  "noImplicitAny": true,
11
10
  "outDir": "node_package/lib",
11
+ "allowImportingTsExtensions": true,
12
+ "rewriteRelativeImportExtensions": true,
12
13
  "strict": true,
13
14
  "incremental": true,
14
- "target": "es5",
15
+ "target": "es2020",
15
16
  "typeRoots": ["./node_modules/@types", "./node_package/types"]
16
17
  },
17
18
  "include": ["node_package/src/**/*", "node_package/types/**/*"]
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: react_on_rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 15.0.0.alpha.2
4
+ version: 15.0.0.rc.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Gordon
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2025-03-07 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: addressable
@@ -117,6 +116,7 @@ files:
117
116
  - SUMMARY.md
118
117
  - app/helpers/react_on_rails_helper.rb
119
118
  - docker-compose.yml
119
+ - eslint.config.ts
120
120
  - knip.ts
121
121
  - lib/generators/USAGE
122
122
  - lib/generators/react_on_rails/adapt_for_older_shakapacker_generator.rb
@@ -195,6 +195,7 @@ files:
195
195
  - lib/tasks/generate_packs.rake
196
196
  - lib/tasks/locale.rake
197
197
  - react_on_rails.gemspec
198
+ - tsconfig.eslint.json
198
199
  - tsconfig.json
199
200
  homepage: https://github.com/shakacode/react_on_rails
200
201
  licenses:
@@ -221,8 +222,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
221
222
  - !ruby/object:Gem::Version
222
223
  version: '0'
223
224
  requirements: []
224
- rubygems_version: 3.5.11
225
- signing_key:
225
+ rubygems_version: 3.6.9
226
226
  specification_version: 4
227
227
  summary: Rails with react server rendering with webpack.
228
228
  test_files: []