react_on_rails 16.2.0.beta.3 → 16.2.0.beta.8

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 (104) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +42 -5
  3. data/CLAUDE.md +59 -0
  4. data/CONTRIBUTING.md +49 -1
  5. data/Gemfile.development_dependencies +1 -1
  6. data/Gemfile.lock +25 -10
  7. data/SWITCHING_CI_CONFIGS.md +55 -6
  8. data/Steepfile +51 -0
  9. data/bin/ci-rerun-failures +68 -22
  10. data/bin/ci-run-failed-specs +26 -2
  11. data/bin/ci-switch-config +262 -34
  12. data/bin/lefthook/check-trailing-newlines +2 -12
  13. data/bin/lefthook/eslint-lint +0 -10
  14. data/bin/lefthook/prettier-format +0 -10
  15. data/bin/lefthook/ruby-autofix +3 -6
  16. data/knip.ts +35 -9
  17. data/lib/generators/react_on_rails/templates/base/base/config/initializers/react_on_rails.rb.tt +32 -52
  18. data/lib/generators/react_on_rails/templates/base/base/config/shakapacker.yml +5 -1
  19. data/lib/react_on_rails/configuration.rb +56 -12
  20. data/lib/react_on_rails/controller.rb +3 -3
  21. data/lib/react_on_rails/dev/server_manager.rb +11 -4
  22. data/lib/react_on_rails/doctor.rb +249 -2
  23. data/lib/react_on_rails/helper.rb +12 -3
  24. data/lib/react_on_rails/pro_helper.rb +2 -44
  25. data/lib/react_on_rails/react_component/render_options.rb +7 -7
  26. data/lib/react_on_rails/utils.rb +40 -0
  27. data/lib/react_on_rails/version.rb +1 -1
  28. data/react_on_rails_pro/CHANGELOG.md +142 -29
  29. data/react_on_rails_pro/CONTRIBUTING.md +2 -13
  30. data/react_on_rails_pro/Gemfile.development_dependencies +1 -0
  31. data/react_on_rails_pro/Gemfile.lock +24 -3
  32. data/react_on_rails_pro/README.md +559 -38
  33. data/react_on_rails_pro/docs/code-splitting-loadable-components.md +1 -1
  34. data/react_on_rails_pro/docs/contributors-info/releasing.md +2 -2
  35. data/react_on_rails_pro/docs/installation.md +129 -109
  36. data/react_on_rails_pro/docs/node-renderer/basics.md +29 -22
  37. data/react_on_rails_pro/docs/node-renderer/error-reporting-and-tracing.md +8 -8
  38. data/react_on_rails_pro/docs/node-renderer/js-configuration.md +25 -23
  39. data/react_on_rails_pro/docs/node-renderer/troubleshooting.md +2 -0
  40. data/react_on_rails_pro/docs/updating.md +209 -15
  41. data/react_on_rails_pro/lib/react_on_rails_pro/concerns/stream.rb +58 -4
  42. data/react_on_rails_pro/lib/react_on_rails_pro/configuration.rb +17 -3
  43. data/react_on_rails_pro/lib/react_on_rails_pro/license_public_key.rb +9 -9
  44. data/react_on_rails_pro/lib/react_on_rails_pro/request.rb +41 -25
  45. data/react_on_rails_pro/lib/react_on_rails_pro/stream_request.rb +27 -7
  46. data/react_on_rails_pro/lib/react_on_rails_pro/utils.rb +3 -3
  47. data/react_on_rails_pro/lib/react_on_rails_pro/version.rb +1 -1
  48. data/react_on_rails_pro/package-scripts.yml +1 -1
  49. data/react_on_rails_pro/package.json +5 -8
  50. data/react_on_rails_pro/packages/node-renderer/src/integrations/api.ts +1 -1
  51. data/react_on_rails_pro/packages/node-renderer/src/master/restartWorkers.ts +39 -17
  52. data/react_on_rails_pro/packages/node-renderer/src/master.ts +15 -4
  53. data/react_on_rails_pro/packages/node-renderer/src/shared/configBuilder.ts +44 -5
  54. data/react_on_rails_pro/packages/node-renderer/src/shared/utils.ts +4 -2
  55. data/react_on_rails_pro/packages/node-renderer/src/worker/handleGracefulShutdown.ts +49 -0
  56. data/react_on_rails_pro/packages/node-renderer/src/worker/vm.ts +3 -3
  57. data/react_on_rails_pro/packages/node-renderer/src/worker.ts +5 -2
  58. data/react_on_rails_pro/packages/node-renderer/tests/helper.ts +8 -8
  59. data/react_on_rails_pro/packages/node-renderer/tests/testingNodeRendererConfigs.js +1 -1
  60. data/react_on_rails_pro/packages/node-renderer/tests/worker.test.ts +19 -19
  61. data/react_on_rails_pro/rakelib/public_key_management.rake +6 -5
  62. data/react_on_rails_pro/rakelib/rbs.rake +47 -0
  63. data/react_on_rails_pro/react_on_rails_pro.gemspec +1 -0
  64. data/react_on_rails_pro/sig/react_on_rails_pro/cache.rbs +13 -0
  65. data/react_on_rails_pro/sig/react_on_rails_pro/configuration.rbs +100 -0
  66. data/react_on_rails_pro/sig/react_on_rails_pro/error.rbs +4 -0
  67. data/react_on_rails_pro/sig/react_on_rails_pro/utils.rbs +7 -0
  68. data/react_on_rails_pro/sig/react_on_rails_pro.rbs +5 -0
  69. data/react_on_rails_pro/spec/dummy/Gemfile.lock +23 -3
  70. data/react_on_rails_pro/spec/dummy/app/controllers/pages_controller.rb +3 -3
  71. data/react_on_rails_pro/spec/dummy/bin/dev +4 -8
  72. data/react_on_rails_pro/spec/dummy/client/node-renderer.js +4 -4
  73. data/react_on_rails_pro/spec/dummy/config/environments/production.rb +1 -1
  74. data/react_on_rails_pro/spec/dummy/config/initializers/react_on_rails.rb +28 -12
  75. data/react_on_rails_pro/spec/dummy/config.ru +1 -1
  76. data/react_on_rails_pro/spec/dummy/package.json +2 -2
  77. data/react_on_rails_pro/spec/dummy/spec/helpers/react_on_rails_pro_helper_spec.rb +40 -11
  78. data/react_on_rails_pro/spec/dummy/spec/rails_helper.rb +1 -1
  79. data/react_on_rails_pro/spec/dummy/spec/requests/renderer_console_logging_spec.rb +5 -5
  80. data/react_on_rails_pro/spec/dummy/spec/system/integration_spec.rb +15 -10
  81. data/react_on_rails_pro/spec/dummy/spec/system/renderer_integration_spec.rb +3 -3
  82. data/react_on_rails_pro/spec/dummy/yarn.lock +4 -4
  83. data/react_on_rails_pro/spec/execjs-compatible-dummy/config/environments/production.rb +1 -1
  84. data/react_on_rails_pro/spec/execjs-compatible-dummy/config/initializers/react_on_rails.rb +16 -43
  85. data/react_on_rails_pro/spec/react_on_rails_pro/assets_precompile_spec.rb +15 -18
  86. data/react_on_rails_pro/spec/react_on_rails_pro/cache_spec.rb +1 -1
  87. data/react_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rb +5 -3
  88. data/react_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rb +27 -12
  89. data/react_on_rails_pro/spec/react_on_rails_pro/request_spec.rb +0 -27
  90. data/react_on_rails_pro/spec/react_on_rails_pro/spec_helper.rb +1 -1
  91. data/react_on_rails_pro/spec/react_on_rails_pro/stream_decorator_spec.rb +89 -0
  92. data/react_on_rails_pro/spec/react_on_rails_pro/stream_spec.rb +144 -0
  93. data/react_on_rails_pro/spec/react_on_rails_pro/support/caching.rb +1 -1
  94. data/react_on_rails_pro/spec/react_on_rails_pro/support/mock_block_helper.rb +4 -2
  95. data/sig/react_on_rails/controller.rbs +1 -1
  96. data/sig/react_on_rails/error.rbs +4 -0
  97. data/sig/react_on_rails/helper.rbs +2 -2
  98. data/sig/react_on_rails/json_parse_error.rbs +10 -0
  99. data/sig/react_on_rails/prerender_error.rbs +21 -0
  100. data/sig/react_on_rails/smart_error.rbs +28 -0
  101. data/sig/react_on_rails.rbs +3 -24
  102. metadata +14 -4
  103. data/lib/react_on_rails/pro_utils.rb +0 -37
  104. data/react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/TestingStreamableComponent.jsx +0 -15
@@ -1,25 +1,219 @@
1
- # Using a Branch Rather Than a Published Package
2
- _And Old Installation Instructions_
1
+ # Upgrading React on Rails Pro
3
2
 
4
- ## Get the oauth token from justin@shakacode.com
3
+ ## Upgrading from GitHub Packages to Public Distribution
5
4
 
6
- * ShakaCode does:
7
- * Creates a github user, like customer-rorp with email customer-rorp@shakacode.com created via a Google apps group.
8
- * Confirm email for account
9
- * Add user to have read-only access for shakacode/react_on_rails_pro
10
- * Create an auth token for this user.
5
+ ### Who This Guide is For
11
6
 
7
+ This guide is for existing React on Rails Pro customers who are:
12
8
 
13
- ## Update the Gemfile
9
+ - Currently using GitHub Packages authentication (private distribution)
10
+ - On version 16.2.0-beta.x or earlier
11
+ - Upgrading to version 16.2.0 or higher
12
+
13
+ If you're a new customer, see [Installation](./installation.md) instead.
14
+
15
+ ### What's Changing
16
+
17
+ React on Rails Pro packages are now **publicly distributed** via npmjs.org and RubyGems.org:
18
+
19
+ - ✅ No more GitHub Personal Access Tokens (PATs)
20
+ - ✅ No more `.npmrc` configuration
21
+ - ✅ Simplified installation with standard `gem install` and `npm install`
22
+ - ✅ License validation now happens at **runtime** using JWT tokens
23
+
24
+ Package names have changed:
25
+
26
+ - **Scoped** (old): `@shakacode-tools/react-on-rails-pro-node-renderer`
27
+ - **Unscoped** (new): `react-on-rails-pro-node-renderer`
28
+
29
+ ### Your Current Setup (GitHub Packages)
30
+
31
+ If you're upgrading, you currently have:
32
+
33
+ **1. Gemfile with GitHub Packages source:**
34
+
35
+ ```ruby
36
+ source "https://rubygems.pkg.github.com/shakacode-tools" do
37
+ gem "react_on_rails_pro", "16.1.1"
38
+ end
39
+ ```
40
+
41
+ **2. `.npmrc` file with GitHub authentication:**
42
+
43
+ ```
44
+ always-auth=true
45
+ //npm.pkg.github.com/:_authToken=YOUR_TOKEN
46
+ @shakacode-tools:registry=https://npm.pkg.github.com
47
+ ```
48
+
49
+ **3. Scoped package name in package.json:**
50
+
51
+ ```json
52
+ {
53
+ "private": true,
54
+ "dependencies": {
55
+ "@shakacode-tools/react-on-rails-pro-node-renderer": "16.1.1"
56
+ }
57
+ }
58
+ ```
59
+
60
+ **4. Scoped require statements:**
61
+
62
+ ```javascript
63
+ const { reactOnRailsProNodeRenderer } = require('@shakacode-tools/react-on-rails-pro-node-renderer');
64
+ ```
65
+
66
+ ### Migration Steps
67
+
68
+ #### Step 1: Update Gemfile
69
+
70
+ **Remove** the GitHub Packages source and use standard gem installation:
71
+
72
+ ```diff
73
+ - source "https://rubygems.pkg.github.com/shakacode-tools" do
74
+ - gem "react_on_rails_pro", "16.1.1"
75
+ - end
76
+ + gem "react_on_rails_pro", "~> 16.2"
77
+ ```
78
+
79
+ Then run:
80
+
81
+ ```bash
82
+ bundle install
83
+ ```
84
+
85
+ #### Step 2: Remove .npmrc Configuration
86
+
87
+ If you have a `.npmrc` file with GitHub Packages authentication, **delete it** or remove the GitHub-specific lines:
88
+
89
+ ```bash
90
+ # Remove the entire file if it only contained GitHub Packages config
91
+ rm .npmrc
92
+
93
+ # Or edit it to remove these lines:
94
+ # always-auth=true
95
+ # //npm.pkg.github.com/:_authToken=YOUR_TOKEN
96
+ # @shakacode-tools:registry=https://npm.pkg.github.com
97
+ ```
98
+
99
+ #### Step 3: Update package.json
100
+
101
+ Change the package name from **scoped** to **unscoped**:
102
+
103
+ ```diff
104
+ {
105
+ "dependencies": {
106
+ - "@shakacode-tools/react-on-rails-pro-node-renderer": "16.1.1"
107
+ + "react-on-rails-pro-node-renderer": "^16.2.0"
108
+ }
109
+ }
110
+ ```
111
+
112
+ Then reinstall:
113
+
114
+ ```bash
115
+ npm install
116
+ # or
117
+ yarn install
118
+ ```
119
+
120
+ #### Step 4: Update Require Statements
121
+
122
+ Update all require/import statements to use the **unscoped** package name:
123
+
124
+ **In your node renderer configuration file:**
125
+
126
+ ```diff
127
+ - const { reactOnRailsProNodeRenderer } = require('@shakacode-tools/react-on-rails-pro-node-renderer');
128
+ + const { reactOnRailsProNodeRenderer } = require('react-on-rails-pro-node-renderer');
129
+ ```
130
+
131
+ **If using integrations (Sentry, Honeybadger):**
132
+
133
+ ```diff
134
+ - require('@shakacode-tools/react-on-rails-pro-node-renderer/integrations/sentry').init();
135
+ + require('react-on-rails-pro-node-renderer/integrations/sentry').init();
136
+
137
+ - require('@shakacode-tools/react-on-rails-pro-node-renderer/integrations/honeybadger').init();
138
+ + require('react-on-rails-pro-node-renderer/integrations/honeybadger').init();
139
+ ```
140
+
141
+ #### Step 5: Configure License Token
142
+
143
+ Add your React on Rails Pro license token as an environment variable:
144
+
145
+ ```bash
146
+ export REACT_ON_RAILS_PRO_LICENSE="your-license-token-here"
147
+ ```
148
+
149
+ **Or** configure it in your Rails initializer:
14
150
 
15
151
  ```ruby
16
- CUSTOMER_GITHUB_AUTH = '3f5fblahblahblah:x-oauth-basic'
17
- gem "react_on_rails_pro", git: "https://#{CUSTOMER_GITHUB_AUTH}@github.com/shakacode/react_on_rails_pro.git", tag: '1.0.0'
152
+ # config/initializers/react_on_rails_pro.rb
153
+ ReactOnRailsPro.configure do |config|
154
+ config.license_token = ENV["REACT_ON_RAILS_PRO_LICENSE"]
155
+ end
156
+ ```
157
+
158
+ ⚠️ **Security Warning**: Never commit your license token to version control. Always use environment variables or secure secret management systems (Rails credentials, Heroku config vars, AWS Secrets Manager, etc.).
159
+
160
+ **Where to get your license token:** Contact [justin@shakacode.com](mailto:justin@shakacode.com) if you don't have your license token.
161
+
162
+ ### Verify Migration
163
+
164
+ #### 1. Verify Gem Installation
165
+
166
+ ```bash
167
+ bundle list | grep react_on_rails_pro
168
+ # Should show: react_on_rails_pro (16.2.0) or higher
169
+ ```
170
+
171
+ #### 2. Verify NPM Package Installation
172
+
173
+ ```bash
174
+ npm list react-on-rails-pro-node-renderer
175
+ # or
176
+ yarn list --pattern react-on-rails-pro-node-renderer
177
+
178
+ # Should show: react-on-rails-pro-node-renderer@16.2.0 or higher
18
179
  ```
19
180
 
20
- ## Update the client/package.json
181
+ #### 3. Verify License Token
182
+
183
+ Start your Rails server. You should see a success message in the logs:
21
184
 
22
- ```sh
23
- CUSTOMER_GITHUB_AUTH=3f5fblahblahblah
24
- yarn add https://${CUSTOMER_GITHUB_AUTH}:x-oauth-basic@github.com/shakacode/react_on_rails_pro.git\#1.0.0
25
185
  ```
186
+ React on Rails Pro license validated successfully
187
+ ```
188
+
189
+ If the license is invalid or missing, you'll see an error with instructions.
190
+
191
+ #### 4. Test Your Application
192
+
193
+ - Start your Rails server
194
+ - Start the node renderer (if using): `npm run node-renderer`
195
+ - Verify that server-side rendering works correctly
196
+
197
+ ### Troubleshooting
198
+
199
+ #### "Could not find gem 'react_on_rails_pro'"
200
+
201
+ - Ensure you removed the GitHub Packages source from your Gemfile
202
+ - Run `bundle install` again
203
+ - Check that you have the correct version specified
204
+
205
+ #### "Cannot find module 'react-on-rails-pro-node-renderer'"
206
+
207
+ - Verify you updated all require statements to the unscoped name
208
+ - Delete `node_modules` and reinstall: `rm -rf node_modules && npm install`
209
+ - Check that package.json has the correct unscoped package name
210
+
211
+ #### "License validation failed"
212
+
213
+ - Ensure `REACT_ON_RAILS_PRO_LICENSE` environment variable is set
214
+ - Verify the token string is correct (no extra spaces or quotes)
215
+ - Contact [justin@shakacode.com](mailto:justin@shakacode.com) if you need a new token
216
+
217
+ ### Need Help?
218
+
219
+ If you encounter issues during migration, contact [justin@shakacode.com](mailto:justin@shakacode.com) for support.
@@ -38,12 +38,66 @@ module ReactOnRailsPro
38
38
  # So we strip extra newlines from the template string and add a single newline
39
39
  response.stream.write(template_string)
40
40
 
41
- @rorp_rendering_fibers.each do |fiber|
42
- while (chunk = fiber.resume)
43
- response.stream.write(chunk)
41
+ begin
42
+ drain_streams_concurrently
43
+ ensure
44
+ response.stream.close if close_stream_at_end
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ def drain_streams_concurrently
51
+ require "async"
52
+ require "async/limited_queue"
53
+
54
+ return if @rorp_rendering_fibers.empty?
55
+
56
+ Sync do |parent|
57
+ # To avoid memory bloat, we use a limited queue to buffer chunks in memory.
58
+ buffer_size = ReactOnRailsPro.configuration.concurrent_component_streaming_buffer_size
59
+ queue = Async::LimitedQueue.new(buffer_size)
60
+
61
+ writer = build_writer_task(parent: parent, queue: queue)
62
+ tasks = build_producer_tasks(parent: parent, queue: queue)
63
+
64
+ # This structure ensures that even if a producer task fails, we always
65
+ # signal the writer to stop and then wait for it to finish draining
66
+ # any remaining items from the queue before propagating the error.
67
+ begin
68
+ tasks.each(&:wait)
69
+ ensure
70
+ # `close` signals end-of-stream; when writer tries to dequeue, it will get nil, so it will exit.
71
+ queue.close
72
+ writer.wait
73
+ end
74
+ end
75
+ end
76
+
77
+ def build_producer_tasks(parent:, queue:)
78
+ @rorp_rendering_fibers.each_with_index.map do |fiber, idx|
79
+ parent.async do
80
+ loop do
81
+ chunk = fiber.resume
82
+ break unless chunk
83
+
84
+ # Will be blocked if the queue is full until a chunk is dequeued
85
+ queue.enqueue([idx, chunk])
86
+ end
87
+ end
88
+ end
89
+ end
90
+
91
+ def build_writer_task(parent:, queue:)
92
+ parent.async do
93
+ loop do
94
+ pair = queue.dequeue
95
+ break if pair.nil?
96
+
97
+ _idx_from_queue, item = pair
98
+ response.stream.write(item)
44
99
  end
45
100
  end
46
- response.stream.close if close_stream_at_end
47
101
  end
48
102
  end
49
103
  end
@@ -32,7 +32,8 @@ module ReactOnRailsPro
32
32
  rsc_payload_generation_url_path: Configuration::DEFAULT_RSC_PAYLOAD_GENERATION_URL_PATH,
33
33
  rsc_bundle_js_file: Configuration::DEFAULT_RSC_BUNDLE_JS_FILE,
34
34
  react_client_manifest_file: Configuration::DEFAULT_REACT_CLIENT_MANIFEST_FILE,
35
- react_server_client_manifest_file: Configuration::DEFAULT_REACT_SERVER_CLIENT_MANIFEST_FILE
35
+ react_server_client_manifest_file: Configuration::DEFAULT_REACT_SERVER_CLIENT_MANIFEST_FILE,
36
+ concurrent_component_streaming_buffer_size: Configuration::DEFAULT_CONCURRENT_COMPONENT_STREAMING_BUFFER_SIZE
36
37
  )
37
38
  end
38
39
 
@@ -59,6 +60,7 @@ module ReactOnRailsPro
59
60
  DEFAULT_RSC_BUNDLE_JS_FILE = "rsc-bundle.js"
60
61
  DEFAULT_REACT_CLIENT_MANIFEST_FILE = "react-client-manifest.json"
61
62
  DEFAULT_REACT_SERVER_CLIENT_MANIFEST_FILE = "react-server-client-manifest.json"
63
+ DEFAULT_CONCURRENT_COMPONENT_STREAMING_BUFFER_SIZE = 64
62
64
 
63
65
  attr_accessor :renderer_url, :renderer_password, :tracing,
64
66
  :server_renderer, :renderer_use_fallback_exec_js, :prerender_caching,
@@ -68,7 +70,7 @@ module ReactOnRailsPro
68
70
  :renderer_request_retry_limit, :throw_js_errors, :ssr_timeout,
69
71
  :profile_server_rendering_js_code, :raise_non_shell_server_rendering_errors, :enable_rsc_support,
70
72
  :rsc_payload_generation_url_path, :rsc_bundle_js_file, :react_client_manifest_file,
71
- :react_server_client_manifest_file
73
+ :react_server_client_manifest_file, :concurrent_component_streaming_buffer_size
72
74
 
73
75
  def initialize(renderer_url: nil, renderer_password: nil, server_renderer: nil, # rubocop:disable Metrics/AbcSize
74
76
  renderer_use_fallback_exec_js: nil, prerender_caching: nil,
@@ -79,7 +81,9 @@ module ReactOnRailsPro
79
81
  renderer_request_retry_limit: nil, throw_js_errors: nil, ssr_timeout: nil,
80
82
  profile_server_rendering_js_code: nil, raise_non_shell_server_rendering_errors: nil,
81
83
  enable_rsc_support: nil, rsc_payload_generation_url_path: nil,
82
- rsc_bundle_js_file: nil, react_client_manifest_file: nil, react_server_client_manifest_file: nil)
84
+ rsc_bundle_js_file: nil, react_client_manifest_file: nil,
85
+ react_server_client_manifest_file: nil,
86
+ concurrent_component_streaming_buffer_size: DEFAULT_CONCURRENT_COMPONENT_STREAMING_BUFFER_SIZE)
83
87
  self.renderer_url = renderer_url
84
88
  self.renderer_password = renderer_password
85
89
  self.server_renderer = server_renderer
@@ -105,6 +109,7 @@ module ReactOnRailsPro
105
109
  self.rsc_bundle_js_file = rsc_bundle_js_file
106
110
  self.react_client_manifest_file = react_client_manifest_file
107
111
  self.react_server_client_manifest_file = react_server_client_manifest_file
112
+ self.concurrent_component_streaming_buffer_size = concurrent_component_streaming_buffer_size
108
113
  end
109
114
 
110
115
  def setup_config_values
@@ -113,6 +118,7 @@ module ReactOnRailsPro
113
118
  validate_remote_bundle_cache_adapter
114
119
  setup_renderer_password
115
120
  setup_assets_to_copy
121
+ validate_concurrent_component_streaming_buffer_size
116
122
  setup_execjs_profiler_if_needed
117
123
  check_react_on_rails_support_for_rsc
118
124
  end
@@ -204,6 +210,14 @@ module ReactOnRailsPro
204
210
  end
205
211
  end
206
212
 
213
+ def validate_concurrent_component_streaming_buffer_size
214
+ return if concurrent_component_streaming_buffer_size.is_a?(Integer) &&
215
+ concurrent_component_streaming_buffer_size.positive?
216
+
217
+ raise ReactOnRailsPro::Error,
218
+ "config.concurrent_component_streaming_buffer_size must be a positive integer"
219
+ end
220
+
207
221
  def setup_renderer_password
208
222
  return if renderer_password.present?
209
223
 
@@ -16,15 +16,15 @@ module ReactOnRailsPro
16
16
  # TODO: Add a prepublish check to ensure this key matches the latest public key from the API.
17
17
  # This should be implemented after publishing the API endpoint on the ShakaCode website.
18
18
  KEY = OpenSSL::PKey::RSA.new(<<~PEM.strip.strip_heredoc)
19
- -----BEGIN PUBLIC KEY-----
20
- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzcS/fpHz5CbnTQxb4Zot
21
- khjzXu7xNS+Y9VKfapMaHOMzNoCMfy1++hxHJatRedr+YQfZRCjfiN168Cpe+dhe
22
- yfNtOoLU9/+/5jTsxH+WQJWNRswyKms5HNajlIMN1GEYdZmZbvOPaZvh6ENsT+EV
23
- HnhjJtsHl7qltBoL0ul7rONxaNHCzJcKk4lf3B2/1j1wpA91MKz4bbQVh4/6Th0E
24
- /39f0PWvvBXzQS+yt1qaa1DIX5YL6Aug5uEpb1+6QWcN3hCzqSPBv1HahrG50rsD
25
- gf8KORV3X2N9t6j6iqPmRqfRcTBKtmPhM9bORtKiSwBK8LsIUzp2/UUmkdHnkyzu
26
- NQIDAQAB
27
- -----END PUBLIC KEY-----
19
+ -----BEGIN PUBLIC KEY-----
20
+ MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzcS/fpHz5CbnTQxb4Zot
21
+ khjzXu7xNS+Y9VKfapMaHOMzNoCMfy1++hxHJatRedr+YQfZRCjfiN168Cpe+dhe
22
+ yfNtOoLU9/+/5jTsxH+WQJWNRswyKms5HNajlIMN1GEYdZmZbvOPaZvh6ENsT+EV
23
+ HnhjJtsHl7qltBoL0ul7rONxaNHCzJcKk4lf3B2/1j1wpA91MKz4bbQVh4/6Th0E
24
+ /39f0PWvvBXzQS+yt1qaa1DIX5YL6Aug5uEpb1+6QWcN3hCzqSPBv1HahrG50rsD
25
+ gf8KORV3X2N9t6j6iqPmRqfRcTBKtmPhM9bORtKiSwBK8LsIUzp2/UUmkdHnkyzu
26
+ NQIDAQAB
27
+ -----END PUBLIC KEY-----
28
28
  PEM
29
29
  end
30
30
  end
@@ -9,9 +9,7 @@ module ReactOnRailsPro
9
9
  class << self
10
10
  def reset_connection
11
11
  @connection&.close
12
- @connection_without_retries&.close
13
12
  @connection = create_connection
14
- @connection_without_retries = create_connection(enable_retries: false)
15
13
  end
16
14
 
17
15
  def render_code(path, js_code, send_bundle)
@@ -84,29 +82,17 @@ module ReactOnRailsPro
84
82
 
85
83
  private
86
84
 
87
- # NOTE: We maintain two separate HTTP connection pools to handle streaming vs non-streaming requests.
88
- # This doubles the memory footprint (e.g., if renderer_http_pool_size is 10, we use 20 total connections).
89
- # This tradeoff is acceptable to prevent body duplication in streaming responses.
90
-
91
85
  def connection
92
86
  @connection ||= create_connection
93
87
  end
94
88
 
95
- def connection_without_retries
96
- @connection_without_retries ||= create_connection(enable_retries: false)
97
- end
98
-
99
- def perform_request(path, **post_options) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
100
- # For streaming requests, use connection without retries to prevent body duplication
101
- # The StreamRequest class handles retries properly by starting fresh requests
102
- conn = post_options[:stream] ? connection_without_retries : connection
103
-
89
+ def perform_request(path, **post_options) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity
104
90
  available_retries = ReactOnRailsPro.configuration.renderer_request_retry_limit
105
91
  retry_request = true
106
92
  while retry_request
107
93
  begin
108
94
  start_time = Time.now
109
- response = conn.post(path, **post_options)
95
+ response = connection.post(path, **post_options)
110
96
  raise response.error if response.is_a?(HTTPX::ErrorResponse)
111
97
 
112
98
  request_time = Time.now - start_time
@@ -231,20 +217,50 @@ module ReactOnRailsPro
231
217
  ReactOnRailsPro::Utils.common_form_data
232
218
  end
233
219
 
234
- def create_connection(enable_retries: true)
220
+ def create_connection # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
235
221
  url = ReactOnRailsPro.configuration.renderer_url
236
222
  Rails.logger.info do
237
223
  "[ReactOnRailsPro] Setting up Node Renderer connection to #{url}"
238
224
  end
239
225
 
240
- http_client = HTTPX
241
- # For persistent connections we want retries,
242
- # so the requests don't just fail if the other side closes the connection
243
- # https://honeyryderchuck.gitlab.io/httpx/wiki/Persistent
244
- # However, for streaming requests, retries cause body duplication
245
- # See https://github.com/shakacode/react_on_rails/issues/1895
246
- http_client = http_client.plugin(:retries, max_retries: 1, retry_change_requests: true) if enable_retries
247
- http_client
226
+ HTTPX
227
+ # For persistent connections we want retries,
228
+ # so the requests don't just fail if the other side closes the connection
229
+ # https://honeyryderchuck.gitlab.io/httpx/wiki/Persistent
230
+ .plugin(
231
+ :retries, max_retries: 1,
232
+ retry_change_requests: true,
233
+ # Official HTTPx docs says that we should use the retry_on option to decide if the
234
+ # request should be retried or not
235
+ # However, HTTPx assumes that connection errors such as timeout error should be retried
236
+ # by default and it doesn't consider retry_on block at all at that case
237
+ # So, we have to do the following trick to avoid retries when a Timeout error happens
238
+ # while streaming a component
239
+ # If the streamed component returned any chunks, it shouldn't retry on errors, as it
240
+ # would cause page duplication
241
+ # The SSR-generated html will be written to the page two times in this case
242
+ retry_after: lambda do |request, response|
243
+ if request.stream.instance_variable_get(:@react_on_rails_received_first_chunk)
244
+ e = response.error
245
+ raise(
246
+ ReactOnRailsPro::Error,
247
+ "An error happened during server side render streaming " \
248
+ "of a component.\nOriginal error:\n#{e}\n#{e.backtrace}"
249
+ )
250
+ end
251
+
252
+ Rails.logger.info do
253
+ "[ReactOnRailsPro] An error occurred while making " \
254
+ "a request to the Node Renderer.\n" \
255
+ "Error: #{response.error}.\n" \
256
+ "Retrying by HTTPX \"retries\" plugin..."
257
+ end
258
+ # The retry_after block expects to return a delay to wait before
259
+ # retrying the request
260
+ # nil means no waiting delay
261
+ nil
262
+ end
263
+ )
248
264
  .plugin(:stream)
249
265
  # See https://www.rubydoc.info/gems/httpx/1.3.3/HTTPX%2FOptions:initialize for the available options
250
266
  .with(
@@ -10,6 +10,7 @@ module ReactOnRailsPro
10
10
  # @param position [Symbol] The position of the chunk in the stream (:first, :middle, or :last)
11
11
  # The position parameter is used by actions that add content to the beginning or end of the stream
12
12
  @actions = [] # List to store all actions
13
+ @rescue_blocks = []
13
14
  end
14
15
 
15
16
  # Add a prepend action
@@ -39,27 +40,45 @@ module ReactOnRailsPro
39
40
  self # Return self to allow chaining
40
41
  end
41
42
 
43
+ def rescue(&block)
44
+ @rescue_blocks << block
45
+ self # Return self to allow chaining
46
+ end
47
+
42
48
  def handle_chunk(chunk, position)
43
49
  @actions.reduce(chunk) do |acc, action|
44
50
  action.call(acc, position)
45
51
  end
46
52
  end
47
53
 
48
- def each_chunk
49
- return enum_for(:each_chunk) unless block_given?
54
+ def each_chunk(&block) # rubocop:disable Metrics/CyclomaticComplexity
55
+ return enum_for(:each_chunk) unless block
50
56
 
51
57
  first_chunk = true
52
58
  @component.each_chunk do |chunk|
53
59
  position = first_chunk ? :first : :middle
54
60
  modified_chunk = handle_chunk(chunk, position)
55
- yield modified_chunk
61
+ yield(modified_chunk)
56
62
  first_chunk = false
57
63
  end
58
64
 
59
65
  # The last chunk contains the append content after the transformation
60
66
  # All transformations are applied to the append content
61
67
  last_chunk = handle_chunk("", :last)
62
- yield last_chunk unless last_chunk.empty?
68
+ yield(last_chunk) unless last_chunk.empty?
69
+ rescue StandardError => e
70
+ current_error = e
71
+ rescue_block_index = 0
72
+ while current_error.present? && (rescue_block_index < @rescue_blocks.size)
73
+ begin
74
+ @rescue_blocks[rescue_block_index].call(current_error, &block)
75
+ current_error = nil
76
+ rescue StandardError => inner_error
77
+ current_error = inner_error
78
+ end
79
+ rescue_block_index += 1
80
+ end
81
+ raise current_error if current_error.present?
63
82
  end
64
83
  end
65
84
 
@@ -75,9 +94,6 @@ module ReactOnRailsPro
75
94
 
76
95
  send_bundle = false
77
96
  error_body = +""
78
- # Retry logic for streaming requests is handled here by starting fresh requests.
79
- # The HTTPx connection used for streaming has retries disabled (see Request#connection_without_retries)
80
- # to prevent body duplication when partial chunks are already sent to the client.
81
97
  loop do
82
98
  stream_response = @request_executor.call(send_bundle)
83
99
 
@@ -89,6 +105,9 @@ module ReactOnRailsPro
89
105
  break
90
106
  rescue HTTPX::HTTPError => e
91
107
  send_bundle = handle_http_error(e, error_body, send_bundle)
108
+ rescue HTTPX::ReadTimeoutError => e
109
+ raise ReactOnRailsPro::Error, "Time out error while server side render streaming a component.\n" \
110
+ "Original error:\n#{e}\n#{e.backtrace}"
92
111
  end
93
112
  end
94
113
 
@@ -135,6 +154,7 @@ module ReactOnRailsPro
135
154
  line = "".b
136
155
 
137
156
  response.each do |chunk|
157
+ response.instance_variable_set(:@react_on_rails_received_first_chunk, true)
138
158
  line << chunk
139
159
 
140
160
  while (idx = line.index("\n"))
@@ -108,7 +108,7 @@ module ReactOnRailsPro
108
108
  @rsc_bundle_hash = calc_bundle_hash(server_rsc_bundle_js_file_path)
109
109
  end
110
110
 
111
- # Returns the hashed file name when using webpacker. Useful for creating cache keys.
111
+ # Returns the hashed file name when using Shakapacker. Useful for creating cache keys.
112
112
  def self.bundle_file_name(bundle_name)
113
113
  # bundle_js_uri_from_packer can return a file path or a HTTP URL (for files served from the dev server)
114
114
  # Pathname can handle both cases
@@ -117,8 +117,8 @@ module ReactOnRailsPro
117
117
  pathname.basename.to_s
118
118
  end
119
119
 
120
- # Returns the hashed file name of the server bundle when using webpacker.
121
- # Necessary fragment-caching keys.
120
+ # Returns the hashed file name of the server bundle when using Shakapacker.
121
+ # Necessary for fragment-caching keys.
122
122
  def self.server_bundle_file_name
123
123
  return @server_bundle_hash if @server_bundle_hash && !Rails.env.development?
124
124
 
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ReactOnRailsPro
4
- VERSION = "16.2.0.beta.3"
4
+ VERSION = "16.2.0.beta.8"
5
5
  PROTOCOL_VERSION = "2.0.0"
6
6
  end
@@ -47,7 +47,7 @@ scripts:
47
47
  [ -f packages/node-renderer/dist/ReactOnRailsProNodeRenderer.js ] ||
48
48
  (nps build >/dev/null 2>&1 || true) &&
49
49
  [ -f packages/node-renderer/dist/ReactOnRailsProNodeRenderer.js ] ||
50
- { echo 'Building @shakacode-tools/react-on-rails-pro-node-renderer seems to have failed!'; }
50
+ { echo 'Building react-on-rails-pro-node-renderer seems to have failed!'; }
51
51
 
52
52
  clean:
53
53
  description: Clean the project
@@ -1,6 +1,6 @@
1
1
  {
2
- "name": "@shakacode-tools/react-on-rails-pro-node-renderer",
3
- "version": "16.2.0-beta.3",
2
+ "name": "react-on-rails-pro-node-renderer",
3
+ "version": "16.2.0-beta.8",
4
4
  "protocolVersion": "2.0.0",
5
5
  "description": "react-on-rails-pro JavaScript for react_on_rails_pro Ruby gem",
6
6
  "exports": {
@@ -17,9 +17,6 @@
17
17
  "bin": {
18
18
  "react-on-rails-pro-node-renderer": "packages/node-renderer/dist/default-node-renderer.js"
19
19
  },
20
- "publishConfig": {
21
- "registry": "https://npm.pkg.github.com"
22
- },
23
20
  "directories": {
24
21
  "doc": "docs"
25
22
  },
@@ -119,7 +116,7 @@
119
116
  },
120
117
  "repository": {
121
118
  "type": "git",
122
- "url": "git+https://github.com/shakacode-tools/react_on_rails_pro.git"
119
+ "url": "git+https://github.com/shakacode/react_on_rails.git"
123
120
  },
124
121
  "keywords": [
125
122
  "react",
@@ -132,9 +129,9 @@
132
129
  "author": "justin@shakacode.com",
133
130
  "license": "UNLICENSED",
134
131
  "bugs": {
135
- "url": "https://github.com/shakacode/react_on_rails_pro/issues"
132
+ "url": "https://github.com/shakacode/react_on_rails/issues"
136
133
  },
137
- "homepage": "https://github.com/shakacode/react_on_rails_pro#readme",
134
+ "homepage": "https://github.com/shakacode/react_on_rails/tree/master/react_on_rails_pro#readme",
138
135
  "jest": {
139
136
  "clearMocks": true,
140
137
  "collectCoverageFrom": [
@@ -4,7 +4,7 @@
4
4
  * @example
5
5
  * ```ts
6
6
  * import Bugsnag from '@bugsnag/js';
7
- * import { addNotifier, setupTracing } from '@shakacode-tools/react-on-rails-pro-node-renderer/integrations/api';
7
+ * import { addNotifier, setupTracing } from 'react-on-rails-pro-node-renderer/integrations/api';
8
8
  * Bugsnag.start({ ... });
9
9
  *
10
10
  * addNotifier((msg) => { Bugsnag.notify(msg); });