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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +42 -5
- data/CLAUDE.md +59 -0
- data/CONTRIBUTING.md +49 -1
- data/Gemfile.development_dependencies +1 -1
- data/Gemfile.lock +25 -10
- data/SWITCHING_CI_CONFIGS.md +55 -6
- data/Steepfile +51 -0
- data/bin/ci-rerun-failures +68 -22
- data/bin/ci-run-failed-specs +26 -2
- data/bin/ci-switch-config +262 -34
- data/bin/lefthook/check-trailing-newlines +2 -12
- data/bin/lefthook/eslint-lint +0 -10
- data/bin/lefthook/prettier-format +0 -10
- data/bin/lefthook/ruby-autofix +3 -6
- data/knip.ts +35 -9
- data/lib/generators/react_on_rails/templates/base/base/config/initializers/react_on_rails.rb.tt +32 -52
- data/lib/generators/react_on_rails/templates/base/base/config/shakapacker.yml +5 -1
- data/lib/react_on_rails/configuration.rb +56 -12
- data/lib/react_on_rails/controller.rb +3 -3
- data/lib/react_on_rails/dev/server_manager.rb +11 -4
- data/lib/react_on_rails/doctor.rb +249 -2
- data/lib/react_on_rails/helper.rb +12 -3
- data/lib/react_on_rails/pro_helper.rb +2 -44
- data/lib/react_on_rails/react_component/render_options.rb +7 -7
- data/lib/react_on_rails/utils.rb +40 -0
- data/lib/react_on_rails/version.rb +1 -1
- data/react_on_rails_pro/CHANGELOG.md +142 -29
- data/react_on_rails_pro/CONTRIBUTING.md +2 -13
- data/react_on_rails_pro/Gemfile.development_dependencies +1 -0
- data/react_on_rails_pro/Gemfile.lock +24 -3
- data/react_on_rails_pro/README.md +559 -38
- data/react_on_rails_pro/docs/code-splitting-loadable-components.md +1 -1
- data/react_on_rails_pro/docs/contributors-info/releasing.md +2 -2
- data/react_on_rails_pro/docs/installation.md +129 -109
- data/react_on_rails_pro/docs/node-renderer/basics.md +29 -22
- data/react_on_rails_pro/docs/node-renderer/error-reporting-and-tracing.md +8 -8
- data/react_on_rails_pro/docs/node-renderer/js-configuration.md +25 -23
- data/react_on_rails_pro/docs/node-renderer/troubleshooting.md +2 -0
- data/react_on_rails_pro/docs/updating.md +209 -15
- data/react_on_rails_pro/lib/react_on_rails_pro/concerns/stream.rb +58 -4
- data/react_on_rails_pro/lib/react_on_rails_pro/configuration.rb +17 -3
- data/react_on_rails_pro/lib/react_on_rails_pro/license_public_key.rb +9 -9
- data/react_on_rails_pro/lib/react_on_rails_pro/request.rb +41 -25
- data/react_on_rails_pro/lib/react_on_rails_pro/stream_request.rb +27 -7
- data/react_on_rails_pro/lib/react_on_rails_pro/utils.rb +3 -3
- data/react_on_rails_pro/lib/react_on_rails_pro/version.rb +1 -1
- data/react_on_rails_pro/package-scripts.yml +1 -1
- data/react_on_rails_pro/package.json +5 -8
- data/react_on_rails_pro/packages/node-renderer/src/integrations/api.ts +1 -1
- data/react_on_rails_pro/packages/node-renderer/src/master/restartWorkers.ts +39 -17
- data/react_on_rails_pro/packages/node-renderer/src/master.ts +15 -4
- data/react_on_rails_pro/packages/node-renderer/src/shared/configBuilder.ts +44 -5
- data/react_on_rails_pro/packages/node-renderer/src/shared/utils.ts +4 -2
- data/react_on_rails_pro/packages/node-renderer/src/worker/handleGracefulShutdown.ts +49 -0
- data/react_on_rails_pro/packages/node-renderer/src/worker/vm.ts +3 -3
- data/react_on_rails_pro/packages/node-renderer/src/worker.ts +5 -2
- data/react_on_rails_pro/packages/node-renderer/tests/helper.ts +8 -8
- data/react_on_rails_pro/packages/node-renderer/tests/testingNodeRendererConfigs.js +1 -1
- data/react_on_rails_pro/packages/node-renderer/tests/worker.test.ts +19 -19
- data/react_on_rails_pro/rakelib/public_key_management.rake +6 -5
- data/react_on_rails_pro/rakelib/rbs.rake +47 -0
- data/react_on_rails_pro/react_on_rails_pro.gemspec +1 -0
- data/react_on_rails_pro/sig/react_on_rails_pro/cache.rbs +13 -0
- data/react_on_rails_pro/sig/react_on_rails_pro/configuration.rbs +100 -0
- data/react_on_rails_pro/sig/react_on_rails_pro/error.rbs +4 -0
- data/react_on_rails_pro/sig/react_on_rails_pro/utils.rbs +7 -0
- data/react_on_rails_pro/sig/react_on_rails_pro.rbs +5 -0
- data/react_on_rails_pro/spec/dummy/Gemfile.lock +23 -3
- data/react_on_rails_pro/spec/dummy/app/controllers/pages_controller.rb +3 -3
- data/react_on_rails_pro/spec/dummy/bin/dev +4 -8
- data/react_on_rails_pro/spec/dummy/client/node-renderer.js +4 -4
- data/react_on_rails_pro/spec/dummy/config/environments/production.rb +1 -1
- data/react_on_rails_pro/spec/dummy/config/initializers/react_on_rails.rb +28 -12
- data/react_on_rails_pro/spec/dummy/config.ru +1 -1
- data/react_on_rails_pro/spec/dummy/package.json +2 -2
- data/react_on_rails_pro/spec/dummy/spec/helpers/react_on_rails_pro_helper_spec.rb +40 -11
- data/react_on_rails_pro/spec/dummy/spec/rails_helper.rb +1 -1
- data/react_on_rails_pro/spec/dummy/spec/requests/renderer_console_logging_spec.rb +5 -5
- data/react_on_rails_pro/spec/dummy/spec/system/integration_spec.rb +15 -10
- data/react_on_rails_pro/spec/dummy/spec/system/renderer_integration_spec.rb +3 -3
- data/react_on_rails_pro/spec/dummy/yarn.lock +4 -4
- data/react_on_rails_pro/spec/execjs-compatible-dummy/config/environments/production.rb +1 -1
- data/react_on_rails_pro/spec/execjs-compatible-dummy/config/initializers/react_on_rails.rb +16 -43
- data/react_on_rails_pro/spec/react_on_rails_pro/assets_precompile_spec.rb +15 -18
- data/react_on_rails_pro/spec/react_on_rails_pro/cache_spec.rb +1 -1
- data/react_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rb +5 -3
- data/react_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rb +27 -12
- data/react_on_rails_pro/spec/react_on_rails_pro/request_spec.rb +0 -27
- data/react_on_rails_pro/spec/react_on_rails_pro/spec_helper.rb +1 -1
- data/react_on_rails_pro/spec/react_on_rails_pro/stream_decorator_spec.rb +89 -0
- data/react_on_rails_pro/spec/react_on_rails_pro/stream_spec.rb +144 -0
- data/react_on_rails_pro/spec/react_on_rails_pro/support/caching.rb +1 -1
- data/react_on_rails_pro/spec/react_on_rails_pro/support/mock_block_helper.rb +4 -2
- data/sig/react_on_rails/controller.rbs +1 -1
- data/sig/react_on_rails/error.rbs +4 -0
- data/sig/react_on_rails/helper.rbs +2 -2
- data/sig/react_on_rails/json_parse_error.rbs +10 -0
- data/sig/react_on_rails/prerender_error.rbs +21 -0
- data/sig/react_on_rails/smart_error.rbs +28 -0
- data/sig/react_on_rails.rbs +3 -24
- metadata +14 -4
- data/lib/react_on_rails/pro_utils.rb +0 -37
- data/react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/TestingStreamableComponent.jsx +0 -15
|
@@ -1,25 +1,219 @@
|
|
|
1
|
-
#
|
|
2
|
-
_And Old Installation Instructions_
|
|
1
|
+
# Upgrading React on Rails Pro
|
|
3
2
|
|
|
4
|
-
##
|
|
3
|
+
## Upgrading from GitHub Packages to Public Distribution
|
|
5
4
|
|
|
6
|
-
|
|
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
|
-
|
|
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
|
-
|
|
17
|
-
|
|
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
|
-
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
|
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
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
|
@@ -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
|
|
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": "
|
|
3
|
-
"version": "16.2.0-beta.
|
|
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
|
|
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/
|
|
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 '
|
|
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); });
|